单元测试框架并非只能用于代码级别的测试,对于单元测试框架来讲,主要完成三件事:
提供用例组织与执行:当你的测试用例只有几条时,可以不必考虑用例的组织,但是,当测试用例达到成百上千条时,大量的测试用例堆砌在一起,就产生了扩展性与维护性等问题,此时需要考虑用例的规范与组织问题了。单元测试框架就是用例解决这个问题的。
提供丰富的比较方法:不论是功能测试,还是单元测试,在用例执行完成之后都需要将实际结果与预期结果进行比较(断言),从而断定用例是否执行通过。单元测试框架一般会提供丰富的断言方法。例如,判断相等/不等,包含/不包含,True/False 的断言方法等。
提供丰富的日志:当测试用例执行失效时能抛出清晰的失败原因,当所有用例执行完成后能提供丰富的执行结果。例如,总执行时间、失败用例数、成功用例数等。
一般的单元测试框架都会提供这些功能,从单元测试框架的这些特征来看,它同样适用于web自动化用例的开发与执行。
一、认识 unittest
什么是单元测试?单元测试负责对最小的软件设计单元(模块)进行验证,它使用软件设计文档中对模块的描述作为指南,对重要的程序分支进行测试以发现模块中的错误。在Python语言下有诸多单元测试框架,如doctest、unittest、pytest、nose等,unittest框架(原名PyUnit框架)为Python语言自带的单元测试框架。
1.1 认识单元测试
可能读者会问不用单元测试框架能写单元测试么?答案是肯定得,单元测试本身就是通过一段代码去验证另一段代码,所以不用单元测试框架也可以写单元测试,下面就通过例子演示不同测试框架的单元测试。
# calculator.py
class Count(object): """docstring for Count""" def __init__(self, a, b): self.a = int(a) self.b = int(b) # 计算加法 def add(self): return self.a + self.b
根据上面所实现的功能,不用测试框架所编写的单元测试如 test.py
from calculator import Count # 测试两个整数相加 class TestCount: def test_add(self): try: j = Count(2,3) add = j.add() assert(add == 5), "Integer adtion result error!" except AssertionError as e: print(e) else: print("Test pass!") # 执行测试类的测试方法 mytest = TestCount() mytest.test_add()
首先,引入 calculator 文件中的 Count 类:然后再 test_add() 方法中调用 Count 类并传入两个参数 2 和 3;最后调用Count 类中的 add() 方法对两个参数做加法运算,并通过 assert() 方法判断 add() 的返回值是否等于5。如果不相等则抛出自定义的 "Integer adtion result error!" 异常信息,如果相等则打印 “Test pass!”。
----------正确时---------- C:\Users\Administrator\AppData\Local\Programs\Python\Python37\python.exe C:/Users/Administrator/Desktop/py/test.py Test pass! Process finished with exit code 0 ----------错误时---------- C:\Users\Administrator\AppData\Local\Programs\Python\Python37\python.exe C:/Users/Administrator/Desktop/py/test.py Integer adtion result error! Process finished with exit code 0
不难发现这种测试方法存在许多问题。首先,测试程序的写法没有一定的规范可以遵循,十个程序员完全可能写出十种不同的测试程序来,不统一的代码维护起来会十分麻烦。其次,需要编写大量的辅助代码才能进行单元测试,在 test.py 中用于测试的代码甚至比被测试的代码还要多,而且这仅仅是一个测试用例,对一个单元模块来说,只编写一条测试用例显然是不够的。
为了让单元测试代码更容易维护和编写,最好的方式是遵循一定的规范来编写测试用例,这也是单元测试框架诞生的初衷。接下来讲如何通过 unittest 单元测试框架编写单元测试用例。
from calculator import Count import unittest class TestCount(unittest.TestCase): """docstring for TestCount""" def setUp(self): print("test start") def test_add(self): j = Count(2, 3) self.assertEqual(j.add(), 5) def tearDown(self): print("test end") if __name__ == '__main__': unittest.main()
分析上面的代码,首先引入 unittest 模块,创建 TestCount 类继承 unittest 的TestCase 类,我们可以将 TestCase 类看成是对特定类进行测试的集合。
setUp() 方法用于测试用例执行前的初始化工作,这里只简单打印 “test start”信息。tearDown() 方法与 setUp() 方法相呼应,用于测试用例执行之后的善后工作,这里打印 “test end”信息。
在test_add() 中首先调用 Count类并传入要计算的数,通过调用 add() 方法得到两数相加的返回值。这里不再使用繁琐的异常处理,而是调用unittest 框架所提供的 assertEqual() 方法对 add() 的返回值进行断言,判断两者是否相等,assertEqual() 方法由 TestCase 类继承而来。
unittest 提供了全局的main() 方法,使用它可以方便地将一个单元测试模块变成可以直接运行的测试脚本,main() 方法使用 TestLoader 类来搜索所有包含在该模块中以 “test” 命名开头的测试方法,并自动执行它们。
C:\Users\Administrator\AppData\Local\Programs\Python\Python37\python.exe C:/Users/Administrator/Desktop/py/test.py . test start test end ---------------------------------------------------------------------- Ran 1 test in 0.000s OK Process finished with exit code 0
1.2 重要的概念
在 unittest 的文档中开篇就介绍了 4 个重要的概念:test fixture、test case、test suite 和 test runner,只有理解了这几个概念才能理解单元测试的基本特征。
1.2.1 Test Case
一个 TestCase 的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp)、实现测试过程的代码(run),以及测试后环境的还原(tearDown)。单元测试(unit test)的本质也就在这里,一个测试用例就是一个完整的测试单元,通过运行这个测试单元,可以对某一个功能进行验证。
1.2.2 Test Suite
一个功能的验证往往需要多个测试用例,可以把多个测试用例集合在一起执行,这就产生了测试套件 TestSuite 的概念。Test Suite 用来组装单个测试用例。可以通过 addTest 加载 TestCase 到 TestSuite 中,从而返回一个 TestSuite 实例。
1.2.3 Test Runner
测试的执行也是单元测试中非常重要的一个概念,一般单元测试框架中都会提供丰富的执行策略和执行结果。在 unittest 单元测试框架中,通过 TextRunner 类提供的 run() 方法来执行 test suite/test case。test runner 可以使用图形界面、文本界面,或返回一个特殊的值等方式来表示测试执行的结果。
1.2.4 Test Fixture
对一个测试用例环境的搭建和销毁,就是一个 fixture,通过覆盖 TestCase 的 setUp() 和 tearDown() 方法来实现。有什么用呢?比如说在这个测试用例中需要访问数据库,那么可以在setUp()