Are you ready?
↓↓↓
今天的课程为《 Python单元测试》,内容共分为三个部分:单元测试的概念、工具与方法、Coverage 统计单元测试覆盖率的工具和Mock 简化单元测试的工具。
单元测试的概念、工具与方法
单元测试的概念
测试具有许多种不同的类型,比如说单元测试、模块测试、联调测试、系统测试、交付测试等。在这些测试之中,单元测试是最先要完成的。单元测试通常是由开发者去完成,用来验证代码中的函数是否符合预期。因此,它聚焦于函数的逻辑以及核心的算法是否正确。通常而言,一个单元测试用例是用于判断在某个特定条件或场景下,某个特定函数的行为。
单元测试的意义
单元测试的意义包括两个方面。
(1)质量
①单元测试主要针对函数,颗粒度小、测试针对性强,bug更容易暴露;
②由于单元测试覆盖面较窄,无需考虑其它函数或者所依赖的模块,所以它的场景易构造,核心功能验证更充分;
③进行单元测试保证整体代码结构良好,使代码就具有较高的可测性和可维护性。
(2)效率
单元测试能够提高开发效率,主要表现在:
①单元测试进行的时间较早,测试场景构建快,可有效减少调试时间。
②由于单元测试只针对修改的代码展开测试,无需考虑额外内容,所以在较短时间内即可把预期的逻辑测试充分。
③单元测试能够在项目开发初期发现的bug,bug发现的时间越早,所带来的收益越大。由于尽早发现bug能够节省整个项目开发的时间,所以单元测试可加快开发效率,缩短开发周期。
单元测试框架
(1)测试框架选择
在进行单元测试的时候,我们需要测试框架作为工具。一个良好的测试框架能够让测试工作事半功倍,此处我们介绍三个框架。
①Unittest是Python标准库中原生自带的,它的好处在于无兼容性问题,是其他框架的基础;不足之处在于编码略显繁琐,入门门槛稍高。
②Pytest的好处在于编码简洁、生态好、插件丰富、使用人数多;不足之处在于他属于第三方库,使用前另需提前安装。
③Nose2能够兼容Unittest,也属于第三方库,需要安装,但是与Pytest相比,它迭代缓慢,使用人数少。
在这里我们选择Unittest作为单元测试的框架,原因有二:首先,作为Python标准库中原生自带的框架,Unittest无兼容性问题;其次,第三方库难以保证长期快速迭代,易过时。
(2)Unitest的基础概念
在做单元测试之前,需要先了解一下Unittest的几个基础概念。
①Test(测试用例),针对一个特定场景,特定目的具体测试过程。
比如说一个函数通过一组输入测试它,就是一个测试用例;如果一个函数通过三组输入来测试,即为三个测试用例。
②TestCase(测试类),可以包含同一个测试对象的多个测试用例。
如果一个函数通过三组输入来测试,也就是三个测试用例,这三个测试用例可以合成为一个测试类。
③TestSuite(测试集),可以包含多个测试类的多个测试用例。
④Assertion(断言),必须使用断言判断测试结果。
⑤TestFixture,为测试做统一的准备和清除工作,通常是初始化,连接数据库,准备数据,断开数据库,清除现场等。
扩展来说,TestFixture有四种最常使用的作用范围,分别为:
→ setUp:在测试类的每个测试用例执行前执行。
→ teardown:在测试类的每个测试用例执行后执行。
→ setUpClass:在测试类的第一个测试用例执行前执行。
→ tearDownClass:在测试类的最后一个测试用例执行后执行。
TestFixture可以让单元测试代码更简单,但并非必须使用,也不要求配对出现。
单元测试的规范
想要做好单元测试,必须遵循一定的规范。下面介绍一下单元测试涉及的规范。
(1)所有的单元测试必须使用断言(assert)判断结果,禁止出现无断言的测试用例;
使用断言,不但有利于他人理解,而且一旦出现不符合预期的情况,可以立即找出问题。
可以使用assertEqual, assertNotEqual 来判断相等或不相等,assertTrue,assertFalse 来判断Boolean, assertRaises 判断抛出的异常是否符合预期。
(2)测试用例需要具有自表述能力,达到见名知意。
比如命名test_login_with_invalid_password(),通过它的名字便可知它是用一个非法的密码去测试登录功能,具有自表述能力;但是如果命名为 test_login_case_(),名字减少了很多信息,难以得知它具体在做什么,不具有自表述能力。
(3)测试用例之间相互独立,不应相互依赖、相互调用。
(4)一个测试用例只测一个函数。一个测试用例里面可以包含这一个函数的多个场景,但不能包含有多个参数的函数。原因在于,复杂测试用例出现错误时,无法定位问题的出处。
单元测试对编码的要求
单元测试中代码需保持一致性,尽量不要出现结果不一致的情况。假设有的代码会带来不一致性,导致单元测试无法稳定运行。针对这种情况,有两种解决方案:第一,将带来不一致性的代码抽取出来,把它作为一种变量传入我们需要调用或使用一致性变量的时候;第二,借助第三部分即将讲到的一个工具——mock——来解决这种问题。
Coverage 统计 单元测试覆盖率的工具
单元测试做完之后如何评价我们单元测试的效果。此时需要用到覆盖率工具,即Coverage。Coverage是一个第三方的工具,需要提前下载安装。
(1)统计覆盖率方法
把python替换为coverage run-branch,然后会生成coverage文件,文件里会记录所有我们需要的覆盖率信息。
(2)打印覆盖率信息
执行coverage report-m 命令,读取当前目录下.coverage文件,打印覆盖率信息。输出Stmts(总行数), Miss(未覆盖行数), Branch(总分支数), BrPart (未覆盖分支数), Cover(覆盖率) , Missing(未覆盖具体信息)等信息。
(3)覆盖率中排除某些文件
执行coverage report-m—omit=file 1[,file 2,……] 命令, 在统计并打印覆盖率时,排除某些文件。若有多个文件用逗号分隔。
(4)生成HTML格式的覆盖率信息
针对代码量较大,查找覆盖率信息难度较大、耗时较长的情况,执行coverage html [–omit=file1[,file2,……]]命令,将覆盖率信息以html格式显示。
Mock 简化单元测试的工具
使用mock工具的原因与其功能
Mock基于实际进行单元测试的场景而产生,以下三类场景非常具有代表性:
①构造模块。需要测试模块A,但它要调用的模块B还未开发,可是测试却不容推迟、需按时进行,面对这种情况,我们可以使用Mock生成一个还未写完的代码,即可进行相应的测试。
②改变函数逻辑。代码中含有结果不可预知的代码,例如time.time()(时间), random.random()(随机数)。Mock可以改变含有结果不可预知代码的函数的逻辑,强行让其返回我们想要的返回值,使其结果可预知。
③减少依赖。在所有模块代码都已完成,但无法保证代码稳定性的情况下。针对其他模块的质量不可靠的情况,可通过Mock工具构造一个相对稳定的模块,从而规避其他模块的问题。
Mock使用场景
通过以下10个场景来讲述Mock的常见用法。
场景01:通过 return_value,Mock可以强行修改,永远返回我们想要的返回值,支持的类型包括string,number,Boolean,list,dict等。
场景02:将前一个例子的实例名改为类名,可实现替换类方法的返回值。
场景03:通过 side_effect,根据调用次数返回想要的结果,当超出调用次数时抛StopIteration 异常。
场景04:通过 side_effect可以完全修改函数的逻辑,使用另一个函数来替换它,根据参数返回想要的结果。
场景05:通过 side_effect抛出想要的异常或错误。
场景06:针对需要mock在特定要求下生效的情况,通过with.patch.object设定一个作用域以达到限制mock作用域的目的。
场景07:获取调用信息,如函数是否被调用、函数被调用的次数、函数被调用的形式、函数调用的参数等。
场景08:通过create_autospec在返回值改变的同时,确保api不会因mock而改变。
场景09:针对需要调用的函数、调用的接口完全没有开发的情况,可以通过Mock从零构造依赖模块从而完成测试。
场景10 :替换函数调用链。比如说用popen去执行一个命令,然后用read函数把它读取出来,再用split去做切分,这就是一个函数调用链(os.popen(cmd).read().split())。
Mock 对编码的要求
在模块引入方式上,推荐以import XXX的形式引入,以XXX.func()形式调用,不要from.xxx import *,因为需要一个链条指向它,否则无法达到我们的预期。
关于Python单元测试
我们今天就讲解到这啦
点击进入获得更多技术信息~~
|