pytest

高质量的测试是很多良好编程实践比如 TDD、重构以及持续集成等的基本功,也是敏捷软件开发,DevOps 等方法论落地的安全网,更是目前招聘市场对工程师技能考核的重要一环。

很多一线工程师甚至打趣道,先有测试,后有天。

Pytest 作为现今最流行的 Python 测试框架在实际项目中被广泛使用,本文着重介绍如何使用 Pytest 编写测试代码, 内容包括但不限于:

  • Pytest 的安装及配置
  • Pytest 的基本方法
  • Pytest Fixture
  • Pytest Conftest.py
  • Pytest 参数化测试

Pytest 简介及安装方法

Pytest 是一款基于 Python 的测试框架,可以用来编写和运行测试代码。

优势:

  • Pytest 可以并发执行多个测试,节约测试时间
  • 测试函数嗅探
  • 运行测试时可以选择性跳过部分测试
  • 可以指定部分测试运行
  • 免费且开源
  • 语法简单,入门容易

安装方法

需要确保 Python 已经被成功安装:

pip install pytest

查看 Pytest 是否安装成功:

➜python pytest -v=========== test session starts ==============platform linux -- Python 3.6.8, pytest-5.3.1, py-1.8.0

Pytest 基本用法

Pytest 的测试函数需要以 test 开头:

运行测试脚本

# Filename test1.pyimport mathdef test_sqrt():   num = 49   assert math.sqrt(num) == 7def testsquare():   num = 7   assert 7*7 == 88def tesequality(): # 函数不是以 test 开头,pytest 识别不到   assert 10 == 11

执行测试:

pytest test1

输出:

➜  python pytest test1.py=== test session starts ===platform linux -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1rootdir: /mnt/f/pythoncollected 2 items                   test1.py .F                      [100%]=== FAILURES ===___ testsquare ____    def testsquare():       num = 7>      assert 7*7 == 88E      assert (7 * 7) == 88test1.py:11: AssertionError==== 1 failed, 1 passed in 0.03s === # 识别到两个测试函数,一个成功,一个测试失败

可见没有以 test 开头的测试函数并没有被识别,已识别的函数中一个成功,一个测试失败了。

使用 -v 参数可以看到更详细的输出:

pytest -v test1

输出

➜  python pytest -v test1.py=== test session starts ===platform linux -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3cachedir: .pytest_cacherootdir: /mnt/f/pythoncollected 2 items       test1.py::test_sqrt PASSED       [ 50%] # 详细输出test1.py::testsquare FAILED      [100%] # 详细输出=== FAILURES ===___ testsquare ___    def testsquare():       num = 7>      assert 7*7 == 88E      assert 49 == 88E        -49E        +88test1.py:11: AssertionError=== 1 failed, 1 passed in 0.03s ===

指定测试函数

python pytest test1.py -k testsquare -v

输出:

➜  python pytest test1.py -k testsquare -v=== test session starts ===platform linux -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3cachedir: .pytest_cacherootdir: /mnt/f/pythoncollected 2 items / 1 deselected / 1 selected      test1.py::testsquare FAILED     [100%]=== FAILURES ===___ testsquare ___    def testsquare():       num = 7>      assert 7*7 == 88E      assert 49 == 88E        -49E        +88test1.py:11: AssertionError=== 1 failed, 1 deselected in 0.03s ===

Pytest - Fixtures

fixture 本身就是函数,会在测试函数运行前执行。fixture 可以用来准备测试数据,比如数据库链接,URL ,或者是某些输入数据。所以被反复使用的某些测试数据可以使用 fixture 来构造以减少重复代码。语法糖如下:

@pytest.fixture

代码示例:

import pytest@pytest.fixturedef input_value():   input = 39   return inputdef test_divisible_by_3(input_value):   assert input_value % 3 == 0def test_divisible_by_6(input_value):   assert input_value % 6 == 0

fixture 的局限性在于它只在被定义的测试文件内有效,我们无法使用某个脚本内的 fixture 作用于另一个测试脚本,为了使 fixture 可以跨脚本生效,需要把相关 fixture 定义在 conftest.py 文件中。

Pytest - conftest.py

把 fixture 定义到 conftest.py 中,可以跨脚本生效,我们来举例说明:

首先,我们新建一个 conftest.py 文件

# Filename conftest.py import pytest@pytest.fixturedef input_value():   input = 39   return input

接着,我们创建测试脚本 testdivby36.py ,该脚本包含两个测试函数,一个测试输入数据否能被 3 整除,一个测试输入数据是否能被 6 整除:

# Filename test_div_by_3_6.pyimport pytestdef test_divisible_by_3(input_value):   assert input_value % 3 == 0def test_divisible_by_6(input_value):   assert input_value % 6 == 0

然后,我们再创建一个测试脚本 testdivby_13.py ,该脚本包含一个测试函数,测试输入数据能否被 13 整除。

# Filename test_div_by_13.pyimport pytestdef test_divisible_by_13(input_value):   assert input_value % 13 == 0

总体脚本结构如下:file structure运行测试:

pytest -k divisible -v

输出:

➜  python pytest -k divisible -v=== test session starts ===platform linux -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3cachedir: .pytest_cacherootdir: /mnt/f/pythoncollected 3 items   test_div_by_13.py::test_divisible_by_13 PASSED      [ 33%]test_div_by_3_6.py::test_divisible_by_3 PASSED      [ 66%]test_div_by_3_6.py::test_divisible_by_6 FAILED      [100%]=== FAILURES ===___ test_divisible_by_6 ___input_value = 39    def test_divisible_by_6(input_value):>      assert input_value % 6 == 0E      assert 3 == 0E        -3E        +0test_div_by_3_6.py:7: AssertionError=== 1 failed, 2 passed in 0.04s ===

可见 conftest.py 的 fixture ( 输入数据 39 ),被所有不同文件中的测试函数成功接收并都按照预期执行,给出了相关测试结果。

Pytest - 参数化测试

参数化测试(Parameterizing),就是向同一测试函数中传递多个不同的测试参数组合,语法糖:

@pytest.mark.parametrize

使用举例:

import pytest@pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(6,66)])def test_multiplication_11(num, output):   assert 11*num == output

执行测试:

pytest -k multiplication -v

输出:

➜  python pytest -k multiplication -v=== test session starts ===platform linux -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3cachedir: .pytest_cacherootdir: /mnt/f/pythoncollected 7 items / 3 deselected / 4 selected                                                                                                                                                test_multiplication.py::test_multiplication_11[1-11] PASSED     [ 25%]test_multiplication.py::test_multiplication_11[2-22] PASSED     [ 50%]test_multiplication.py::test_multiplication_11[3-35] FAILED     [ 75%]test_multiplication.py::test_multiplication_11[6-66] PASSED     [100%]=== FAILURES ===___ test_multiplication_11[3-35] ___num = 3, output = 35    @pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(6,66)])    def test_multiplication_11(num, output):>      assert 11*num == outputE      assert 33 == 35E        -33E        +35test_multiplication.py:5: AssertionError=== 1 failed, 3 passed, 3 deselected in 0.06s ===

由此就实现向同一测试函数传递不同的测试数据组合,以减少重复代码。

Xfail/Skip Tests

为了使测试函数管理更加灵活,Pytest 支持我们手动标记测试函数为 Xfail 或者 skip 。

如果一个测试函数被标记成 Xfail ,那么该函数会执行,但无论测试结果成功或失败,都不会有详细的日志输出,其语法糖为:

@pytest.mark.xfail

代码示例:

import pytest@pytest.mark.xfaildef test_greater():   num = 100   assert num > 100@pytest.mark.xfaildef test_greater_equal():   num = 100   assert num >= 100

输出:

➜  python pytest -k greater -v=== test session starts ===platform linux -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3cachedir: .pytest_cacherootdir: /mnt/f/pythoncollected 10 items / 8 deselected / 2 selected    # XFAIL 与标准 FAILED 不同,不会有详细日志输出test_compare.py::test_greater XFAIL           [ 50%] test_compare.py::test_greater_equal XPASS     [100%]=== 8 deselected, 1 xfailed, 1 xpassed in 0.05s ===

相比之下 skip 就很好理解,标记 skip 的测试函数会被跳过不予执行,其语法糖为:

@pytest.mark.skip

代码示例:

import pytest@pytest.mark.xfaildef test_greater():   num = 100   assert num > 100@pytest.mark.skip # 标记 skipdef test_greater_equal():   num = 100   assert num >= 100

输出:

➜  python pytest -k greater -v=== test session starts ===platform linux -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3cachedir: .pytest_cacherootdir: /mnt/f/pythoncollected 10 items / 8 deselected / 2 selected    test_compare.py::test_greater XFAIL               [ 50%]test_compare.py::test_greater_equal SKIPPED       [100%]=== 1 skipped, 8 deselected, 1 xfailed in 0.05s ===

测试失败容忍度

从上面的例子中,你可能会观察到默认情况下 Pytest 会执行所有相关的测试,并在测试完成后汇总测试结果,输出日志信息。

如果你的代码要部署到生产环境,原则上任何单元测试都不允许失败。那么如何让测试运行途中一旦发现失败就马上停止运行并输出日志呢?

# maxfail 指定可以接受的最多失败测试个数,一旦达到,立即终止运行,输出日志pytest --maxfail = <num>

代码示例:

# Filename test_failure.pyimport pytestimport mathdef test_sqrt_failure():   num = 25   assert math.sqrt(num) == 88def test_square_failure():   num = 7   assert 7*7 == 99def test_equality_failure():   assert 10 == 66

运行测试:

pytest test_failure.py -v --maxfail=1

输出:

➜  python pytest test_failure.py -v --maxfail=1=== test session starts ===platform linux -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3cachedir: .pytest_cacherootdir: /mnt/f/pythoncollected 3 items    test_failure.py::test_sqrt_failure FAILED   [ 33%]=== FAILURES ===___ test_sqrt_failure ___    def test_sqrt_failure():       num = 25>      assert math.sqrt(num) == 88E      assert 5.0 == 88E        -5.0E        +88test_failure.py:6: AssertionError!!! stopping after 1 failures !!!=== 1 failed in 0.04s ===

高级特性

并发执行测试

如果你有大量的测试需要完成,那么并发执行是一个很好的方式。

并发执行测试,你需要 pytest-xdist 插件,安装方法如下:

pip install pytest-xdist

执行方法:

# -n 3 同时会有三个任务并发执行pytest -n 3

把测试结果存入 XML 文件中

把测试结果存入 XML 文件中方便测试数据的可视化展示,用法如下:

# --junitxml 指定输出 XML 文件名pytest test_multiplication.py -v --junitxml='test_result.xml'

Pytest 良好实践

高质量的测试对于软件产品质量的重要性不言而喻,现今诸多流行的编程实践皆以测试为基础,比如测试驱动开发(TDD),持续集成(CI)等。

关于 Pytest 的使用,几条推荐良好实践是:

  • 针对不同的功能或模块,定义相对应的测试脚本。
  • 测试函数命名清晰明了。
  • 善用 fixture 减少重复代码。

更多 Python 及云计算相关技术问题,欢迎添加我的个人微信 chen5640 一起讨论学习。

阅读全文: http://gitbook.cn/gitchat/activity/5e5316ed98a13e1481f36953

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值