Python之pytest单元测试方法
参考:https://zhuanlan.zhihu.com/p/29968920
1 单元测试
单元测试(又称为模块测试, Unit Testing)是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
为什么要进行单元测试? 测试除了保证程序的健壮性外,是可以让你重新思考代码的设计的。引用Axb博客的话:
编写单元测试的难易程度能够直接反应出代码的设计水平,能写出单元测试和写不出单元测试之间体现了编程能力上的巨大的鸿沟。无论是什么样的程序员,坚持编写一段时间的单元测试之后,都会明显感受到代码设计能力的巨大提升。
如果发现代码难以构造测试,很有可能就是接口设计不够优雅,或者耦合严重,尝试从测试的角度思考能够让我们更好地设计。单元测试同时也为重构提供了保证,比如我们想优化一个函数内部实现,更换更优的数据结构和算法,只需要重新跑一下测试就可以验证新的实现是否引入了错误或bug。
总的来说,单元测试有以下好处:
- 确保代码质量
- 改善代码设计,难以测试的代码一般是设计不够简洁的代码。
- 保证重构不会引入新问题,以函数为单位进行重构的时候,只需要重新跑测试就基本可以保证重构没引入新问题。
2 框架选择
关于单元测试,有很多现成的框架,其特点如下
- unittest,内置库,模仿PyUnit写的,简洁易用,缺点是比较繁琐。
- nose,测试发现,发现并运行测试。
- pytest,写起来很方便,并且很多知名开源项目在用,推荐。
- mock,替换掉网络调用或者 rpc 请求等
根据pytest官方网站介绍,它具有如下特点
- 非常容易上手,入门简单,文档丰富,文档中有很多实例可以参考
- 能够支持简单的单元测试和复杂的功能测试
- 支持参数化
- 执行测试过程中可以将某些测试跳过,或者对某些预期失败的case标记成失败
- 支持重复执行失败的case
- 支持运行由nose, unittest编写的测试case
- 具有很多第三方插件,并且可以自定义扩展
- 方便的和持续集成工具集成
由于pytest使用起来比较方便,因此选择pytest作为python代码单元测试的工具,关于pytest的具体介绍和使用方法,可参考官网文档
3 安装
直接用pip安装即可
sudo pip install -U pytest
安装完之后可用以下方法验证
py.test --version
4 单个测试
此处参考官网示例,新建一个测试文件
# test_sample.py的内容
def func(x):
return x + 1
def test_answer():
assert func(3) == 5
直接在脚本目录中运行pytest,输出如下
nvidia@tegra-ubuntu:~/test$ pytest
============================= test session starts ==============================
platform linux -- Python 3.5.2, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /home/nvidia/test, inifile:
collected 1 item
test_sample.py F [100%]
=================================== FAILURES ===================================
_________________________________ test_answer __________________________________
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
test_sample.py:6: AssertionError
=========================== 1 failed in 0.11 seconds ===========================
执行测试的时候,我们只需要在测试文件test_sample所在的目录下,运行pytest即可。pytest会在当前的目录下,寻找以test开头的文件(即测试文件),找到测试文件之后,进入到测试文件中寻找test_开头的测试函数并执行。通过上面的测试输出,我们可以看到该测试过程中,一个收集到了一个测试函数,测试结果是失败的(标记为F),并且在FAILURES部分输出了详细的错误信息,帮助我们分析测试原因,我们可以看到"assert func(3) == 5"这条语句出错了,错误的原因是func(3)=4,然后我们断言func(3) 等于 5。
5 多个测试
我们在实际应用时,往往不只包含一个测试,需要测试多个部分,这个时候可以写个类来实现
# content of test_class.py
class TestClass(object):
def test_one(self):
x = "this"
assert 'h' in x
def test_two(self):
x = "hello"
assert hasattr(x, 'check')
运行测试后输出如下
nvidia@tegra-ubuntu:~/test$ pytest test_class.py
============================= test session starts ==============================
platform linux -- Python 3.5.2, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /home/nvidia/test, inifile:
collected 2 items
test_class.py .F [100%]
=================================== FAILURES ===================================
______________________________ TestClass.test_two ______________________________
self = <test_class.TestClass object at 0x7f76fe74a8>
def test_two(self):
x = "hello"
> assert hasattr(x, 'check')
E AssertionError: assert False
E + where False = hasattr('hello', 'check')
test_class.py:9: AssertionError
====================== 1 failed, 1 passed in 0.08 seconds ======================
可以看到第二个测试报错了,并列出了错误位置和错误原因
6 总结
通过上面2个实例,我们发现编写pytest测试样例非常简单,只需要按照下面的规则:
- 测试文件以test_开头(以_test结尾也可以)
- 测试类以Test开头,并且不能带有 __init__ 方法
- 测试函数以test_开头
- 断言使用基本的assert即可
执行测试样例的方法很多种,上面第一个实例是直接执行pytest,第二个实例是传递了测试文件给pytest。其实pytest有好多种方法执行测试:
pytest # run all tests below current dir
pytest test_mod.py # run tests in module
pytest somepath # run all tests below somepath
pytest -k stringexpr # only run tests with names that match the
# the "string expression", e.g. "MyClass and not method"
# will select TestMyClass.test_something
# but not TestMyClass.test_method_simple
pytest test_mod.py::test_func # only run tests that match the "node ID",
# e.g "test_mod.py::test_func" will select
# only test_func in test_mod.py
pytest可以方便的生成测试报告,即可以生成HTML的测试报告,也可以生成XML格式的测试报告用来与持续集成工具集成。
生成HTML格式报告:
pytest test_class.py --resultlog=/home/nvidia/test/test.html
生成XML格式的报告:
pytest test_class.py --junitxml=/home/nvidia/test/test.xml
更多推荐
所有评论(0)