test fixture 表示为了开展一项或多项测试所需要进行的准备工作,以及所有相关的清理操作。
举个例子,在测试之前我们可能需要包含创建临时或代理的数据库、目录,或者启动一个服务器进程等操作,这些都可以依赖fixture来实现。
我们可能同时存在多个前置操作相同的测试。
我们可以把测试的前置操作从测试代码中拆解出来,并实现测试前置方法 setUp() 。在运行测试时,测试框架会自动地为每个单独测试调用前置方法。
在测试运行时,若 setUp() 方法引发异常,测试框架会认为测试发生了错误,因此测试方法不会被运行。
同理 tearDown() 方法在测试方法运行后进行清理工作。
若 setUp() 成功运行,无论测试方法是否成功,都会运行 tearDown() 。
参考示例:
import unittest
class StudyTestCase(unittest.TestCase):
def setUp(self):
print("setUp : 准备完成,可以测试...")
def tearDown(self):
print("tearDown : 测试完成 ...")
def test_case1(self):
print("run : test_case1")
def test_case2(self):
print("run : test_case2")
@classmethod
def setUpClass(cls):
...
@classmethod
def tearDownClass(cls):
...
import unittest
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls._connection = createExpensiveConnectionObject()
@classmethod
def tearDownClass(cls):
cls._connection.destroy()
如果在setUpClass期间引发异常,则不会运行该类中的测试,也不会运行tearDownClass。 跳过的类将不会运行setUpClass或tearDownClass。 如果该异常是SkipTest异常,则该类将被报告为已跳过而不是错误。
类和模块级别的固定装置在TestSuite中实现。 当测试套件遇到来自新类的测试时,将调用前一类(如果有)的tearDownClass(),然后调用新类中的setUpClass()。
同样,如果测试来自与先前测试不同的模块,则运行来自先前模块的tearDownModule,然后运行来自新模块的setUpModule。
我们需要注意以下几点:
在运行所有测试之后,运行最后的tearDownClass和tearDownModule。
共享fixture不能很好地发挥[潜在的]特性,如测试并行化,它们会破坏测试隔离。使用时要小心。
unittest测试加载程序创建的测试的默认顺序是将来自相同模块和类的所有测试组合在一起 。 这将导致每个类和模块仅一次调用setUpClass / setUpModule(等)。 如果我们将顺序随机化,以使来自不同模块和类的测试彼此相邻,则可以在一次测试运行中多次调用这些共享的夹具功能。
共享fixture不适合使用非标准排序的套件。对于不希望支持共享fixture的框架,仍然存在一个BaseTestSuite。
如果在共享夹具功能之一期间引发了任何异常,则将测试报告为错误。 因为没有相应的测试实例,所以创建了_ErrorHolder对象(与TestCase具有相同的接口)来表示错误。 如果您只是使用标准的单元测试测试运行程序,那么此细节并不重要,但是如果您是框架作者,则可能很重要。
执行顺序为 :setUpModule 、setUpClass 、setUp TestCase(TestSuite) ,即按照模块、类、测试单元的setUp 。下来是 tearDown、tearDownClass、tearDownModule ,即按照与setUp相反的顺序来的。
import unittest
# 模块级夹具定义在模块中(测试类之外)
def setUpModule():
print("模块级夹具 setUpModule : study_testfixture2.py")
def tearDownModule():
print("模块级夹具 tearDownModule : study_testfixture2.py")
class Test(unittest.TestCase):
@classmethod # 装饰器,类成员方法
def setUpClass(cls):
print("类夹具 setUpClass ...")
@classmethod
def tearDownClass(cls):
print("类夹具 tearDownClass ...")
def test_case(self):
print("run : Test_case")
if __name__ == '__main__':
unittest.main()
# study_testfixture.py 模块
import os
import unittest
def setUpModule():
print("模块级夹具 setUpModule : study_testfixture.py")
def tearDownModule():
print("模块级夹具 tearDownModule : study_testfixture.py")
class StudyTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
print("类夹具 setUpClass : study_testfixture.py")
@classmethod
def tearDownClass(self):
print("类夹具 tearDownClass : study_testfixture.py")
def setUp(self):
print("setUp ... study_testfixture.py")
def tearDown(self):
print("tearDown ... study_testfixture.py")
def test_case1(self):
print("run : test_case1 : study_testfixture.py")
def test_case2(self):
print("run : test_case2 : study_testfixture.py")
if __name__ == '__main__':
suite = unittest.TestSuite()
testcases = unittest.defaultTestLoader.discover(
start_dir=os.getcwd(), # 目录:os.getcwd()当前目录路径
pattern='*.py' # 文件: 所有以 .py 结尾的文件中 test开头的测试单元
)
suite.addTests( testcases) # 加载测试用例集
unittest.main(defaultTest='suite')
# study_testfixture2.py 模块
import unittest
def setUpModule():
print("模块级夹具 setUpModule : study_testfixture2.py")
def tearDownModule():
print("模块级夹具 tearDownModule : study_testfixture2.py")
class Test(unittest.TestCase):
@classmethod # 装饰器,类成员方法
def setUpClass(cls):
print("类夹具 setUpClass : study_testfixture2.py")
@classmethod
def tearDownClass(cls):
print("类夹具 tearDownClass : study_testfixture2.py")
def test_case(self):
print("run : Test_case : study_testfixture2.py")
if __name__ == '__main__':
unittest.main()
如果setUpModule中引发了异常,则模块中的任何测试都不会运行,而tearDownModule也不会运行。 如果该异常是SkipTest异常,则该模块将被报告为已跳过而不是错误。
要添加即使在发生异常情况下也必须运行的清理代码,请使用addModuleCleanup。
在tearDownModule()之后添加一个要调用的函数,以清理测试类中使用的资源。 将以与添加功能相反的顺序调用这些功能(LIFO)。 添加它们时,将使用传递到addModuleCleanup()中的任何参数和关键字参数来调用它们。
如果setUpModule()失败,这意味着未调用tearDownModule(),则仍将调用添加的任何清理函数。
如果在tearDownModule()之后或在setUpModule()如果setUpModule()引发异常的情况下无条件调用此函数。
它负责调用addCleanupModule()添加的所有清除函数。 如果需要在tearDownModule()之前调用清除函数,则可以自己调用doModuleCleanups()。
doModuleCleanups()一次将方法从清理函数的堆栈中弹出,因此可以随时调用它。
如上 1.3.2 示例中,如果每个测试单元所需要的setUp等操作都是相同的,每个 py模块中都需要写入了 setUp、tearDown 等相关的函数,让代码显得很繁琐,不变于阅读和理解。那么我们可以选择将测试夹具进行封装,成为一个新的模块。
文件结构:
说明:在myunite中,设计MyUnite类继承unittest.TestCase类,然后实现 setUp 等方法。 在 study_testfixture.py 等模块中,只需要继承 Myunite 类即可。
runall模块:一次加载所有模块中的测试集,进行测试。
# myunite.py
import unittest
class MyUnite(unittest.TestCase):
@classmethod
def setUpClass(cls):
print("类夹具 setUpClass : study_testfixture.py")
@classmethod
def tearDownClass(self):
print("类夹具 tearDownClass : study_testfixture.py")
def setUp(self):
print("setUp ... study_testfixture.py")
def tearDown(self):
print("tearDown ... study_testfixture.py")
# study_testfixture.py
# 封装的unittest的setUp等操作
from 测试.fixture.myunite import MyUnite
class StudyTestCase(MyUnite):
def test_case1(self):
print("run : test_case1 : study_testfixture.py")
def test_case2(self):
print("run : test_case2 : study_testfixture.py")
# study_testfixture2.py
from 测试.fixture.myunite import MyUnite
class Test(MyUnite):
@classmethod # 装饰器,类成员方法
def setUpClass(cls):
print("类夹具 setUpClass : study_testfixture2.py")
@classmethod
def tearDownClass(cls):
print("类夹具 tearDownClass : study_testfixture2.py")
def test_case(self):
print("run : Test_case : study_testfixture2.py")
# runall.py
import os
import unittest
if __name__ == '__main__':
suite = unittest.TestSuite()
testcases = unittest.defaultTestLoader.discover(
start_dir=os.getcwd(), # 目录:os.getcwd()当前目录路径
pattern='*.py' # 文件: 所有以 .py 结尾的文件中 test开头的测试单元
)
suite.addTests(testcases) # 加载测试用例集
unittest.main(defaultTest='suite')
参考:https://docs.python.org/zh-cn/3/library/unittest.html?highlight=unittest#unittest.TestCase.assert
TestCase类提供了几种断言方法来检查和报告故障。 下表列出了最常用的方法(有关更多断言方法,请参见下表):
Method | Checks that |
---|---|
assertEqual(a, b) | a == b |
assertNotEqual(a, b) | a != b |
assertTrue(x) | bool(x) is True |
assertFalse(x) | bool(x) is False |
assertIs(a, b) | a is b |
assertIsNot(a, b) | a is not b |
assertIsNone(x) | x is None |
assertIsNotNone(x) | x is not None |
assertIn(a, b) | a in b |
assertNotIn(a, b) | a not in b |
assertIsInstance(a, b) | isinstance(a, b) |
assertNotIsInstance(a, b) | not isinstance(a, b) |
除此之外,也可以使用以下方法检查异常,警告和日志消息的产生:
Method |
Checks that |
---|---|
|
|
|
|
|
|
|
|
|
The |
assertRaises(exception, callable, *args, **kwds)
assertRaises(exception, *, msg=None)
测试是否使用还传递给assertRaises()的任何位置或关键字参数调用callable时引发了异常。 如果引发异常,则测试通过;如果引发另一个异常,则测试通过;如果未引发异常,则测试通过。 为了捕获一组异常中的任何一个,可以将包含异常类的元组作为异常传递。
assertRaisesRegex(exception, regex, callable, *args, **kwds)
assertRaisesRegex(exception, regex, *, msg=None)
像assertRaises()一样,也测试正则表达式是否与引发的异常的字符串表示形式匹配。 regex可以是正则表达式对象,也可以是包含适合re.search()使用的正则表达式的字符串。
assertWarnsRegex(warning, regex, callable, *args, **kwds)
assertWarnsRegex(warning, regex, *, msg=None)
像assertWarns()一样,还测试触发警告消息中的正则表达式是否匹配。 regex可以是正则表达式对象,也可以是包含适合re.search()使用的正则表达式的字符串。
还有其他用于执行更具体检查的方法,例如:
Method | Checks that |
---|---|
assertAlmostEqual(a, b) | round(a-b, 7) == 0 |
assertNotAlmostEqual(a, b) | round(a-b, 7) != 0 |
assertGreater(a, b) | a > b |
assertGreaterEqual(a, b) | a >= b |
assertLess(a, b) | a < b |
assertLessEqual(a, b) | a <= b |
assertRegex(s, r) | r.search(s) |
assertNotRegex(s, r) | not r.search(s) |
assertCountEqual(a, b) | a and b have the same elements in the same number, regardless of their order. |
下表总结了assertEqual()自动使用的类型特定方法的列表。 请注意,通常不必直接调用这些方法。
Method | Used to compare |
---|---|
assertMultiLineEqual(a, b) | strings |
assertSequenceEqual(a, b) | sequences |
assertListEqual(a, b) | lists |
assertTupleEqual(a, b) | tuples |
assertSetEqual(a, b) | sets or frozensets |
assertDictEqual(a, b) | dicts |
TestRunner 测试运行器默认是使用 TextTestRunner(文本的测试运行器),而对于生成的文本测试结果有时候并不是那么的直观,此时我们便可以选择其他的测试运行器。
下载HTMLTestRunner.py :http://tungwaiyip.info/software/HTMLTestRunner.html
将HTMLTestRunner.py 放到python安装目录下的 lib目录下。如果不知到python的安装目录可以使用如下命令查看:
官网发布的HTMLTestRunner.py 是基于 python2开发的,我们需要对其进行修改才能在 python3 中正常使用。
#第94行
import StringIO 修改为:import io
#第539行
self.outputBuffer = StringIO.StringIO() 修改为:self.outputBuffer = io.StringIO()
#第631行
print >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)修改为:print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime))
#第642行
if not rmap.has_key(cls):修改为:if not cls in rmap:
#第766行
uo = o.decode('latin-1')修改为:uo = o
#第772行
ue = e.decode('latin-1')修改为:ue = e
我们使用TextTestRunner作为测试器进行测试时的代码是这样的。
#使用TextTestRuner运行
unittest.TextTestRunner(suite)
而我们使用HTMLTestRunner作为测试器进行测试也可以使用同样的方式,只不过我们要先生成一个html文件,一遍在测试结束后向此文件中写入报告。
# 使用HTMLTestRunner运行
# 创建 html 文件
name = open(os.getcwd()+"/report,"wb")
# 创建运行器实例
runner = HTMLTestRunner(stream=name, # 输出到文件
title="Python自动化测试报告", # html title
description="报告详情如下:") # 描述
runner.run(suite)
# unittest.main(testRunner=runner,testcases=suite)
需要注意的是,使用HTMLTestRunner作为测试运行器时,TestCase中的帮助字符串将会作为生成的html的注释。
例如,现在我们有一个 my_math.py 的python程序。
# my_math.py
def fib(n = 0)->int:
""" 斐波那契数列 """
a, b = 0, 1
for i in range(n):
a, b = b, a+b
return a
def NarcissisticNumber(left=100, right=999)->list:
""" 水仙花数 """
assert(left >= 0 and right <= 999)
lst = []
for i in range(left, right+1):
s = str(i)
one = int(s[-1])
ten = int(s[-2])
hun = int(s[-3])
if i == one ** 3 + ten ** 3 + hun ** 3:
lst.append(i)
return lst
if __name__ == '__main__':
print("斐波那契数列第10项是:",fib(10))
print("10 - 1000 的水仙花数有:\n",NarcissisticNumber(100,999))
在此 my_math 模块中有两个方法,分别为 求斐波那契第n项 与 求范围内的水仙花数 。
下面我们要对这两个方法进行测试,只需要新建一个 test_my_math.py 文件,将 unittest 与 my_math 模块引入即可。
import unittest
from 测试.测试实例 import my_math
class MyMathTest(unittest.TestCase):
def test_fib(self):
print("测试",my_math.fib.__doc__)
a, b = 0, 1
for i in range(0, 50):
# 输出结果
# print("斐波那契数列第{0}项是{1}".format(i, my_math.fib(i)))
self.assertEqual(my_math.fib(i), a, "断言出错..")
a , b = b , a+b
def test_NarcissisticNumber(self):
print("测试",my_math.NarcissisticNumber.__doc__)
# 三位水仙花数
lst = [153,370,371,407]
res = my_math.NarcissisticNumber()
self.assertEqual(lst, res, "断言出错..")
if __name__ == '__main__':
unittest.main(verbosity=2)