unittest单元测试框架(八)
(九)unittest
1、基本概念
python自带的unittest单元测试框架不仅可以适用于单元测试,也适用于WEB自动化测试用例的开发与执行,uinttest测试框架可以实现执行测试用例,用断言方法将实际结果与期望结果进行比对,从而判断测试用例是否通过,最终出具测试报告,实现一个完整的测试流程。
unittest最核心的四个概念是:TestCase TestSuite TestLoader TestRunner
TestCase:一个testcase的实例就是一个测试用例;
继承unittest里面的TestCase类,继承这个类之后就可以写测试用例了,每个测试用例要记得引入fixture,做一些准备以及结束工作,写测试用例的编写步骤如下:
1、导入unittest模块/被测文件/被测文件其中的类;
2、创建一个测试类并继承unittest.TestCase;
3、重写setUp和tearDown方法(如果有初始化条件和结束条件的话就要写夹心饼干)
4、定义测试函数,函数名以test_开头;
5、调用unittest.main()方法运行测试用例;
TestSuite:多个测试用例集合在一起;
TestLoader:是用来加载TestCase到TestSuite中的;
TextTestRunner:用来执行测试用例的,其中run(test)会执行TestSuite/TestCase中的run(result)方法;
TextTestResult:保存TextTestTunner执行的测试结果;
fixture:测试用例环境的搭建和销毁,测试前准备环境的搭建(setUp),执行测试代码(run)以及测试后环境的还原(tearDown)
功能测试的主要流程:
1:写测试用例; --> TestCase
2:执行测试用例; -->TestSuite 存储用例 TestLoadder找用例,加载用例,存到TestSuite中去
3:对比实际结果与期望结果是否一致,判断测试用例是否通过; --> 也就是加断言 Assert
4:出具测试报告; -->TextTestRunner
unittest就可以很好的实现以上四点~
如果想了解更多关于unittest的内容,请戳这里👉:https://docs.python.org/3/library/unittest.html
接口测试的本质其实就是测试类里面的函数,通过数据驱动;
单元测试的本质就是测试函数,是属于代码级别的,通过代码;
2、属性介绍(此部分转载自:https://www.cnblogs.com/yufeihlf/p/5707929.html)
a、unittest的属性
接下来将对unittest模块的各个属性进行一一讲解:
unittest.TestCase:TestCase类,所有测试用例类继承的基本类;
unittest.main():使用她可以方便的将一个单元测试模块变为可直接运行的测试脚本,main()方法使用TestLoader类来搜索所有包含在该模块中以“test_”命名开头的测试方法,并自动执行他们。执行方法的默认顺序是:根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z,所以以A开头的测试用例方法会优先执行,以a开头会后执行;
unittest.TestSuite():unittest框架的TestSuite()类是用来创建测试套件的;
unittest.TextTextRunner():unittest框架的TextTextRunner()类,通过该类下面的run()方法来运行suite所组装的测试用例,入参为suite测试套件;
unittest.defaultTestLoader(): defaultTestLoader()类,通过该类下面的discover()方法可自动更具测试目录start_dir匹配查找测试用例文件(test*.py),并将查找到的测试用例组装到测试套件,因此可以直接通过run()方法执行discover;
unittest.skip():装饰器,当运行用例时,有些用例可能不想执行等,可用装饰器暂时屏蔽该条测试用例。一种常见的用法就是比如说想调试某一个测试用例,想先屏蔽其他用例就可以用装饰器屏蔽;
@unittest.skip(reason):skip(reason)装饰器:无条件跳过装饰的测试,并说明跳过测试的原因;
@unittest.skipIf(reason): skipIf(condition,reason)装饰器:条件为真时,跳过装饰的测试,并说明跳过测试的原因;
@unittest.skipUnless(reason): skipUnless(condition,reason)装饰器:条件为假时,跳过装饰的测试,并说明跳过测试的原因;
@unittest.expectedFailure(): expectedFailure()测试标记为失败;
b、TestCase类
setUp():setUp()方法用于测试用例执行前的初始化工作。例如测试用例中需要访问数据库,可以在setUp中建立数据库连接并进行初始化。例如测试用例需要登录web,可以先实例化浏览器;
tearDown():tearDown()方法用于测试用例执行之后的善后工作。如关闭数据库连接。关闭浏览器;
assert*():一些断言方法:在执行测试用例的过程中,最终用例是否执行通过,是通过判断测试得到的实际结果和预期结果是否相等决定的;
assertEqual(a,b,[msg='测试失败时打印的信息']):断言a和b是否相等,相等则测试用例通过;
assertNotEqual(a,b,[msg='测试失败时打印的信息']):断言a和b是否相等,不相等则测试用例通过;
assertTrue(x,[msg='测试失败时打印的信息']):断言x是否True,是True则测试用例通过;
assertFalse(x,[msg='测试失败时打印的信息']):断言x是否False,是False则测试用例通过;
assertIs(a,b,[msg='测试失败时打印的信息']):断言a是否是b,是则测试用例通过;
assertNotIs(a,b,[msg='测试失败时打印的信息']):断言a是否是b,不是则测试用例通过;
assertIsNone(x,[msg='测试失败时打印的信息']):断言x是否None,是None则测试用例通过;
assertIsNotNone(x,[msg='测试失败时打印的信息']):断言x是否None,不是None则测试用例通过;
assertIn(a,b,[msg='测试失败时打印的信息']):断言a是否在b中,在b中则测试用例通过;
assertNotIn(a,b,[msg='测试失败时打印的信息']):断言a是否在b中,不在b中则测试用例通过;
assertIsInstance(a,b,[msg='测试失败时打印的信息']):断言a是是b的一个实例,是则测试用例通过;
assertNotIsInstance(a,b,[msg='测试失败时打印的信息']):断言a是是b的一个实例,不是则测试用例通过;
c、TestSuit类的属性
addTest(): addTest()方法是将测试用例添加到测试套件中;
d、TextTestRunner类的属性
run(): run()方法是运行测试套件的测试用例,入参为suite测试套件;
例如:
1 runner = unittest.TextTestRunner() 2 runner.run(suite)
3、运用(测试用例执行情况、测试报告、上下文管理器、HTMLTestRunnerNew)
例子🌰:
写一个计算加法和乘法的类,文件目录如下:
1 class MathMethod: 2 def __init__(self, a, b): 3 self.a = a 4 self.b = b 5 6 # 加法 7 def add(self): 8 return self.a + self.b 9 10 # 乘法 11 def multi(self): 12 return self.a * self.b
对其进行测试:
1 import unittest 2 from class_1027_unittest.math_method import MathMethod 3 4 5 # 写一个测试类对MathMethod模块里面的类进行单元测试 6 7 class TestAdd (unittest.TestCase): # 继承了unittest里面的TestCase 8 # 编写测试用例 9 # 1:一个测试用例就是一个函数,不能传参,只有self关键字; 10 # 2:所有的用例都是以test开头,test_ 11 def test_add_two_positive(self): 12 res = MathMethod (1, 1).add () 13 print ("1+1的结果是", res) 14 15 def test_add_two_zero(self): 16 res = MathMethod (0, 0).add () 17 print ("0+0的结果是", res) 18 19 def test_add_two_negative(self): 20 res = MathMethod (-1, -2).add () 21 print ("-1+-2的结果是", res) 22 23 24 class TestMulti (unittest.TestCase): # 继承了unittest里面的TestCase 25 # 编写测试用例 26 # 1:一个测试用例就是一个函数,不能传参,只有self关键字; 27 # 2:所有的用例都是以test开头,test_ 28 def test_multi_two_positive(self): # 两个正数相乘 29 res = MathMethod (1, 1).multi () 30 print ("1*1的结果是", res) 31 32 def test_multi_two_zero(self): # 两个0相乘 33 res = MathMethod (0, 0).multi () 34 print ("0*0的结果是", res) 35 36 def test_multi_two_negative(self): # 两个负数相乘 37 res = MathMethod (-1, -2).multi() 38 print ("-1*-2的结果是", res) 39 40 41 if __name__ == '__main__': 42 unittest.main ()
最终打印出来的结果是:
......
----------------------------------------------------------------------
Ran 6 tests in 0.000s
OK
-1+-2的结果是 -3
1+1的结果是 2
0+0的结果是 0
-1*-2的结果是 2
1*1的结果是 1
0*0的结果是 0
注:打印出来的结果里面一个.表示成功(有几条用例执行成功就会有几个点),E代表代码出错,F代表测试用例执行失败;执行用例的时候会按照ASCII编码的顺序来执行:0-9 A-Z,a-z;当鼠标放在某一个测试用例边上的时候就会只会执行鼠标停的地方有的用例,要执行全部的测试用例一定要注意鼠标放置的地方。
测试用例执行情况:
单独执行一条用例和多条用例的例子如下:
1 import unittest 2 from class_1027_unittest.class_01 import TestAdd #将需要测试的类进行导入 3 from class_1027_unittest.class_01 import TestMulti 4 5 suite = unittest.TestSuite () # 存储用例 6 7 # 1:执行一条用例 addTest 8 9 suite.addTest (TestAdd ('test_add_two_positive')) 10 suite.addTest (TestAdd ('test_add_two_zero')) 11 suite.addTest (TestAdd ('test_add_two_negative')) 12 13 # 2:执行多条用例 TestLoader 14 from class_1027_unittest import class_01 15 16 loder = unittest.TestLoader () # 创建一个加载器 17 18 # 从测试类里面去找测试用例 19 suite.addTest (loder.loadTestsFromTestCase (TestMulti)) 20 suite.addTest (loder.loadTestsFromTestCase (TestAdd)) 21 22 # 从测试模块里面去找测试用例 23 suite.addTest (loder.loadTestsFromModule (class_01)) # 具体到模块名 24 25 # 执行 26 runner = unittest.TextTestRunner () 27 runner.run (suite)
以上关于测试单独一条测试用例和多条测试用例的情况都可以去试试看,以便更好的掌握方法~
加断言Assert:
常用的断言有以下几个:
assertEqual(a,b):判断a==b
assertNotEqual(a,b):判断a!=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
例如(跟上面说是进行测试的代码基本一致,就是加了断言的部分):
1 import unittest 2 from class_1027_unittest.math_method import MathMethod 3 4 5 # 写一个测试类对MathMethod模块里面的类进行单元测试 6 7 class TestAdd (unittest.TestCase): # 继承了unittest里面的TestCase 8 # 编写测试用例 9 # 1:一个测试用例就是一个函数,不能传参,只有self关键字; 10 # 2:所有的用例都是以test开头,test_ 11 def test_add_two_positive(self): 12 res = MathMethod (1, 1).add () # 实际发起请求的结果值 13 print ("1+1的结果是", res) 14 # 加断言:判断期望值与实际值的比对结果 一致则通过 不一致则失败 15 self.assertEqual (2, res) 16 17 def test_add_two_zero(self): 18 res = MathMethod (0, 0).add () 19 print ("0+0的结果是", res) 20 self.assertEqual (1, res, "两个0相加的值不对!") # 断言里面的msg是用例执行失败的时候才会显示出来 21 22 def test_add_two_negative(self): 23 res = MathMethod (-1, -2).add () 24 print ("-1+-2的结果是", res) 25 self.assertEqual (-3, res) 26 27 28 class TestMulti (unittest.TestCase): # 继承了unittest里面的TestCase 29 # 编写测试用例 30 # 1:一个测试用例就是一个函数,不能传参,只有self关键字; 31 # 2:所有的用例都是以test开头,test_ 32 def test_multi_two_positive(self): # 两个正数相乘 33 res = MathMethod (1, 1).multi () 34 print ("1*1的结果是", res) 35 36 def test_multi_two_zero(self): # 两个0相乘 37 res = MathMethod (0, 0).multi () 38 print ("0*0的结果是", res) 39 40 def test_multi_two_negative(self): # 两个负数相乘 41 res = MathMethod (-1, -2).multi () 42 print ("-1*-2的结果是", res) 43 44 45 if __name__ == '__main__': 46 unittest.main ()
打印出来的结果是:
..F...
-1+-2的结果是 -3
1+1的结果是 2
======================================================================
0+0的结果是 0
FAIL: test_add_two_zero (__main__.TestAdd)
-1*-2的结果是 2
1*1的结果是 1
----------------------------------------------------------------------
0*0的结果是 0
Traceback (most recent call last):
File "/Users/xuzhulin/PycharmProjects/python_api_review/class_1027_unittest/class_01.py", line 27, in test_add_two_zero
self.assertEqual (1, res, "两个0相加的值不对!") # 断言里面的msg是用例执行失败的时候才会显示出来
AssertionError: 1 != 0 : 两个0相加的值不对!
----------------------------------------------------------------------
Ran 6 tests in 0.001s
FAILED (failures=1)
接下来再讲测试报告的部分:
例如:
1 import unittest 2 from class_1027_unittest.class_01 import TestAdd 3 from class_1027_unittest.class_01 import TestMulti 4 5 suite = unittest.TestSuite () # 存储用例 6 7 # 1:执行一条用例 8 9 suite.addTest (TestAdd ('test_add_two_positive')) 10 suite.addTest (TestAdd ('test_add_two_zero')) 11 suite.addTest (TestAdd ('test_add_two_negative')) 12 13 # 2:执行多条用例 TestLoader 14 from class_1027_unittest import class_01 15 16 loder = unittest.TestLoader () # 创建一个加载器 17 18 # 从测试类里面去找测试用例 19 suite.addTest (loder.loadTestsFromTestCase (TestMulti)) 20 suite.addTest (loder.loadTestsFromTestCase (TestAdd)) 21 22 # 从测试模块里面去找测试用例 23 suite.addTest (loder.loadTestsFromModule (class_01)) # 具体到模块名 24 25 # 执行 26 file = open ("test.txt", 'w+', encoding='utf-8') 27 28 # stream:指定输出渠道,verbosity是指输出的内容详细度 数字越大越详细 0 1 2 29 runner = unittest.TextTestRunner (stream=file, verbosity=2) 30 31 runner.run (suite)
以上的代码执行结束后,会在目录中新增一个名为"test.txt"的文件:
文件的内容为:
1 test_add_two_positive (class_1027_unittest.class_01.TestAdd) ... ok 2 test_add_two_zero (class_1027_unittest.class_01.TestAdd) ... FAIL 3 test_add_two_negative (class_1027_unittest.class_01.TestAdd) ... ok 4 test_multi_two_negative (class_1027_unittest.class_01.TestMulti) ... ok 5 test_multi_two_positive (class_1027_unittest.class_01.TestMulti) ... ok 6 test_multi_two_zero (class_1027_unittest.class_01.TestMulti) ... ok 7 test_add_two_negative (class_1027_unittest.class_01.TestAdd) ... ok 8 test_add_two_positive (class_1027_unittest.class_01.TestAdd) ... ok 9 test_add_two_zero (class_1027_unittest.class_01.TestAdd) ... FAIL 10 test_add_two_negative (class_1027_unittest.class_01.TestAdd) ... ok 11 test_add_two_positive (class_1027_unittest.class_01.TestAdd) ... ok 12 test_add_two_zero (class_1027_unittest.class_01.TestAdd) ... FAIL 13 test_multi_two_negative (class_1027_unittest.class_01.TestMulti) ... ok 14 test_multi_two_positive (class_1027_unittest.class_01.TestMulti) ... ok 15 test_multi_two_zero (class_1027_unittest.class_01.TestMulti) ... ok 16 17 ====================================================================== 18 FAIL: test_add_two_zero (class_1027_unittest.class_01.TestAdd) 19 ---------------------------------------------------------------------- 20 Traceback (most recent call last): 21 File "/Users/xuzhulin/PycharmProjects/python_api_review/class_1027_unittest/class_01.py", line 27, in test_add_two_zero 22 self.assertEqual (1, res, "两个0相加的值不对!") # 断言里面的msg是用例执行失败的时候才会显示出来 23 AssertionError: 1 != 0 : 两个0相加的值不对! 24 25 ====================================================================== 26 FAIL: test_add_two_zero (class_1027_unittest.class_01.TestAdd) 27 ---------------------------------------------------------------------- 28 Traceback (most recent call last): 29 File "/Users/xuzhulin/PycharmProjects/python_api_review/class_1027_unittest/class_01.py", line 27, in test_add_two_zero 30 self.assertEqual (1, res, "两个0相加的值不对!") # 断言里面的msg是用例执行失败的时候才会显示出来 31 AssertionError: 1 != 0 : 两个0相加的值不对! 32 33 ====================================================================== 34 FAIL: test_add_two_zero (class_1027_unittest.class_01.TestAdd) 35 ---------------------------------------------------------------------- 36 Traceback (most recent call last): 37 File "/Users/xuzhulin/PycharmProjects/python_api_review/class_1027_unittest/class_01.py", line 27, in test_add_two_zero 38 self.assertEqual (1, res, "两个0相加的值不对!") # 断言里面的msg是用例执行失败的时候才会显示出来 39 AssertionError: 1 != 0 : 两个0相加的值不对! 40 41 ---------------------------------------------------------------------- 42 Ran 15 tests in 0.003s 43 44 FAILED (failures=3)
大家也可以修改详细度自行体会一下~
再讲一个可以生成html测试报告的东东:
关于HTMLTestRunnerNew大家可以移步到我写的这篇博客👉: https://www.cnblogs.com/xuxiaozhu/p/10724443.html
import HTMLTestRunnerNew
测试报告的部分改为以下的内容:
1 with open("test_report.heml",'wb') as file:
2 runner=HTMLTestRunnerNew.HTMLTestRunner(stream=file,
3 verbosity=2,
4 title='python单元测试报告',
5 description='第一次测试报告',
6 tester='小猪')
7 runner.run(suite)
执行结束之后在目录文件夹会新增一个名为"test_report.html"的文件,选中右键选择Browser打开就可以看到html格式的测试报告了:
测试报告中红框的部分均可点击查看详细的信息内容~
再讲一个很重要的东东:上下文管理器
其实按照上上面的代码的写法来说是没有将文件进行关闭的,一般来说都比较容易忘记关闭文件,忘记关闭文件带来的影响就是会占用资源,造成浪费,所以这个时候就很有必要用到上下文管理器来解决这个问题:
1 import unittest 2 from class_1027_unittest.class_01 import TestAdd 3 from class_1027_unittest.class_01 import TestMulti 4 5 suite = unittest.TestSuite () # 存储用例 6 7 # 1:执行一条用例 8 9 suite.addTest (TestAdd ('test_add_two_positive')) 10 suite.addTest (TestAdd ('test_add_two_zero')) 11 suite.addTest (TestAdd ('test_add_two_negative')) 12 13 # 2:执行多条用例 TestLoader 14 from class_1027_unittest import class_01 15 16 loder = unittest.TestLoader () # 创建一个加载器 17 18 # 从测试类里面去找测试用例 19 suite.addTest (loder.loadTestsFromTestCase (TestMulti)) 20 suite.addTest (loder.loadTestsFromTestCase (TestAdd)) 21 22 # 从测试模块里面去找测试用例 23 suite.addTest (loder.loadTestsFromModule (class_01)) # 具体到模块名 24 25 # 执行 上下文管理器 26 27 with open ("test.txt", 'w+', encoding='utf-8') as file: 28 29 # stream:指定输出渠道,verbosity是指输出的内容详细度 数字越大越详细 0 1 2 30 runner = unittest.TextTestRunner (stream=file, verbosity=2) 31 runner.run (suite)
可以将文件是不是关闭的状态打印出来看看:print(file.closed),打印结果一定是True,这就说明文件已经被关闭了。
以上,第八部分到此结束~