背景

🤙🏻 项目代码测试是必要滴!目前主流的 python 自动化测试框架包括 unittestpytest 和 nose/nose2,以下对这三种框架进行简介并进行对比:

  • unittest是 python 内嵌的测试库,如果对 python 仅了解基础知识那么 unittest 框架相对而言易于理解,unittest 框架可以实现大部分的业务测试,但是测试代码编写格式固定而且较为复杂。
  • pytest 是基于 unittest 扩展的测试框架,相对于 unittest 而言更简单高效而且功能齐全,插件丰富,pytest 兼容 unittest 测试用例但是 unittest 不兼容 pytest,是目前主流的 python 测试框架 🔥。
  • nose/nose2 是一个第三方测试框架,它也完全兼容 unittest 测试并且简单易用,但目前已进入维护模式更新活跃度低。

附上 🧑🏻‍💻 体验感表格来比较功能体验:

功能unittestnose/nose2pytest
自动发现用例☆☆☆☆☆☆☆☆☆
指定用例执行☆☆☆☆☆☆☆☆☆
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# content of test_sample1.py
def inc(x):
return x + 1


def test_answer():
assert inc(3) == 5

# content of test_class.py
import pytest

def f():
raise SystemExit(1)

class TestClass:
def test_one(self):
x = "this"
assert "h" in x

def test_two(self):
x = "hello"
assert hasattr(x, "check")

def test_mytest(self):
with pytest.raises(SystemExit):
f()

在终端执行 pytest 命令结果如下所示:

测试用例

可以看出终端输出的可视化结果比较好,共四个测试用例通过 2 失败 2 👍

还可以安装pytest-html 库,然后通过 pytest --html report.html 命令生成 html 文件可视化。

pytest-html

标记函数

如果代码存在 bug 或者还在 fix,环境不满足等等,可以通过@pytest.mark.skip注解跳过指定的测试。针对以上案例增加 mark 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# content of test_sample.py
def inc(x):
return x + 1


def test_answer():
assert inc(3) == 5

# content of test_class.py
import pytest

def f():
raise SystemExit(1)

class TestClass:
def test_one(self):
x = "this"
assert "h" in x

@pytest.mark.skip
def test_two(self):
x = "hello"
assert hasattr(x, "check")

def test_mytest(self):
with pytest.raises(SystemExit):
f()

那么对应的标记函数测试将不会被执行。

对于上面的 skip,还可以根据指定条件进行忽略测试单元,非常强大

1
2
3
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher")
def test_function():
...

还可使用其他的标签或者自定义标签,使用 @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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 单参数
import pytest
@pytest.mark.parametrize('telephone',
['shenmengjia',
'18798339889',
'1111'])
def test_tele(telephone):
assert len(telephone) == 11

# 多参数
@pytest.mark.parametrize('user, passwd',
[('shen', 'pwd111'),
('meng', 'pwd222')])
def test_passwd(user, passwd):
db = {
'shen': 'pwd111',
'meng': 'pwd333'
}

assert passwd == db[user]

还有个非常重要的概念是 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 打开后如下所示

coverage html

可以看到每个文件测试对应的代码覆盖率,点开对应的文件即可看到测试覆盖了哪些代码而哪些没有覆盖,体验感还是非常好滴!🎊

coverage html

以上简单介绍了常用的 pytest 测试方法和代码测试覆盖率工具 coverage,当然这些工具远比介绍的更强大!积极探索吧~

联系作者