跳转至

pytest 单元测试

概要: 在Python中使用更加易用强大的pytest进行单元测试

创建时间: 2022.10.02 23:28:48

更新时间: 2023.07.28 22:41:47

Python测试

测试步骤

大多数测试基于 Arrange-Act-Assert 的AAA模型,即

  1. 第一步 Arrange,或者叫 setup,目的是准备测试条件
  2. 第二步 Act,通过调用某些函数和方法进行动作
  3. 第三步 Assert,即通过断言第二步的动作的执行结果与预期结果的关系,进行测试结果判定

举个例子,来自Arrange-Act-Assert: A Pattern for Writing Good Tests | Automation Panda

Python
def test_abs_for_a_negative_number():

  # Arrange
  negative = -5

  # Act
  answer = abs(negative)

  # Assert
  assert answer == 5

unittest

unittest是Python自带的单元测试模块,典型的测试方法如下

Python
# test_with_unittest.py

from unittest import TestCase


def plus_1(x: int):
    return x + 1


class TryTesting(TestCase):
    def test_plus_1_pass(self):
        self.assertEqual(2, plus_1(1))


    def test_plus_1_fail(self):
        self.assertEqual(3, plus_1(1))
执行测试用例
Bash
python -m unittest discover
image.png
可以总结使用unittest编写Python测试用例的流程如下

  1. 导入 unittest.TestCase 类,并继承
  2. 在继承类下编写测试方法
  3. 使用 self.assertXXX 方法来进行测试断言

Python自带的unittest中断言方式有很多种,如下所示
image.png
但是如果测试一个纯方法,而不是类方法,还要继承unittest.TestCase 类,显得有些多余

pytest

安装/升级pytest

Bash
pip install -U pytest
同样测试 plus_1 方法,使用pytest可以这么写
Python
# test_with_pytest.py

def plus_1(x: int):
    return x + 1


def test_plus_1_equal():
    assert plus_1(1) == 2


def test_plus_1_not_equal():
    assert not plus_1(1) == 3
image.png

pytest配置

配置文件

pytest的配置文件共分为以下几种

  1. pytest.ini
  2. tox.ini (可参考tox文档)
  3. setup.cfg
  4. pyproject.toml (pytest 6.0+)

除去 pyproject.toml 外,其余三个配置文件格式与 ini 文件类型基本一致

pytest测试

raise

一个简单的测试用例,捕获意料中的异常,参考 How to write and report assertions in tests — pytest documentation

Python
"""
测试捕获指定的异常
"""

import pytest


def f():
    raise SystemExit(1)


def test_f():
    with pytest.raises(SystemExit):
        f()

fixtures

pytest中的fixtures的特点是明确、模块化和可扩展的,主要用于为测试用例提供必要的上下文,可以实现诸如mock依赖方法和数据的功能。

下面举例使用fixture机制来复用测试数据

Python
"""
使用 fixtures 来输入公共测试数据,供两个测试方法使用
"""
import pytest


def plus_10(x: int) -> int:
    return x + 10


def subtract_10(x: int) -> int:
    return x - 10


@pytest.fixture
def example_data():
    return 1


def test_plus_10(example_data):
    assert plus_10(example_data) == 11


def test_subtract_10(example_data):
    assert subtract_10(example_data) == -9
此外,使用pytest.caplog可以对Python自带的logger或者loguru的logger进行测试,详见 DahlitzFlorian/pytest-loguru-support-video-snippets

marks

pytest中的marks用于为测试方法注入元数据(如测试数据,但不会mock对象),然后被fixtures或者插件调用,它可以实现诸如过滤警告信息、参数化测试、条件跳过测试方法、期望测试方法失败等功能。

此处举例使用 pytest.mark 进行参数化测试

Python
"""
参数化测试,使用多组不同数据测试同个方法
https://doc.pytest.org/en/latest/example/parametrize.html
"""
import pytest


def plus(a: int, b: int) -> int:
    return a + b


test_plus_data = [
    (1, 1, 2),
    (1, -1, 0),
    (1, 0, 1),
]


@pytest.mark.parametrize('a, b, expected', test_plus_data)
def test_plus(a, b, expected):
    assert plus(a, b) == expected

durations

找出运行较慢的测试用例,参考 How to invoke pytest — pytest documentation

Python
"""
运行 pytest --durations=1 来抓取测试时长最长的1个用例
"""
import time


def big_sleep():
    time.sleep(1.5)
    return True


def small_sleep():
    time.sleep(0.5)
    return True


def test_big_sleep():
    assert big_sleep()


def test_small_sleep():
    assert small_sleep()
执行测试时加上参数 --durations=1 ,即可找到时长最长的1个测试用例
Python
pytest tests/test_durations_slow_code.py --durations=1
image.png

pytest扩展

覆盖率 pytest-cov

pytest-cov是一个集成了Python覆盖率工具coverage.py的pytest插件,通过如下增加如下参数在pytest调用

Bash
pytest ./tests --cov=src --cov-report=html
文件 src/plus.py
Python
def plus(a, b):
    return a + b
文件 tests/test_plus.py
Python
1
2
3
4
5
from src.plus import plus


def test_plus():
    assert plus(1, 1) == 2
即可输出到项目根目录下的 htmlcov 文件夹中,本地打开如下
image.png
image.png
关于Python测试覆盖率模块 coverage.py 的使用,参考官方文档 Coverage.py — Coverage.py 6.5.0 documentation

随机化 pytest-randomly

单元测试之间应该是相互独立的,彼此解藕的。因此一个测试用例的正确性不能依赖其他测试用例,pytest-randomly就是专门为了乱序执行测试用例而编写的pytest插件。基本使用方法如下,只需要指定随机种子即可

Bash
pip install pytest-randomly
pytest --randomly-seed=1234
image.png
image.png
可以看到不同的种子对于测试用例的执行顺序是有影响的。
此插件的详细说明参考 pytest-dev/pytest-randomly: Pytest plugin to randomly order tests and control random.seed

测试报告 pytest-html

此插件将测试结果呈现到html文件中,详细参考 pytest-html — pytest-html documentation

Bash
pip install pytest-html
pytest --html=./pytest_result.html --self-contained-html
image.png

测试看板 pytest-html-reporter

与上面的插件类似,但此插件旨在将pytest结果数据呈现到看板中,包括历史数据,可视化效果更好,详细参考 prashanth-sams/pytest-html-reporter: Generates a static html report based on pytest framework

Bash
pip install pytest-html-reporter
pytest --html-report=./pytest_dashboard.html --title='PYTEST REPORT'
image.png
image.png
image.png
image.png
image.png

pytest实践

mock

参考