🔥前言
哲学三问:什么是Unittest?Unittest可以做什么?为什么用Unitest?
1️⃣Python自带的单元测试框架,此外基于Python还有其他的单元测试框架:pytest,doctest,nose等
2️⃣编写规范的测试用例,组织测试用例,生成测试结果
3️⃣自动化编写脚本(自动化测试用例)通常使用单元测试框架来编写,组织和生成测试结果
下面就是实操环节了,尽情期待吧!
🚀unittest编写测试用例
第一步:打开你已经装好的神器:pyCharm,没错就是这个东西:
第二步:新建一个工程---->unitTest1
第三步:建立一个简单的被测试文件(包含了加减乘除的函数类)---->count.py
class Count: def __init__(self, a, b): self.a = a self.b = b def add(self): c = self.a + self.b return c def sub(self): d = self.a - self.b return d def div(self): e = self.a * self.b return e def mul(self): e = self.a / self.b return e
第四步:根据被测函数使用unittest编写测试代码创建测试文件----Testcount.py
1️⃣这里记得选Python unit Test创建Python测试文件:
2️⃣随后会自动生成这些代码:
import unittest class MyTestCase(unittest.TestCase): def test_something(self): self.assertEqual(True, False) # add assertion here if __name__ == '__main__': unittest.main()
第五步:开始编写构造测试用例函数了:
import unittest from count import Count class TestCaseCount(unittest.TestCase): def setUp(self) -> None: # 每个测试用例开始前执行 print("这是执行的测试准备阶段!我要开始测试了!") # add assertion here def test_add1(self): # 定义测试步骤与断言 print("我执行的是加法函数测试!") # 添加该print语句帮助我们了解test_add1何时被执行 c1 = Count(1, 2) # 根据Count类生成对象c1,会自动调用Count类里的init方法 r1 = c1.add() # r1保存的是实际被测代码的运行结果 self.assertEqual(r1, 3) # 将实际结果跟预期结果做等值比较,相等测试通过,不等测试失败 def test_sub2(self): # 定义测试步骤与断言 print("我执行的是减法函数测试!") # 添加该print语句帮助我们了解test_sub2何时被执行 d1 = Count(2, 1) # 根据Count类生成对象d1,会自动调用Count类里的init方法 r2 = d1.sub() # r2保存的是实际被测代码的运行结果 self.assertEqual(r2, 1) # 将实际结果跟预期结果做等值比较,相等测试通过,不等测试失败 def test_div3(self): # 定义测试步骤与断言 print("我执行的是乘法函数测试!") # 添加该print语句帮助我们了解test_div3何时被执行 e1 = Count(2, 2) # 根据Count类生成对象e1,会自动调用Count类里的init方法 r3 = e1.div() # r3保存的是实际被测代码的运行结果 self.assertEqual(r3, 4) # 将实际结果跟预期结果做等值比较,相等测试通过,不等测试失败 def test_mul4(self): # 定义测试步骤与断言 print("我执行的是除法函数测试!") # 添加该print语句帮助我们了解test_mul4何时被执行 f1 = Count(4, 2) # 根据Count类生成对象c1,会自动调用Count类里的init方法 r4 = f1.mul() # r1保存的是实际被测代码的运行结果 self.assertEqual(r4, 2) # 将实际结果跟预期结果做等值比较,相等测试通过,不等测试失败 def tearDown(self) -> None: # 每个测试用例结束后执行 print("这是执行测试的结束,收拾残局阶段!!") if __name__ == '__main__': unittest.main()
PS:执行Python文件的内部调用与外部调用
🚀unittest测试用例执行
上面的测试用例函数结构是:
方法名必须是以“test_”打头,然后再是定义测试步骤与断言,断言会在下面讲到。现在是运行截图:
🚀unittest常见的断言方法
1️⃣assertEqual(a,b):判断a,b是否相等,如果相等,测试通过,如果不相等,测试失败
2️⃣assertNotEqual(a,b):判断a,b是否不相等,如果相等,测试失败
3️⃣assertTrue(x):用于判断bool(x)是否是True,如果不是True,测试失败 与1️⃣等效
4️⃣assertFalse(x):用于判断bool(x)是否是False,如果不是False,测试失败 与2️⃣等效
比如一个函数判断一个数是否是素数便可用3、4的断言方法
5️⃣assertIn(a,b):用于判断a是否在b中,如果a不在b中,则测试失败
6️⃣assertNotIn(a,b)和上个结果相反
🚀unittest测试结果分析
测试结果有三种:
1️⃣测试通过–》 .
一个点表示一个用例通过,上面的图片已经指出,下面着重介绍另外两种
2️⃣测试失败–》
测试的函数方法与预期结果不符合便是测试失败;比如我把加法的方法改一下:
def add(self): c = self.a return c
然后再进行测试:
显示三个用例成功,一个用例失败。
3️⃣测试错误–》
被测试代码或语法原因产生的错误:
现在我把被测试函数的默认属性方法改错:
# def __init__(self, a, b): def __int__(self, a, b): self.a = a self.b = b
执行后直接报错:
🚀unittest测试用例的执行顺序
测试方法的执行跟编写的顺序无关,而是根据test_
后面接的ASCII码的顺序来执行(0-9,A-Z, _, a-z编码越小执行的优先级越高)
当然我们也可以按照自定义的顺序来执行:
不使用unittest的main方法默认顺序执行,而是通过改为使用unittest提供的TestSuit(测试套件)+TestRunner(测试运行器)的方式可以实现自定义顺序执行测试方法
我把下面main方法改一下:
if __name__ == '__main__': # unittest.main() # 调用main()使用的是默认执行顺序 # 使用test suite+test runner来自定义执行顺序 # 一、按自定义顺序加载测试方法到测试套件里 ts = unittest.TestSuite() # 调用unittest提供的TestSuite类生成对象ts ts.addTest(TestCaseCount("test_mul4"))# 调用TestSuite里的addTest方法来加载TestCaseCount类里的test_mul4测试方法 ts.addTest(TestCaseCount("test_div3"))# 调用TestSuite里的addTest方法来加载TestCaseCount类里的test_div3测试方法 ts.addTest(TestCaseCount("test_sub2"))# 调用TestSuite里的addTest方法来加载TestCaseCount类里的test_sub2测试方法 ts.addTest(TestCaseCount("test_add1"))# 调用TestSuite里的addTest方法来加载TestCaseCount类里的test_add1测试方法 # 二、使用测试运行器执行测试套件里的用例,生成测试结果 tr = unittest.TextTestRunner() # 调用unittest提供的TextTestRunner类生成对象tr tr.run(ts) # 调用TextTestRunner里的run方法来执行测试套件ts里的用例执行并生成结果
执行结果如下:
🚀跨文件组织测试用例
被测功能点肯定不止一个,而是多个,当被测功能点不断扩展,相应的测试代码也会不断的增多,这些测试代码不可能全部写在一个文件里。此时,通常的处理办法:根据所测功能点的不同,将相应的测试代码分散在不同的文件里,然后组织这些分散在不同文件里的代码一起执行。
组织分散在不同文件里的测试代码一起执行的常见办法:
🅰️Test Suite+Test Runner
🅱️discover
下面我将创建一个测试文件testcount2,里面的测试用例为其他测试数,两个runtest测试文件来区分上面🅰️,🅱️两种不同的执行方法
testcount2的文件代码为:
import unittest from count import Count class TestCaseCount(unittest.TestCase): def setUp(self) -> None: # 每个测试用例开始前执行 print("这是第二次执行的测试准备阶段!我要开始测试了!") # add assertion here def test_add1(self): # 定义测试步骤与断言 print("我执行的是第二次加法函数测试!") # 添加该print语句帮助我们了解test_add1何时被执行 c1 = Count(2, 2) # 根据Count类生成对象c1,会自动调用Count类里的init方法 r1 = c1.add() # r1保存的是实际被测代码的运行结果 self.assertEqual(r1, 4) # 将实际结果跟预期结果做等值比较,相等测试通过,不等测试失败 def test_sub2(self): # 定义测试步骤与断言 print("我执行的是第二次减法函数测试!") # 添加该print语句帮助我们了解test_sub2何时被执行 d1 = Count(4, 1) # 根据Count类生成对象d1,会自动调用Count类里的init方法 r2 = d1.sub() # r2保存的是实际被测代码的运行结果 self.assertEqual(r2, 3) # 将实际结果跟预期结果做等值比较,相等测试通过,不等测试失败 def test_div3(self): # 定义测试步骤与断言 print("我执行的是第二次乘法函数测试!") # 添加该print语句帮助我们了解test_div3何时被执行 e1 = Count(1, 2) # 根据Count类生成对象e1,会自动调用Count类里的init方法 r3 = e1.div() # r3保存的是实际被测代码的运行结果 self.assertEqual(r3, 2) # 将实际结果跟预期结果做等值比较,相等测试通过,不等测试失败 def test_mul4(self): # 定义测试步骤与断言 print("我执行的是第二次除法函数测试!") # 添加该print语句帮助我们了解test_mul4何时被执行 f1 = Count(1, 1) # 根据Count类生成对象c1,会自动调用Count类里的init方法 r4 = f1.mul() # r1保存的是实际被测代码的运行结果 self.assertEqual(r4, 1) # 将实际结果跟预期结果做等值比较,相等测试通过,不等测试失败 def tearDown(self) -> None: # 每个测试用例结束后执行 print("这是第二次执行测试的结束,收拾残局阶段!!") if __name__ == '__main__': unittest.main()
runtest1的代码为:所使用的是Test Suite+Test Runner方法
import unittest import testcount import testcount2 if __name__ == '__main__': # unittest.main() # 调用main()使用的是默认执行顺序 # 使用test suite+test runner来自定义执行顺序 # 一、按自定义顺序加载测试方法到测试套件里 ts = unittest.TestSuite() # 调用unittest提供的TestSuite类生成对象ts ts.addTest(testcount.TestCaseCount("test_mul4")) # 调用TestSuite里的addTest方法来加载TestCaseCount类里的test_mul4测试方法 ts.addTest(testcount2.TestCaseCount2("test_mul4")) ts.addTest(testcount.TestCaseCount("test_div3")) # 调用TestSuite里的addTest方法来加载TestCaseCount类里的test_div3测试方法 ts.addTest(testcount2.TestCaseCount2("test_div3")) ts.addTest(testcount.TestCaseCount("test_sub2")) # 调用TestSuite里的addTest方法来加载TestCaseCount类里的test_sub2测试方法 ts.addTest(testcount2.TestCaseCount2("test_sub2")) ts.addTest(testcount.TestCaseCount("test_add1")) # 调用TestSuite里的addTest方法来加载TestCaseCount类里的test_add1测试方法 ts.addTest(testcount2.TestCaseCount2("test_add1")) # 二、使用测试运行器执行测试套件里的用例,生成测试结果 tr = unittest.TextTestRunner() # 调用unittest提供的TextTestRunner类生成对象tr tr.run(ts) # 调用TextTestRunner里的run方法来执行测试套件ts里的用例执行并生成结果
运行结果截图:
runtest2的代码为:所使用的是discover方法
import unittest if __name__ == '__main__': # unittest.main() # 使用discover加载testcount.py和testcount2.py里的用例一起执行 # 自动发现unitTest1包下的testcount打头的Python文件里的测试方法,将其加载到测试套件ts里 ts = unittest.defaultTestLoader.discover("./", pattern="testcount*.py") # 运行器运行套件用例 tr =unittest.TextTestRunner() tr.run(ts)
运行截图如下:
🚀HTMLTestRunner生成测试报告
既然我们做的是测试,那么便要实现生成独立的测试报告:这里我们需要用到第三方库HTMLTestRunner.py------>独立生成HTML文件格式的测试文档:
在网上(包括在Python官方文库)找不到HTMLTestRunner相关解释资料。其实HTMLTestRunner是一个第三方的unittest HTML报告库,关于unittest在Python官方文库上很容易找到:然后下载下来放到Python的lib文件下
具体实现代码在runtest2下,代码修改如下:
import unittest import HTMLTestRunner if __name__ == '__main__': # unittest.main() # 使用discover加载testcount.py和testcount2.py里的用例一起执行 # 自动发现unitTest1包下的testcount打头的Python文件里的测试方法,将其加载到测试套件ts里 ts = unittest.defaultTestLoader.discover("./", pattern="testcount*.py") # 运行器运行套件用例 # tr =unittest.TextTestRunner() # tr.run(ts) # 除了可以使用unittest提供的TextTestRunner文本测试运行器来执行用例生成测试结果以外, # 还可以使用HTMLTestRunner.py模块提供的HTMLTestRunner类来执行用例生成独立的测试报告 # 以二进制写的模式打开当前目录下的report.html文件,准备往里面写内容,如果文件不存在则自动创建 f = open('./report.html', 'wb') tr = HTMLTestRunner.HTMLTestRunner(stream=f, title="四则运算测试报告", description="说明信息") tr.run(ts) f.close()
生成的HTML文件测试报告截图如下:
⭐️⭐️⭐️总结
test case | 测试用例 | unittest提供了TestCase类用来编写测试用例 |
test suite | 测试套件 | unittest提供了TestSuite类用来组装测试用例生成测试用例集合 |
test runner | 测试运行器 | unittest提供了TextTestRunner类用来执行测试用例生成测试结果 |
test fixture | 测试固件 | unittest提供了一系列的固件:setUp,tearDown就是测试固件的一种,用来完成测试前的准备工作和测试后的清理工作。 |