整体结构概览
unittest 原名为 PyUnit, 是由 java 的 JUnit 衍生而来. 对于单元测试, 需要设置预先条件, 对比预期结果和实际结果.
整体结构:
unittest 库提供了 test cases, test suites, test fixtures,test runner:
test case : 通过继承 TestCase 类, 我们可以创建一个 test, 或者一组 tests, 包括测试前准备环境的搭建 (setUp), 执行测试代码 (run), 以及测试后环境的还原 (tearDown).
test suites : 测试套件, 多个测试用例集合在一起, TestSuite 也可以嵌套 TestSuite.
test fixtures : setup + test case + teardown 结构
TestLoader: 用来加载 TestCase 到 TestSuite 中, 其中的方法从各个地方寻找 TestCase, 创建它们的实例, 然后 add 到 TestSuite 中, 返回一个 TestSuite 实例.
test runner: 执行测试用例, 其中的 run() 会执行 TestSuite/TestCase.
TextTestResult: 测试的结果会保存到 TextTestResult 实例中, 包括运行用例数, 成功数, 失败数等.
写好 TestCase, 然后由 TestLoader 加载 TestCase 到 TestSuite, 然后由 TextTestRunner 来运行 TestSuite, 运行的结果保存在 TextTestResult 中, 整个过程集成在 unittest.main 模块中.
注:
所有的测试函数以 test 开头, test_XXX.
简单的示例:
- import unittest
- class TestStringMethods(unittest.TestCase):
- def test_upper(self):
- self.assertEqual('foo',upper(),'FOO')
- def test_isupper(self):
- self.assertEqualTure('FOO'.isupper())
- self.assertFalse('Foo'.isupper())
- def test_split(self):
- s = 'hello world'
- self.assertEqual(s.split(),['hello','world'])
- with self.assertEqualRaise(TypeError):
- s.slipt(2)
- if __name__ == '__main__': #unittest.main: 为测试提供了入口.
- unittest.main()
- # 运行结果:
- ...
- ----------------------------------------------------------------------
- Ran 3 tests in 0.064s
- OK
其他与 unittest 类似的单元测试库: https://nose.readthedocs.org/en/latest/ , pytest.
命令行
从命令行中可以运行单元测试的模块, 类, 甚至单独的测试方法.
- python -m unittest test_module1 test_module2 #同时测试多个 module
- python -m unittest test_module.TestClass
- python -m unittest test_module.TestClass.test_method
显示更详细的测试结果的说明使用 - vflag:
python -m unittest -v test_module
查看所有的命令行选项使用命令
- python -m unittest -h
- TestCase
Testcase 类
class unittest.TestCase(methodName='runTest')
TestCase 的实例是最小的可测试单元. testcase 是由 unittest 的 https://docs.python.org/2/library/unittest.html#unittest.TestCase 类的实例表示的. 要编写自己的测试用例必须继承 TestCase 类, 或者使用 FunctionTestCase. 且 Testcase 类提供了各种 assert 的方法来检测预期结果和实际结果.
看下面的例子 (创建一个测试类 DefaultWidgetSizeTestCase):
- import unittest
- class DefaultWidgetSizeTestCase(unittest.TestCase): #unittest.TestCase 表示某个测试函数
- def runTest(self):
- widget = Widget('The widget')
- self.assertEqual(widget.size(), (50, 50), 'incorrect default size')
创建实例
建立这样一个测试用例的一个实例, 使用该类的构造函数, 且不带参数 (这样会执行所有的测试方法):
testCase = DefaultWidgetSizeTestCase()
我们也可以创建多个实例, 且每个实例之间都是独立的.
当我们需要对不同的实例执行不同的测试方法时, 我们可以将要执行的测试用例方法在创建实例时通过类参数传入.
- # 建了两个 WidgetTestCase 的实例, 每个实例只运行 WidgetTestCase 类中的一个测试方法 (通过参数传入)
- defaultSizeTestCase = WidgetTestCase('test_default_size')
- resizeTestCase = WidgetTestCase('test_resize')
常用断言方法 https://docs.python.org/2.7/library/unittest.html#unittest.TestCase.debug
unittest 库提供了很多实用方法来检测程序运行的结果和预期.
包括三种类型的方法, 每一种都覆盖了典型的类型, 比如:
检查相等值
逻辑比较
异常
如果给定的 assertion 通过了, 那么测试会执行下一行代码.
如果给定的 assertion 没有通过, 测试会暂停并且生成错误信息.
unittest 库提供所有标准的 xUnit assert 方法. 下面列出较重要方法的一部分:
检测元素是否相等:
assertEqual(a,b [,msg])
: 检测 a==b, 这个方法检查 a 是否等于 b, 常用语检测元素属性等. 如:
assertEqual(element.text, "10")
assertNotEqual(a,b [,smg]): 检测 a!==b.
检测表达式是否为 Ture, 或者 False:
- assertTrue(x [,msg]) #检测 bool(x) is True.
- # 如: 检测某个元素是否在页面上
- #assertTrue(element.is_dispalyed())
- assertFalse(x [,msg]) #检测 bool(y) is Flase.
- assertIsNot(a, b [,msg]) #检测 a is not b.
检测异常
- assertRaises(exc, fun, *args, **kwds)
- assertRaiseRegexp(exc, r, fun, *args, **kwds)
最有可能使用这些方法的是 NoSuchElementFoundexception
检测数字, 先四舍五入到指定的小数位数后再进行比较
- 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.
正则表达式, 检测正则是否匹配给定的 text
- assertRegexpMatches(s, r) #检测 r.search(s).
- assertNotRegexpMatches(s, r) #检测 not r.search(s).
检测字符串
assertMultiLineEqual(a, b) #检测 string
检测 lists 之间是否相等
assertListEqual(a, b) #检测 lists
fail() 无条件失败, 用户自定义
- fail()
- Test fixtures
方法固定装置:
如果要对一个模块中的每一个测试函数都做同样的初始化操作和结尾清除等操作, 那么创建 n 个测试用例就得写 n 遍一样的代码, 为了减少重复的代码, 可以使用下面两个函数:
setUp(): 每次执行测试用例之前调用. 无参数, 无返回值. 该方法抛出的异常都视为 error, 而不是测试不通过. 没有默认的实现.
tearDown(): 每次执行测试用例之后调用. 无参数, 无返回值. 测试方法抛出异常, 该方法也正常调用, 该方法抛出的异常都视为 error, 而不是测试不通过. 只用 setUp() 调用成功, 该方法才会被调用. 没有默认的实现.
通过 setup 和 tesrDown 组装一个 module 成为一个固定的测试装置.
注意: 如果 setup 运行抛出错误, 则测试用例代码则不会执行. 但是, 如果 setpu 执行成功, 不管测试用例是否执行成功都会执行 teardown.
Class 固定装置:
必须为类实现
setUpClass(): 一个类方法在单个类测试之前运行. setUpClass 作为唯一的参数被调用时, 必须使用 classmethod() 作为装饰器.
tearDownClass(): 一个类方法在单个类测试之后运行. setUpClass 作为唯一的参数被调用时, 必须使用 classmethod() 作为装饰器.
- import unittest
- class Test(unittest.TestCase):
- @classmethod
- def setUpClass(cls): #这里的 cls 是当前类的对象
- cls._connection = createExpensiveConnectionObject()
- @classmethod
- def tearDownClass(cls):
- cls._connection.destroy()
Module 固定装置:
必须为方法实现
- def setUpModule():
- createConnection()
- def tearDownModule():
- closeConnection()
使用 Text Suite 组织测试代码
unittest.TestSuite(tests=())
该类聚合测试用例和测试套件, 运行一个 TestSuite 实例遍历套件, 和单独运行每个 testcase 是相同的. TestSuite 对象的行为就像 TestCase 对象, 除了他们不实现一个测试.
一些方法可以将 testcase 添加到 TestSuite 实例:
addTest(test):Add a TestCase or TestSuite to the suite.
addTests(tests): 添加所有的 tests 从可迭代的 TestCase 和 TestSuite 实例测试套件. 这相当于迭代调用 addTest() 来添加每个元素.
根据不同的业务可能需要在不同的 module 中选择某一个或者几个测试用例, 此时可以根据每个测试实例的特征对测试方法打包:
- widgetTestSuite = unittest.TestSuite() #创建一个测试套件实例
- widgetTestSuite.addTest(WidgetTestCase('test_default_size')) #添加测试用例到套件, 抽取 WidgetTestCase 类中的 test_default_size 测试用例添加到 testsuite
- widgetTestSuite.addTest(WidgetTestCase('test_resize')) #添加测试用例到套件, 抽取 WidgetTestCase 类中的 test_resize 测试用例添加到 testsuite
可以返回该测试套件的 get 入口:
- def suite():
- suite = unittest.TestSuite()
- suite.addTest(WidgetTestCase('test_default_size'))
- suite.addTest(WidgetTestCase('test_resize'))
- return suite
或者更简洁的写法:
- def suite():
- tests = ['test_default_size', 'test_resize']
- return unittest.TestSuite(map(WidgetTestCase, tests))
测试套件中也可以包含测试套件:
- suite1 = module1.TheTestSuite()
- suite2 = module2.TheTestSuite()
- alltests = unittest.TestSuite([suite1, suite2])
使用 TestLoader
class unittest.TestLoader
TestLoader 用来从 clases 和 modules 创建 test suites, 通常也需要创建一个该类的实例, unittest 模块提供了一个实例, 可以作为 unittest.defaultTestLoader 共享. 使用一个子类或实例, 允许定制可配置属性.
该类有以下方法 https://docs.python.org/2.7/library/unittest.html#loading-and-running-tests :
- loadTestsFromTestCase(testCaseClass)
- :
- loadTestsFromModule(module)
: 返回一个给定的模块中所有测试用例, 打包成一个套件返回.
.....
该类创建一个 testsuites 然后加载一个 module 并执行其中所有的测试用例, 执行的顺序是根据测试用例的名称来的.
suite = unittest.TestLoader().loadTestsFromTestCase(WidgetTestCase) #执行 WidgetTestCase 中所有的测试用例
你可以将测试用例和测试套件放在一个 module 中, 最好是分开放置, 方便重构管理, 如果测试策略改变了, 也方便维护.
运行添加的测试套件:
- if __name__ == "__main__":
- login_test_suite = unittest.TestSuite()
- login_test_suite.addTest(Login('test_xxx'))
- login_test_suite.addTest(Login('test_xxx2'))
- runner = unittest.TextTestRunner()
- runner.run(login_test_suite)
跳过测试和预期的失败
Unittest 支持跳过单个的测试方法甚至整个类的测试. 使用 skip() decorator 来设置特定跳过的条件, 如指定操作系统不执行该测试.
- @unittest.skipIf(mylib.__version__ < (1, 3), "not supported in this library version")
- def test_format(self):
- # Tests that work for only a certain version of the library.
- pass
执行的时候如果满足跳过条件, 控制台会将后面的说明打印出来, 并跳过该测试用例. 跳过类也是相似的写法.
也可以自定义 skipping 装饰器.
定义预期的失败使用 unittest.expectedFailure() https://docs.python.org/2/library/unittest.html#unittest.expectedFailure , 运行时 , 如果测试失败, 测试不算作失败.
执行测试
用例执行顺序
unittest 的执行的顺序是根据测试用例的名称来的. 名称是按照英文字母排序. a-z.
每个测试用例之间的数据最好不要相互依赖, 如果一定要相互依赖则调整用力的执行顺序来保证运行的正确性.
要改变运行顺序, 可以使用 addTest 通过测试套件添加测试用例的顺序, 运行测试.
使用 HTMLTestRunner 生成报告
unittest 本身并不具备这个功能, 需要使用 HTMLTestRunner 库 https://pypi.python.org/pypi/HTMLTestRunner
使用步骤:
首先需要下载. py 文件: http://tungwaiyip.info/software/HTMLTestRunner.html
下载后放入 python 安装目录的 lib 文件夹下面.
打开终端进入 python 交互模式导入 HTMLTestRunner , 如果无导入错误显示, 则说明添加成功
import HTMLTestRunner
注意: python3 的 stringIO 有变化, 使用该 HTMLTestRunner 会报错 ImportError: No module named 'StringIO'. 这里使用 python2.7 试验.
1 help() 查看类帮助信息
为了说明在报告中显示用例的注释, 这里先了解下 help() 帮助函数.
help 帮助信息息都是代码中通过 '''xxx''' 或者 """xxx""" 注释符号来标注的.
如我们自定义一个模块 helpin.py, 并将其放入 python 目录的 lib 文件夹下面:
#coding=utf-8
u'''
这是 help 能够显示的帮助信息
'''
def add(a):
u"""这个函数用来输入 a 的值"""
print 'a'
在 cmd 命令行中, 进入 python27 的交互模式, 导入后通过 help 查看帮助信息:
>>> import helpin
>>> help(helpin)
Help on module helpin:
NAME
helpin - 这是 help 能够显示的帮助信息
FILE
e:\python27\lib\helpin.py
FUNCTIONS
add(a)
这个函数用来输入 a 的值
2 报告中显示用例的注释
给报告中的每个测试用例添加注释, 来说明该测试用例是用来干什么的, 非常有必要.
通过 help() 函数来查看 HTMLTestRunner 类的详细说明:
>>> import HTMLTestRunner
>>> help(HTMLTestRunner)
可以看到该类的注释说明, 一个好的开源的框架应该有这样的规范.
这里在每个测试函数的下方添加上注释:
def test_equal(self):
u'''这里是测试 a 和 b 的两个值是否相等'''
- a = 1
- b = 2
- self.assertEqual(a,b)
运行后, 打开生成的 html 文件可以看到, 每个测试用例函数的后面有该用例的注释.
3 动态生成需要多次执行的测试报告文件名称
如果一个测试套件需要多次执行, 如果每次执行不去修改测试报告. html 的文件名, 则每次执行后会将之前的报告覆盖. 如果想要保存每次执行的结果而不手动修改报告名称. 这里可以使用动态生成文件名的方法. 具体的生成可以根据需要来写动态生成的代码.
动态时间
python 时间函数:
time.time() 获取当前时间戳.
time.ctime() 当前时间的字符串形式.
time.localtime() 当前时间的 struct_time 形式.
time.strftime() 用来获得当前时间, 可以将时间格式化为字符串.
修改之前的代码, 将文件名字中添加上当前获取到的时间:
- # 获取当前时间
- now = time.strftime("%Y-%m-%d %H_%M_%S")
- # 定义报告文件存放位置, 这里将结果放在桌面
- outfile = open('C:\\Users\Administrator\\Desktop\\'+ now + '_result.html', "w")
生成的文件名称以 2016-03-09_17_37_26_result.html 格式出现.
4 集成多个测试结果
目前测试报告只集成到了单个测试文件中, 我们的最终目的是将其集成到一个文件中.
之前是通过
testunit.addTests()
函数逐个添加.
现在通过
unittest.defaultTestLoader.discover()
方法循环遍历添加测试用例.
最后通过 runner.run() 方法运行整个测试用例集.
来源: https://www.cnblogs.com/for-you/p/9258699.html