不写测试用例的程序员,不是好测试
以前在 Java 中 junit 是必用的单元测试框架,而 Python 中的 junit 就是 unittest
第一个 TestCase
少废话,先看代码1
2
3
4
5
6
7
8
9
10
11
12
13#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: wxnacy(wxnacy@gmail.com)
import unittest
class Test(unittest.TestCase):
def test_hello_world(self):
self.assertEqual(11, len('Hello World'))
if __name__ == "__main__":
unittest.main()
1 | . |
我们实现了一个最简单的测试用例,unittest 中最基本的结构就是 TestCase,新建一个类继承 unittest.TestCase
,类中每个 test
开头的方法都是一个测试函数(一定要 test 开头)
一个测试通过的用例,运行结果除了分割线,共三行
- 第一行,每个测试函数的运行状态,共四种状态,
.
为成功 - 第二行,运行的测试条数及用时
- 第三行,测试最终结果,一条失败则为 FAILED,并显示各种状态的条数
函数运行状态
测试函数的运行状态分四种
.
成功F
失败E
出错s
跳过
1 | #!/usr/bin/env python |
1 | .FEs |
这是一个包含四种状态的例子,test_3(), test_2()
分别报错和测试失败,在结果中给出了出错的位置和具体原因
现在的测试程序运行结果是简要信息,这取决于 unittest.main()
方法中的 verbosity
参数,默认值为 1,0 则不输出每条结果,2 则为详细信息,如下:1
2
3
4
5
6
7
8
9test_1 (__main__.Test)
test_1 func ... ok
test_2 (__main__.Test)
test_2 func ... FAIL
test_3 (__main__.Test)
test_3 func ... ERROR
test_4 (__main__.Test)
test_4 func ... skipped 'skip this test case'
...
跳过某条测试
在上面的例子中,test_4()
方法是跳过的,unittest 中共有三种装饰器可以跳过程序
- `@unittest.skip(reason)` 无条件跳过
- `@unittest.skipIf(condition, reason)` 当条件为真时跳过
- `@unittest.skipUnless(condition, reason)` 当条件为假时跳过
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: wxnacy(wxnacy@gmail.com)
# Description:
import unittest
class Test(unittest.TestCase):
def add(self):
return 1 + 1
def test_1(self):
'''test_1'''
self.assertEqual(2, self.add())
def test_2(self):
'''test_2'''
self.skipTest('skip test_2 case')
self.assertEqual(3, self.add())
def test_3(self):
self.assertEqual(3, self.add())
def test_4(self):
self.assertEqual(3, self.add())
if __name__ == "__main__":
unittest.main(verbosity=2)
1 | test_1 (__main__.Test) |
在详细结果中,会将跳过原因打印出来,并且在测试程序中,使用 self.skipTest(resaon)
也可以跳过程序
环境的准备和还原
一个完整的测试用例还需要一个环节,准备环境和还原环境,使用 setUp(), tearDown()
两个方法来完成1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: wxnacy(wxnacy@gmail.com)
import unittest
class Test(unittest.TestCase):
def add(self):
return 1 + 1
def setUp(self):
print('begin setUp')
def tearDown(self):
print('begin tearDown')
def test_1(self):
self.assertEqual(2, self.add())
print('run test_1')
def test_2(self):
self.assertEqual(2, self.add())
print('run test_2')
if __name__ == "__main__":
unittest.main()
1 | begin setUp |
从打印结果中可以看出,每次执行一个测试方法前后都会执行准备和还原操作,如果你想要一个 TestCase 只执行一次准备和还原操作,则需要使用这两个方法1
2
3
4
5
6
7
def setUpClass(cls):
print('begin setUpClass')
def tearDownClass(cls):
print('begin tearDownClass')
至此,我们已经经历一个完整的 TestCase 是什么样的,它有环境准备工作(setUp),运行测试代码(run),还原环境(tearDown),根据条件跳过(skip),共同组成了一个元测试(unit test),这样就是完整的测试用例
同时,我们需要开始考虑两个问题
- 怎样控制测试程序的执行顺序
- 如果同时执行多个测试用例
使用 TestSuite
针对刚才提出的问题,我们需要引入 TestSuite 的概念,多个测试用例组合起来,就是 TestSuite,TestSuite 也可以嵌套 TestSuite1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: wxnacy(wxnacy@gmail.com)
import unittest
class Test(unittest.TestCase):
def add(self):
return 1 + 1
def test_1(self):
self.assertEqual(2, self.add())
def test_2(self):
self.assertEqual(2, self.add())
def test_3(self):
self.assertEqual(2, self.add())
if __name__ == "__main__":
suite = unittest.TestSuite()
tests = [Test("test_1"), Test("test_3"), Test("test_2")]
suite.addTests(tests)
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
1 | test_1 (__main__.Test) ... ok |
在将多个 TestCase 调价到 TestSuite 中后,我们用到了 unittest.TextTestRunner()
方法,他会执行 TestSuite 或 TestCase 测 run()
方法,并把结果输出到 TextTestResult 中,后续我们将怎样将结果在打印到文件中
然后如你所见,我们已经可以控制测试程序的执行顺序,同样的,通过这样的方式,我们还可以进行多个 TestCase 同时测试
输出到文件
每次测试结果都输出控制台,从可读性和持久化上都是个问题,我们需要想办法将结果打印到文件中,这里我们需要借助下第三方文件 HTMLTestRunner ,但是原版只支持 python2,我做了下改动,适配了 python3,地址为:https://github.com/wxnacy/study/blob/master/python/unittest_demo/HTMLTestRunner.py
使用方法请见修改 HTMLTestRunner.py 可以在 Python3 运行
检测方法
我们在刚才的例子中只用到了 assertEqual()
方法来检测结果是否相同,还有很多方法可以用来检测测试结果1
2
3
4
5
6
7
8
9
10
11
12
13
14assertEqual(a, b) # a == b
assertNotEqual(a, b) # a != b
assertTrue(x) # bool(x) is True
assertFalse(x) # bool(x) is False
# 3.1 新加
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
# 3.2 新加
assertIsInstance(a, b) # isinstance(a, b)
assertNotIsInstance(a, b) # not isinstance(a, b)
更多方法见文档