Unittest

1. unittest 简介

2. unittest 代码示例

2.1 TestCase(测试用例)

2.2 TestSuite(测试集合)

2.3 按指定顺序执行测试方法

2.4 设置条件忽略指定测试方法

2.5 批量加载测试模块

3. 测试报告(unittest + HTMLTestRunner)

 

 

 

1. unittest 简介

1.1 什么是 unittest?

unittest 是 python 自带的一个单元测试框架,类似于 java 的 junit,基本结构是类似的。

unittest 中有 5 个重要的概念:TestCase、TestSuite、TestRunner、TestLoader、TestFixture

  1. Testcase:一个 TestCase 的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。单元测试(unit test)的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。
  2. TestSuite:多个测试用例集合在一起,就是 TestSuite,而且 TestSuite 也可以嵌套 TestSuite。
  3. TestRunner:是来执行测试用例的,其中的 run(test)会执行 TestSuite/TestCase 中的 run(result)方法。
  4. TestLoader:是用来加载 TestCase 到 TestSuite 中的,其中有几个 loadTestsFrom__() 方法,就是从各个地方寻找 TestCase,创建它们的实例,然后 add 到 TestSuite 中,再返回一个 TestSuite 实例。
  5. TestFixture:对一个测试用例环境的搭建和销毁,是一个 fixture,通过覆盖 TestCase 的 setUp() 和 tearDown() 方法来实现。这个有什么用呢?比如说在这个测试用例中需要访问数据库,那么可以在 setUp() 中建立数据库连接以及进行一些初始化,在 tearDown() 中清除在数据库中产生的数据,然后关闭连接。注意 tearDown 的过程很重要,要为以后的 TestCase 留下一个干净的环境。关于 fixture,还有一个专门的库函数叫做 fixtures,功能更加强大。

1.2 用法

  1. 用 import unittest 导入 unittest 模块;
  2. 定义一个继承自 unittest.TestCase 的测试用例类,如 class xxx(unittest.TestCase);
  3. 定义 setUp 和 tearDown,这两个方法与 junit 相同,即如果定义了则会在每个测试 case 执行前先执行 setUp 方法,执行完毕后执行 tearDown 方法;
  4. 定义测试用例,名字以 test 开头,unittest 会自动将 test 开头的方法放入测试用例集中;
  5. 一个测试用例应该只测试一个任务/方面/场景,测试目的和测试内容应很明确。并主要调用 assertEqual、assertRaises 等断言方法来判断程序执行结果和预期值是否相符;
  6. 调用 unittest.main() 启动测试;
  7. 如果测试未通过,则会显示 E,并给出具体的错误(此处为程序问题导致)。如果测试失败则显示为 F,测试通过为 如有多个 testcase,则结果依次显示。

1.3 常用断言方法

  • 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)

 

2. unittest 代码示例

2.1 TestCase(测试用例)

  • setUpClass() 和 tearDownClass() 方法在整个测试类运行过程中只被执行一次。
  • setUp() 和 tearDown() 在每个测试用例方法执行前和后均被调用。
 1 import unittest
 2 
 3 
 4 class mytest(unittest.TestCase):
 5 
 6     @classmethod
 7     def setUpClass(cls):
 8         '''初始化类固件'''
 9         pass
10 
11     @classmethod
12     def tearDownClass(cls):
13         '''重构类固件'''
14         pass
15 
16     # 初始化工作
17     def setUp(self):
18         pass
19 
20     # 退出清理工作
21     def tearDown(self):
22         pass
23 
24     # 具体的测试用例,一定要以test开头
25     def testSum(self):
26         assert 1+1 == 2
27 
28     def testSub(self):
29         assert 1-1 == 1
30 
31 
32 if __name__ == '__main__':
33     # 启动单元测试
34     unittest.main()

执行结果

在测试结果中,一个 F 表示一个测试方法执行不通过,一个 . 表示一个测试方法执行通过,共执行了两个测试方法。

F.
======================================================================
FAIL: testSub (__main__.mytest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "code.txt", line 29, in testSub
    assert 1-1 == 1
AssertionError

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)

 

2.2 TestSuite(测试集合)

 1 import unittest
 2 
 3 
 4 class MyTestCase1(unittest.TestCase):
 5 
 6     def setUp(self):
 7         pass
 8 
 9     def tearDown(self):
10         pass
11 
12     def testSum(self):
13         pass
14 
15     def testSub(self):
16         pass
17 
18 
19 class MyTestCase2(unittest.TestCase):
20 
21     def setUp(self):
22         pass
23 
24     def tearDown(self):
25         pass
26 
27     def testSum2(self):
28         pass
29 
30     def testSub2(self):
31         pass
32 
33 
34 if __name__ == '__main__':
35     # 根据给定的测试类,获取其中所有的'test'开头的测试方法,并返回一个测试套件
36     testCase1 = unittest.TestLoader().loadTestsFromTestCase(MyTestCase1)
37     testCase2 = unittest.TestLoader().loadTestsFromTestCase(MyTestCase2)
38     # 将多个测试套件组装成一个
39     suite = unittest.TestSuite([testCase1, testCase2])
40     # 设置verbosity=2可以打印更详细的执行信息
41     unittest.TextTestRunner(verbosity=2).run(suite)

执行结果

testSub (__main__.MyTestCase1) ... ok
testSum (__main__.MyTestCase1) ... ok
testSub2 (__main__.MyTestCase2) ... ok
testSum2 (__main__.MyTestCase2) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.006s

OK

 

2.3 按指定顺序执行测试方法

以 unittest.main() 方法启动的单元测试,各测试方法的执行顺序是以所有测试方法名的字符串的 ASCII 码排序后的顺序。

若想按顺序执行,则可按执行顺序增加测试方法到测试集合中。

 1 import unittest
 2 
 3 
 4 class MyTestCase1(unittest.TestCase):
 5 
 6     def setUp(self):
 7         pass
 8 
 9     def tearDown(self):
10         pass
11 
12     def testSum(self):
13         pass
14 
15     def testSub(self):
16         pass
17 
18 
19 class MyTestCase2(unittest.TestCase):
20 
21     def setUp(self):
22         pass
23 
24     def tearDown(self):
25         pass
26 
27     def testSum2(self):
28         pass
29 
30     def testSub2(self):
31         pass
32 
33 
34 if __name__ == '__main__':
35     suite = unittest.TestSuite()
36 
37     # 将测试用例按执行顺序加载到测试集合中
38     suite.addTest((MyTestCase2('testSum2')))
39     suite.addTest((MyTestCase2('testSub2')))
40     suite.addTest((MyTestCase1('testSum')))
41     suite.addTest((MyTestCase1('testSub')))
42 
43     unittest.TextTestRunner(verbosity=2).run(suite)

执行结果

testSum2 (__main__.MyTestCase2) ... ok
testSub2 (__main__.MyTestCase2) ... ok
testSum (__main__.MyTestCase1) ... ok
testSub (__main__.MyTestCase1) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.007s

OK

 

2.4 设置条件忽略指定测试方法

 1 import unittest
 2 import sys
 3 
 4 
 5 class MyTestCase1(unittest.TestCase):
 6 
 7     a = 6
 8 
 9     def setUp(self):
10         pass
11 
12     def tearDown(self):
13         pass
14 
15     # 无条件忽略该测试方法
16     @unittest.skip("skipping")
17     def testSum(self):
18         pass
19 
20     # 如果变量a>5,则忽略该测试方法
21     @unittest.skipIf(a>5, "condition is not satisfied!")
22     def testSub(self):
23         pass
24 
25 
26 class MyTestCase2(unittest.TestCase):
27 
28     def setUp(self):
29         pass
30 
31     def tearDown(self):
32         pass
33 
34     # 除非平台是Linux,否则忽略该测试方法
35     @unittest.skipUnless(sys.platform.startswith("linux"), "requires linux")
36     def testSum2(self):
37         pass
38 
39     def testSub2(self):
40         pass
41 
42 
43 if __name__ == '__main__':
44     suite = unittest.TestSuite()
45 
46     # 将测试用例按执行顺序加载到测试集合中
47     suite.addTest((MyTestCase2('testSum2')))
48     suite.addTest((MyTestCase2('testSub2')))
49     suite.addTest((MyTestCase1('testSum')))
50     suite.addTest((MyTestCase1('testSub')))
51 
52     unittest.TextTestRunner(verbosity=2).run(suite)

执行结果

testSum2 (__main__.MyTestCase2) ... skipped 'requires linux'
testSub2 (__main__.MyTestCase2) ... ok
testSum (__main__.MyTestCase1) ... skipped 'skipping'
testSub (__main__.MyTestCase1) ... skipped 'condition is not satisfied!'

----------------------------------------------------------------------
Ran 4 tests in 0.008s

OK (skipped=3)

 

2.5 批量加载测试模块

执行文件

1 import unittest
2 
3 
4 if __name__ == '__main__':
5 
6     # 加载当前目录下所有有效的测试模块,"."表示当前目录
7     testSuite = unittest.TestLoader().discover('.')
8     unittest.TextTestRunner(verbosity=2).run(testSuite)

同一目录下的 TestCase1.py

 1 import unittest
 2 
 3 class MyTestCase1(unittest.TestCase):
 4 
 5     def setUp(self):
 6         pass
 7 
 8     def tearDown(self):
 9         pass
10 
11     def testSum(self):
12         pass
13 
14     def testSub(self):
15         pass

同一目录下的 TestCase2.py

 1 import unittest
 2 
 3 class MyTestCase2(unittest.TestCase):
 4 
 5     def setUp(self):
 6         pass
 7 
 8     def tearDown(self):
 9         pass
10 
11     def testSum2(self):
12         pass
13 
14     def testSub2(self):
15         pass

执行结果

testSub (TestCase1.MyTestCase1) ... ok
testSum (TestCase1.MyTestCase1) ... ok
testSub2 (TestCase2.MyTestCase2) ... ok
testSum2 (TestCase2.MyTestCase2) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.007s

OK

 

3. 测试报告(unittest + HTMLTestRunner)

HTMLTestRunner 模块的下载地址:https://www.cnblogs.com/benpao1314/p/9633862.html

 1 import unittest
 2 import HTMLTestRunner  # 引入报告模板
 3 import math
 4 import ddt  # 引入数据驱动
 5 
 6 
 7 class Calc(object):
 8 
 9     def add(self, x, y, *d):
10         # 加法计算
11         result = x + y
12         for i in d:
13             result += i
14         return result
15 
16     def sub(self, x, y, *d):
17         # 减法计算
18         result = x - y
19         for i in d:
20             result -= i
21         return result
22 
23 
24 class SuiteTestCalc(unittest.TestCase):
25     def setUp(self):
26         self.c = Calc()
27 
28     @unittest.skip("skipping")
29     def test_Sub(self):
30         print ("sub")
31         self.assertEqual(self.c.sub(100, 34, 6), 61, u'求差结果错误!')
32 
33     def testAdd(self):
34         print ("add")
35         self.assertEqual(self.c.add(1, 32, 56), 89, u'求和结果错误!')
36 
37 
38 @ddt.ddt
39 class SuiteTestPow(unittest.TestCase):
40 
41     def setUp(self):
42         self.seq = list(range(10))
43 
44     # @unittest.skipIf()
45     def test_Pow(self):
46         print ("Pow")
47         self.assertEqual(pow(6, 3), 2161, u'求幂结果错误!')
48     
49     # 使用数据驱动传参可以让函数执行,实际上函数可并不需要用到传入的参数
50     # 一组数据会执行一次函数,因此ddt实际上可实现循环的效果
51     # @ddt.data(*[[i] for i in range(500)])  实现500次循环执行该函数
52     @ddt.data(['t1' ,'r1'] ,
53               ['t2' , 'r2'])
54     @ddt.unpack
55     def test_hasattr(self,data1,data2):
56         print ("hasattr")
57     # 检测math模块是否存在pow属性
58         self.assertTrue(hasattr(math, 'pow'), u"检测的属性不存在!")
59 
60 
61 if __name__ == "__main__":
62     suite1 = unittest.TestLoader().loadTestsFromTestCase(SuiteTestCalc)
63     suite2 = unittest.TestLoader().loadTestsFromTestCase(SuiteTestPow)
64     suite = unittest.TestSuite([suite1, suite2])
65     #unittest.TextTestRunner(verbosity=2).run(suite)
66     filename = "e:\\test.html"  # 定义报告的存放路径(支持相对路径)
67     # 以二进制方式打开文件
68     fp = open(filename, 'wb')
69     # 使用HTMLTestRunner配置参数,输出报告路径、报告标题、描述等。均可自定义配置
70     runner = HTMLTestRunner.HTMLTestRunner(stream = fp, title = '测试报告', description = '测试报告内容')
71     # 运行测试集合
72     runner.run(suite)
73     fp.close()

执行结果

<_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>
Time Elapsed: 0:00:00.002025
.F..

生成的 test.html 文件

 

posted @ 2021-01-11 18:05  Juno3550  阅读(231)  评论(0编辑  收藏  举报