Unittest单元测试框架
1、unittest是什么
unittest是python内置的单元测试框架,具备编写用例、组织用例、执行用例、输出报告等自动化框架的条件
2、单元测试框架的优点
一般来说不用单元测试框架也能编写单元测试,因为单元测试本身就是通过一段代码去验证另一段代码,所以不用单元测试框架也能编写单元测试。只是使用框架时会有更多的优点
1、提供用例组织与执行:
当测试用例达到成百上千条时,就产生了扩展性与维护性等问题,此时就需要考虑用例的规范与组织问题了。单元测试框架便能很好的解决这个问题
2、提供丰富的比较方法:
不论是功能测试还是单元测试,在用例完成之后都需要将实际结果与预期结果进行比较(断言),从而断定用例是否执行通过。单元测试框架一般会提供丰富的断言方法。例如:相等\不相等,包含\不包含,True\False的断言方法等
3、提供丰富的日志:
当测试用例执行失败时能抛出清晰的失败原因,当所有用例执行完成之后能提供丰富的执行结果。例如,总执行时间、失败用例数、成功用例数等
测试用例类,必须继承unittest.TestCase,测试用例函数必须以test开通
class Count:def __init__(self, x, y):self.x = int(x)self.y = int(y)def Plus(self, ):plus = self.x + self.yreturn plusdef Subtract(self):subtract = self.x - self.yreturn subtractdef Multiply(self):multiply = self.x * self.yreturn multiplydef Divide(self):divide = self.x / self.yreturn divideif __name__ == '__main__':count = Count(6, 3)print(count.Plus())print(count.Subtract())print(count.Multiply())print(count.Divide())
如果想测试上述代码:
import unittestfrom 单元测试 import Countclass TestAddCount(unittest.TestCase):def setUp(self) -> None:print("测试开始")def test_add_case(self):count = Count(5, 2)self.assertEqual(count.Plus(), 7)def test_subtract_case(self):count = Count(5, 2)self.assertEqual(count.Subtract(), 3)def test_multiply_case(self):count = Count(5, 2)self.assertEqual(count.Multiply(), 10)def test_divide_case(self):count = Count(5, 2)self.assertEqual(count.Divide(), 2.5)def tearDown(self) -> None:print("测试结束")if __name__ == '__main__':unittest.main()
单元测试重要的概念
使用unittest前需要了解该框架的五个概念,即test case,test suite,testLoader,test runner,test fixture
1、Test Case:
一个TestCase的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程。包括测试前准备环境的搭建(SetUP)、实现测试过程的代码(run),以及测试后环境的还原(tearDown)。单元测试(Unittest)的本质也就在这里,一个测试用例就是一个完整的测试单元,通过运行这个测试单元,可以对某一个功能进行验证
2、Test Suite:
一个功能的验证往往需要多个测试用例,可以把多个测试用例集合在一起执行,这就产生了测试套件TestSuite的概念。TestSuite用来组装单个测试用例。可以通过addTest加载TestCase到TestSuite中,从而返回一个TestSuite实例。而且TestSuite也可以嵌套TestSuite
3、Test Runner:
测试的执行也是单元测试中非常重要的一个概念,一般单元测试框架中都会提供丰富的执行策略和执行结果。在Unittest单元测试框架中,通过TextTestRunner类提供的run()方法来执行test suite/test case。test runner可以使用图形界面、文本界面,或返回一个特殊的值等方式来表示测试执行的结果
4、Test Fixture:
对一个测试用例环境的搭建和销毁,就是一个Fixture,通过覆盖TestCase的setUP()和tearDown()方法来实现。这有什么用呢?比如说在这个测试用例中需要访问数据库,那么可以在setUP()中建立数据库连接来进行初始化,在tearDown()中清除数据库产生的数据,然后关闭连接
import unittestfrom 单元测试 import Countclass TestAddCount(unittest.TestCase):def setUp(self) -> None:print("测试开始")def test_add_case(self):count = Count(5, 2)print("测试加法")self.assertEqual(count.Plus(), 7)def test_subtract_case(self):count = Count(5, 2)print("测试减法")self.assertEqual(count.Subtract(), 3)def test_multiply_case(self):count = Count(5, 2)print("测试乘法")self.assertEqual(count.Multiply(), 10)def test_divide_case(self):count = Count(5, 2)print("测试除法")self.assertEqual(count.Divide(), 2.5)def tearDown(self) -> None:print("测试结束")if __name__ == '__main__':#构造测试集suite = unittest.TestSuite()suite.addTest(TestAddCount("test_multiply_case"))# 执行测试runner = unittest.TextTestRunner()runner.run(suite)
1、从上面的例子中我们去掉了main()方法,采用构造测试集的方法来加载与运行测试用例,实现了有选择的执行测试用例
2、上面代码的执行过程:首先调用unittest框架的TestSuite()类来创建测试套件(实例化),通过它提供的addTest()方法来添加测试用例(test_division_case)。接着调用unittest框架的TextTestRunner()类(实例化),通过它下面的run()方法来运行suite所组装的测试用例
2、内置断言
断言就是用来校验预期结果和实际结果的,正常判断的话,我们需要写if条件,unittest内部封装了很多断言,可以使用它来校验,就不用写if了。 如果有多条断言,有一条执行失败,那它后面的断言都会不再执行了。
| 方法 | 作用 |
|---|---|
| self.assertEqual(a, b,msg=None) | a ==b |
| self.assertNotEqual(a, b,msg=None) | a !=b |
| self.assertTrue(x) | bool(x) is True |
| self.assertFalse(x) | Bool(x) is False |
| self.assertIs(a, b,msg=None) | a is b |
| self.assertIsNot(a, b,msg=None) | a is not b |
| self.assertIsNone(x) | x is None |
| self.assertIsNotNone(x) | x is not None |
| self.assertIn(a, b,msg=None) | a in b |
| self.assertNotIn(a, b,msg=None) | a not in b |
| self.assertIsInstance(a, b,msg=None) | isinstance(a,b,msg=None) |
| self.assertNotIsInstance(a, b,msg=None) | not isinstance(a,b,msg=None) |
| self.assertCountEqual(a,b,msg=None) | 判断传入的两个元素数量是否一样,比如两个list的长度是否一致 |
3、测试用例跳过
我们在执行case的时候,有一些case在某种条件下不需要被执行,那在unittest中也提供了这种功能,使用unittest.skip或者unittest.skipIf装饰测试用例即可。可以装饰函数,也可以装饰类 skip是无条件跳过,skipIf可以传一个条件,在某种条件下可以被执行。
import unittestimport sysclass TestCase(unittest.TestCase):@unittest.skip("无条件跳过")# 无条件跳过,就是不执行,就是玩,需要传一个字符串,说明跳过原因def test_print(self):print("test_print")@unittest.skipIf(sys.platform in ["win32", "darwin"], "不是在服务器上,不执行该条case")# if需要传2个参数,第一个参数是一个布尔值,true或者false,第二个参数是跳过的原因# 带条件的跳过,比如说当运行在本地的时候,不执行这条case,本地电脑一般是mac或者Windows,这里判断一下def test_hello(self):print("test_hello")def test_normal(self):print("我是一个不跳过的case")@unittest.skip("跳过整个类") #也可以把装饰器加在类上面,跳过整个测试类class TestCase2(unittest.TestCase):def test_mygod(self):print("我是一个不跳过的case")def test_my(self):print("我是一个不跳过的case")if __name__ == '__main__':unittest.main()
4、参数化
参数一样,值不一样的情况下,我们只写一个函数就可以了,可以使用参数化,传入多个参数,unittest可以自动根据参数数据调用测试用例,参数化需要安装parameterized模块,它使用的时候需要传入一个二维数组,二维数组里面的元素个数要和函数参数个数一致
[
](https://blog.csdn.net/qq_39314932/article/details/91612529)
安装parameterized模块
pip install parameterized
import unittestimport parameterizedclass TestCalc(unittest.TestCase):@parameterized.parameterized.expand(([1, 1, 2, ],[2, 3, 5, ],[3, 3, 6, ],))def test_param_not_msg(self, a, b,c): # 传入预期结果和入参self.assertEqual(a+b, c)if __name__ == '__main__':unittest.main() # 运行case
import unittestimport parameterizeddef calc(seq):try:ret = eval(seq)except:return Falsereturn retclass TestCalc(unittest.TestCase):@parameterized.parameterized.expand((["1+1", 1, "加法", "case失败提示"],["1*2", 2, "乘法", "case失败提示"],["1-1", 0, "减法", "case失败提示"],["1/1", 1, "除法", "case失败提示"],))def test_param(self, expression, except_result, desc, msg):#有用例描述和需要定义case失败原因的self._testMethodDoc = desc # 用例描述,如果需要指定的话,加上它ret = calc(expression)self.assertEqual(except_result, ret, msg)
5、用例前置、后置操作
实际在用例执行的时候,可能有一些前置条件,执行case之前要打开一下文件,连接一下数据库,初始化数据,或者case执行完之后需要执行数据清理的操作,就需要用到前置操作和后置操作。
| 函数名 | 作用 | 备注 |
|---|---|---|
| setUpClass | 所有case运行前执行 | 是一个类方法,需要使用@classmethod装饰 |
| tearDownClass | 所有case运行后执行 | 是一个类方法,需要使用@classmethod装饰 |
| setUp | 每条case运行前执行 | |
| tearDown | 每条case运行后执行 |
import unittestclass TestCase(unittest.TestCase):@classmethoddef setUpClass(cls):print("所有case运行之前会执行这里的代码")@classmethoddef tearDownClass(cls):print("所有case运行之后会执行这里的代码")def setUp(self):print("每个case执行之前会执行这里的代码")def tearDown(self):print("每个case执行之后会执行这里的代码")def test_print(self):print("我是一个没有感情的测试函数,只是用我print,测其他的")def test_output(self):print("我也是一个没有感情的测试函数,只是用我print,测其他的东西")if __name__ == '__main__':unittest.main()
6、用例查找
前面运行用例的时候都是在一个py文件里面的,实际我们写自动化case的时候不会把所有的case都写到一个py文件里,都是在多个py文件里面,所以需要使用测试用例查找功能
目录结构如下,cases目录下有3个py文件,里面各写几条case,run.py查找test_cases目录下的case,运行

# run.pyimport unittesttest_suite = unittest.defaultTestLoader.discover('test_cases','test*.py') #指定目录,查找case,查找以test开头的py文件,返回用例集合test_runner = unittest.TextTestRunner()#实例化test_runnertest_runner.run(test_suite)#使用test_runner运行case
7、产生测试报告
测试报告需要使用第三方模块,HTMLTestRunner.py,原版的HTMLTestRunner产生的报告不好看,没这个好看,所以这里用的是别人修改过好看的。
使用时下载下来即可。
HTMLTestRunnerNew.py
把HTMLTestRunnerNew.py放在跟cases同层目录中
import unittestimport HTMLTestRunnerNewtest_suite = unittest.defaultTestLoader.discover('test_cases','test*.py') #指定目录,查找case,查找以test开头的py文件,返回用例集合f = open("report.html",'wb') #打开存放测试报告的html文件test_runner = HTMLTestRunnerNew.HTMLTestRunner(f,title="测试报告",description="测试报告描述")case_result = test_runner.run(test_suite) #执行caseprint("用例总数",test_suite.countTestCases())print("成功个数",case_result.success_count)print("错误个数",case_result.error_count) #指的是case运行报错了,不是断言不通过的print("失败个数",case_result.failure_count)#指的是case失败的个数,断言不通过的print("跳过的个数",len(case_result.skipped)) #跳过多少个print("开始运行时间",test_runner.startTime)print("结束运行时间",test_runner.stopTime)f.close()

pytest单元测试框架
原文链接:https://blog.csdn.net/qq_39314932/article/details/91612529
原文链接:https://www.yuque.com/mrwei-vhjbd/lmzics/gthqac
