1.1 Unittest详解
1.1.1快速入门
相信接触过Java语言的朋友一定对Junit单元测试框架不陌生,对于Python语言,同样有类似的单元测试框架Unittest。
Unittest是Python内部自带的一个单元测试的模块,它设计的灵感来源于Junit,具有和Junit类似的结构,有过Junit经验的朋友可以很快上手。Unittest具备完整的测试结构,支持自动化测试的执行,对测试用例集进行组织,并且提供了丰富的断言方法,最后生成测试报告。Unittest框架的初衷是用于单元测试,但也不限于此,在实际工作中,由于它强大的功能,提供的完整的测试流程,我们往往将其用于自动化测试的各个方面,例如在本书中大量的接口测试实例都会用到Unittest。
所谓知己知彼百战不殆,首先我们来一起看下Unittest大家庭里的成员。首先导入unittest模块,使用dir()函数获取Unittest的所有成员,并输出到界面上。
import unittest
print(dir(unittest))
执行结果如下。
['BaseTestSuite', 'FunctionTestCase', 'SkipTest', 'TestCase', 'TestLoader', 'TestProgram', 'TestResult', 'TestSuite', 'TextTestResult', 'TextTestRunner', '_TextTestResult', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__unittest', 'case', 'defaultTestLoader', 'expectedFailure', 'findTestCases', 'getTestCaseNames', 'installHandler', 'load_tests', 'loader', 'main', 'makeSuite', 'registerResult', 'removeHandler', 'removeResult', 'result', 'runner', 'signals', 'skip', 'skipIf', 'skipUnless', 'suite', 'util']
结果里显示了unittest模块的各个成员,看起来非常多,不知道如何下手。其实一个模块里往往包含了大量的成员,很大一部分我们使用的频率并不高,这时需要有重点的去攻克核心的部分,其他的稍作了解即可。下面先简单介绍最常用的一些成员,后续的章节中我们会详细剖析。
(1)TestCase:可以说是unittest中最重要的一个类,也是测试用例类的父类,通过对其继承,使子类具备了执行测试的能力。下例中MainTest是需要执行的测试类。
Class MainTest(unittest.TestCase):
(2)TestSuite: TestSuite类用于创建测试套件。最常见的用法是,使用该类将多个测试用例添加到用例集,通过运行用例集,实现多个测试用例的执行。
(3)main:调用unittest.main()方法可以方便的将测试类里的以“test”命名开头的测试方法以脚本的形式自动执行。
(4)TextTestRunner:主要使用该类的run()方法来运行TestSuite添加好的测试用例。
(5)skipXX:装饰器。有时我们的测试只想运行其中的一部分用例,那么我们可以使用skip装饰器来跳过执行。最常见的场景是:在不同的系统环境上运行时,某些用例是不能通过的,但这并不是我们的产品或用例导致,而是环境的不兼容等问题,此时我们可以使用skip装饰器来处理。
2. 重要概念
在继续学习之前,我们需要掌握四个Unittest的重要概念。以下是官方网站上通过面向对象大的方式进行的解释。
To achieve this, unittest supports some important concepts in an object-oriented way:
test fixture
A test fixture represents the preparation needed to perform one or more tests, and any associate cleanup actions. This may involve, for example, creating temporary or proxy databases, directories, or starting a server process.
test case
A test case is the individual unit of testing. It checks for a specific response to a particular set of inputs. unittest provides a base class, TestCase, which may be used to create new test cases.
test suite
A test suite is a collection of test cases, test suites, or both. It is used to aggregate tests that should be executed together.
test runner
A test runner is a component which orchestrates the execution of tests and provides the outcome to the user. The runner may use a graphical interface, a textual interface, or return a special value to indicate the results of executing the tests.
(1)test fixture:翻译过来是测试固定装置的意思。形象的说,把整个测试过程看作大的装置,这个装置里不仅具有测试执行部件,还有测试之前环境准备和测试之后环境清理的部件,有机的结合起来就是一个更大的测试装置,即test fixture。
(2)test case:测试用例,注意与前面的TestCase类不是同一个概念。一个完整的测试流程就是一个测试用例,通过一些特定的输入得到响应,并对响应进行校验的过程。我们通过去继承TestCase这个父类,可以创建新的测试用例。
(3)test suite:测试套件,也称为测试集合。多个测试用例组合在一起就形成了测试集,当然测试集里不仅能包含测试用例,也可以再次嵌套测试集,测试集可以用于代码的组织和运行。
(4)test runner:是Unittest中的重要组成部分,主要职责为执行测试,通过图形、文本或者返回一些特殊值的方式来呈现最终的运行结果。例如执行的用例数、成功和失败的用例数。
图3-1展示了他们之间的关系,test fixture是包含了以test case为核心的整个组件,多个test case可以集合到一个test suite中,最后调用test runner执行并生成结果。
Unittest组成部分
3. 实例
下面我们通过一个简单的实例让大家对Unittest的基本使用有一个直观的认识。首先创建一个项目,项目里有两个文件,Calculator.py是被测试的代码,Demo.py是执行测试的代码。项目结构如下:
(1)被测代码准备。在Calculator类中定义了一个方法为divide,该方法接收x和y两个参数分别作为分子和分母进行除法运算,并返回运算结果。
class Calculator:
def divide(self,x,y):
return x / y
(2)测试代码。测试代码先通过from和import两种方式分别导入了Calculator和unittest模块,然后定义了一个测试类TestCalculator,并继承于unittest.TestCase模块。测试类中有一个测试方法test_divide,调用了被测试类Calculator中的divide方法,并将结果进行断言。最后调用unittest.main方法来执行当前类中所有以test开头的方法来直接运行,而不必再专门对TestCalculator进行实例化。
import unittest
from my_unittest.unittest_demo.Calculator import Calculator
class TestCalculator(unittest.TestCase):
def testDivide01(self):
cal = Calculator()
result = cal.divide(10,2)
self.assertEqual(result,5)
def testDivide02(self):
cal = Calculator()
result = cal.divide(20,0.5)
self.assertEqual(result,40)
if __name__ == '__main__':
unittest.main()
(3)运行结果如下。两个测试总共运行了0.001秒,测试结果为OK。
..
---------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
(4)接下来我们再看一个测试不通过的例子,依然使用Calculator作为待测试的类,测试类中包含3个测试方法,即3条测试用例:
from C03_Unittest.Ex01_SimpleExample.Calculator import Calculator
class TestCalculator(unittest.TestCase):
def testDivide01(self):
cal = Calculator()
result = cal.divide(10,2)
self.assertEqual(result,5)
def testDivide02(self):
cal = Calculator()
result = cal.divide(10,0.5)
self.assertEqual(result,10)
def testDivide03(self):
cal = Calculator()
result = cal.divide(10,0)
self.assertEqual(result,0)
if __name__ == '__main__':
unittest.main()
(5)第一个方法(即第一条测试用例)用10除以2,预期结果为5,测试结果通过;第二条测试用例用10除以0.5,为了观察运行结果,刻意设置错误的预期结果为10,但实际结果为20,出现“AssertionError: 0.01 != 0.001”,断言错误,测试失败;第三条用属于异常测试,使用10除以0,出现运行错误“ZeroDivisionError: division by zero”,提示不能使用0作为分母。结果中的第一行“.FE”分别代表运行中的三条用例,“.”为通过,“F”为不通过,“E”为错误。
.FE
=====================================================================
ERROR: testDivide03 (__main__.TestCalculator)
---------------------------------------------------------------------
Traceback (most recent call last):
File "C:/Users/Administrator/PycharmProjects/python364/C03_Unittest/Ex01_SimpleExample/Test02.py", line 17, in testDivide03
result = cal.divide(10,0)
File "C:UsersAdministratorPycharmProjectspython364C03_UnittestEx01_SimpleExampleCalculator.py", line 3, in divide
return x / y
ZeroDivisionError: division by zero
=====================================================================
FAIL: testDivide02 (__main__.TestCalculator)
---------------------------------------------------------------------
Traceback (most recent call last):
File "C:/Users/Administrator/PycharmProjects/python364/C03_Unittest/Ex01_SimpleExample/Test02.py", line 13, in testDivide02
self.assertEqual(result,10)
AssertionError: 20.0 != 10
---------------------------------------------------------------------
Ran 3 tests in 0.003s
FAILED (failures=1, errors=1)
(6)通过上面的测试,可以得出一个结论:Calculator类的divide方法具有明显的Bug,该方法中没有对输入参数进行校验,导致在y为0时程序运行错误。当测试人员发现此Bug后应及时通知开发人员进行修复,避免造成更严重的损失。这个实例不仅展现了Unittest框架的实际价值,也进一步体现了测试工作在研发过程中的重要意义。
注:本文为蜗牛学院原创,未经允许请勿转载。不定期分享各类学习资料,只为成就更好的你!