技术杂谈 | 优雅的 Python 代码自动化测试方案
背景
🤙🏻 项目代码测试是必要滴!目前主流的 python 自动化测试框架包括 unittest、pytest 和 nose/nose2,以下对这三种框架进行简介并进行对比:
- unittest是 python 内嵌的测试库,如果对 python 仅了解基础知识那么 unittest 框架相对而言易于理解,unittest 框架可以实现大部分的业务测试,但是测试代码编写格式固定而且较为复杂。
- pytest 是基于 unittest 扩展的测试框架,相对于 unittest 而言更简单高效而且功能齐全,插件丰富,pytest 兼容 unittest 测试用例但是 unittest 不兼容 pytest,是目前主流的 python 测试框架 🔥。
- nose/nose2 是一个第三方测试框架,它也完全兼容 unittest 测试并且简单易用,但目前已进入维护模式更新活跃度低。
附上 🧑🏻💻 体验感表格来比较功能体验:
功能 | unittest | nose/nose2 | pytest |
---|---|---|---|
自动发现用例 | ☆☆☆ | ☆☆☆ | ☆☆☆ |
指定用例执行 | ☆☆☆ | ☆☆☆ | ☆☆☆ |
assert 断言 | ☆ | ☆ | ☆☆☆ |
测试夹具生效级别 | ☆ | ☆ | ☆☆☆ |
支持跳过测试和预计失败 | ☆☆☆ | ☆☆☆ | ☆☆☆ |
参数化测试 | ☆☆ | ☆☆ | ☆☆☆ |
数据结构可视化 | ☆ | ☆☆ | ☆☆☆ |
插件 | - | ☆ | ☆☆☆ |
钩子 | - | ☆☆ | ☆☆☆ |
社区生态 | ☆☆☆ | ☆ | ☆☆☆ |
综上优劣,本文主要采用和介绍 pytest 测试框架,然后结合 coverage 统计单元测试覆盖率,以此实现更优雅的 python 单元测试方案。🤞
pytest 模块
官网地址 slogan:更好地搬砖 🧱
pytest: helps you write better programs
The
pytest
framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries.
首先需要安装第三方包:
1 | pip install -U pytest |
pytest 主要特性如下:
- assert 断言失败时输出详细内容;
- 自动发现测试模块和函数;
- 模块化夹具用以管理各类测试资源;
- 对 unittest 完全兼容,对 nose 基本兼容;
- 非常丰富的插件体系,有 315+ 款第三方插件,社区强大;
- 高亮输出,通过或不通过会用不同的颜色进行区分;
- 更丰富的上下文信息,自动输出代码上下文和变量信息;
- 测试进度展示,测试结果输出布局更加友好易读;
常用参数
执行 pytest --help
即可查看参数列表,列出常用参数:
-s
:显示测试单元中的输入日志。-m
:指定已标注某些标签的测试方法。
用例发现和执行
pytest 支持用例自动(递归)发现:
- 默认发现当前目录下所有符合
test_*.py
或*_test.py
的测试用例文件,以test
开头的测试函数或以Test
开头的测试类中的以test
开头的测试方法; - 可以通过在配置文件中指定特定参数,可配置用例文件、类和函数的名称模式(模糊匹配);
或者指定用例执行:
指定测试文件路径:
pytest /path/to/test/file.py
指定测试类:
pytest /path/to/test/file.py:TestCase
指定测试方法:
pytest another.test::TestClass::test_method
指定测试函数:
pytest /path/to/test/file.py:test_function
编写几个简单的测试用例 example.py
如下
1 | # content of test_sample1.py |
在终端执行 pytest
命令结果如下所示:
可以看出终端输出的可视化结果比较好,共四个测试用例通过 2 失败 2 👍
还可以安装pytest-html
库,然后通过 pytest --html report.html
命令生成 html 文件可视化。
标记函数
如果代码存在 bug 或者还在 fix,环境不满足等等,可以通过@pytest.mark.skip
注解跳过指定的测试。针对以上案例增加 mark 如下所示:
1 | # content of test_sample.py |
那么对应的标记函数测试将不会被执行。
对于上面的 skip,还可以根据指定条件进行忽略测试单元,非常强大
1 |
|
还可使用其他的标签或者自定义标签,使用 @pytest.mark
在函数上进行各种标记,比如fixed,finished/unfinished。
然后在运行的时候可以使用 -m
指定已标注某些标签的测试方法。
1 | pytest -m finished test_with_mark.py |
📢 markers 需要首先被定义,可以根据 pytest --markers
查看默认定义的 mark,参考 working with custom markers
参数化测试
参数化测试使用频率非常高,可以向断言中添加多个值模拟各种正常或非法的参数。
在 pytest 中可以利用 @pytest.mark.parametrize(argnames, argvalues)
使每组参数都独立执行一次测试。
示例 test_params.py
如下
1 |
|
还有个非常重要的概念是 fixture
,作用是为测试用例定义一些可复用的、一致的功能支持,其中最常见的可能就是数据库的初始连接和最后关闭操作,测试数据集的统一提供接口。目前暂时用不到,如需自取 传送门~
coverage 模块
覆盖率是用来衡量单元测试对功能代码的测试情况,通过统计单元测试中对功能代码中行、分支、类等模拟场景数量,来量化说明测试的充分度。每个代码都有其量化工具,例如 Java - JaCoCo。Python 中常用的就是 coverage。
首先安装 coverage:pip install -U coverage
对于 pytest 使用直接在命令行中运行
1 | coverage run -m pytest test_example.py |
运行结束后即可在同目录下生成 .coverage
文件,再通过 coverage html
命令转换成 html 格式。
会在当前目录下生成 htmlcov
文件,例如 test_example.py 生成的 htmlcov 文件下 index.html
打开后如下所示
可以看到每个文件测试对应的代码覆盖率,点开对应的文件即可看到测试覆盖了哪些代码而哪些没有覆盖,体验感还是非常好滴!🎊
以上简单介绍了常用的 pytest 测试方法和代码测试覆盖率工具 coverage,当然这些工具远比介绍的更强大!积极探索吧~