Pytest 是一个功能强大且灵活的 Python 测试框架,广泛用于单元测试、功能测试以及自动化测试。它以简洁的语法和丰富的插件生态系统著称,能够满足从简单到复杂的测试需求。

pytest 的设计思想,围绕测试用例的发现、执行、管理和报告展开,其灵活性和可扩展性也主要服务于测试用例的高效编写和维护。

安装 pytest


安装命令

# 安装 pytest
pip install pytest
# 验证安装,应该输出类似 pytest 9.0.2 信息
pytest --version

简单示例

def test_addition():
    assert True

def test_string():
    assert True

运行结果:


======================================= test session starts =======================================
platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0
collected 2 items

cases/test_a.py ..                                                                         [100%]

======================================= 2 passed in 0.01s ========================================

断言


Pytest 使用 assert 语句进行断言

def test_list_fail():
    assert True, "两个值不同"

def test_list_pass():
    assert False, "两个值不同"

assert 后接两个参数:第一个参数是条件表达式,条件表达式为 True(真)通过断言;条件表达式为 False(假),立即抛出 AssertionError,并附带可选的错误信息;第二个参数是可选的错误信息。

运行结果:

====================================== test session starts =======================================
platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0
collected 2 items

cases/test_a.py .F                                                                         [100%]

============================================ FAILURES ============================================
_________________________________________ test_list_pass _________________________________________

    def test_list_pass():
>       assert False, "两个值不同"
E       AssertionError: 两个值不同
E       assert False

cases/test_a.py:5: AssertionError
==================================== short test summary info =====================================
FAILED cases/test_a.py::test_list_pass - AssertionError: 两个值不同
================================== 1 failed, 1 passed in 0.04s ===================================

条件表达式可以如下

  1. 比较表达式
# 等于
assert 10 == 5  # False

# 不等于
assert 10 != 5  # True

# 大于
assert 10 > 5   # True

# 小于
assert 10 < 5   # False

# 大于等于
assert 10 >= 5  # True

# 小于等于
assert 10 <= 5  # False
  1. 逻辑表达式
# 逻辑与
assert (10 > 5) and (5 < 10)  # True

# 逻辑或
assert (10 > 5) or (5 > 10)   # True

# 逻辑非
assert not (10 > 5)           # False
  1. 成员表达式
# in 操作符
assert 5 in [1, 2, 3, 4, 5]  # True

# not in 操作符
assert 6 not in [1, 2, 3, 4, 5]  # True
  1. 身份表达式
a = [1, 2, 3]
b = [1, 2, 3]
c = a

# is 操作符
assert a is c  # True,因为 a 和 c 引用同一个对象

# is not 操作符
assert a is not b  # True,因为 a 和 b 是不同的对象
  1. 布尔值的隐式转换
# 空列表、空字符串、零等会被视为 False,而非空列表、非空字符串、非零等会被视为 True

# 空列表
assert []  # False

# 非空列表
assert [1, 2, 3]  # True

# 空字符串
assert ""  # False

# 非空字符串
assert "Hello"  # True

# 零
assert 0  # False

# 非零
assert 10  # True
  1. 布尔值
# True
assert True

# False
assert False

多个断言

测试用例中,可以有多个断言,任意断言失败,不再执行该测试用例,判定失败。

def test_case1():
    assert True, "成功"
    assert True, "成功"
    assert True, "成功"


def test_case2():
    assert True, "成功"
    assert False, "失败"
    assert True, "成功"

运行结果:

======================================================== test session starts ========================================================
platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0
collected 2 items

cases/test_a.py .F                                                                                                            [100%]

============================================================= FAILURES ==============================================================
____________________________________________________________ test_case2 _____________________________________________________________

    def test_case2():
        assert True, "成功"
>       assert False, "失败"
E       AssertionError: 失败
E       assert False

cases/test_a.py:9: AssertionError
====================================================== short test summary info ======================================================
FAILED cases/test_a.py::test_case2 - AssertionError: 失败
==================================================== 1 failed, 1 passed in 0.04s ====================================================

测试用例


pytest 强制要求测试文件名称以 test_ 开头或以 _test.py 结尾,比如 test_mod.pymod_test.py。不符合此规则的文件,不会被 pytest 运行。

测试函数

测试函数必须以 test_ 开头,不符合此规则的函数,不会被 pytest 运行。

def test_case1():
    assert True, "失败"


def case2():
    assert True, "失败"

运行结果:

====================================== test session starts =======================================
platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0
collected 1 item

cases/test_a.py .                                                                          [100%]

======================================= 1 passed in 0.01s ========================================

测试类

pytest 支持测试类,可以在类里编写某个功能的所有测试用例。必须以 Test 开头(首字母大写),且不能有 init 方法。

class TestCaseGroup:
    def test_case1(self):
        assert True, "失败"

    def test_case2(self):
        assert True, "失败"
        

class CaseGroup:
    def test_case3(self):
        assert True, "失败"

    def test_case4(self):
        assert True, "失败"
        
class TestCaseGroup1:
    def __init__(self) -> None:
        pass
        
    def test_case5(self):
        assert True, "失败"

    def test_case6(self):
        assert True, "失败"

运行结果:

====================================== test session starts =======================================
platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0
collected 2 items

cases/test_a.py ..                                                                         [100%]

======================================== warnings summary ========================================
cases/test_a.py:16
  /home/ether/projects/video-service-private-network-gateway/cases/test_a.py:16: PytestCollectionWarning: cannot collect test class 'TestCaseGroup1' because it has a __init__ constructor (from: cases/test_a.py)
    class TestCaseGroup1:

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================== 2 passed, 1 warning in 0.01s ==================================

运行测试


编写测试用例后,可以在终端中运行测试用例

  • 运行项目中所有测试用例:
pytest
  • 运行 cases/test_mod.py 中所有测试用例:
pytest cases/test_mod.py
  • 运行 cases/test_mod.py 中的 test_case1 测试用例:
pytest cases/test_mod.py::test_case1
  • 运行 cases/test_mod.py 中的 TestCaseGroup 测试用例组:
pytest cases/test_mod.py::TestCaseGroup
  • 运行 cases/test_mod.py 中 TestCaseGroup 中的 test_case1 测试用例:
pytest cases/test_mod.py::TestCaseGroup::test_case1

启动文件


编辑启动文件

作为项目的启动文件,需要支持 pytest 的各种参数,并支持扩展自定义参数,这里选择使用 argparse 库(内置库)实现。

import logging
import sys
import argparse

import pytest
from pytest import ExitCode  

# 自定义日志格式
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s'
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
logger = logging.getLogger(__name__)

# 运行测试
def run_tests(parser: argparse.ArgumentParser) -> int:
    """运行测试"""
    # 解析自定义参数和 pytest 参数
    customize_args, pytest_args = parser.parse_known_args()
    
    # 如果用户请求帮助,则打印帮助信息并退出
    if customize_args.help:
        parser.print_help()
        print("\n=== pytest 原生参数 help 信息 ===")
        pytest.main(["-h"])
        return 0  

    # 开始运行测试
    try:
        logger.info(f"开始运行测试,,pytest参数: {pytest_args}")
        exit_code = pytest.main(pytest_args)

        exit_code_value = exit_code.value if isinstance(exit_code, ExitCode) else exit_code
        exit_messages = {
            0: "✅ 全部测试用例通过",
            1: "⚠️ 部分测试用例未通过",
            2: "❌ 测试过程中有中断或其他非正常终止",
            3: "❌ 内部错误",
            4: "❌ pytest无法找到任何测试用例",
            5: "❌ 命令行解析错误"
        }

        logger.info(exit_messages.get(exit_code_value, f"❓ 未知的退出码: {exit_code_value}"))
        return exit_code_value

    except Exception:
        logger.exception("运行测试时发生致命错误:")
        return 1

# 构建参数解析器,传递 pytest 参数,后续根据需求创建自定义参数
def parse_arguments() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="pytest 启动脚本(支持自定义参数 + pytest 参数)",
        add_help=False  # 隐藏默认帮助信息,避免与 pytest 的 -h 冲突
    )

    parser.add_argument(
        "-h", "--help",
        action="store_true",
        help="显示帮助信息(包含自定义参数和pytest原生参数)"
    )

    return parser

# 主函数
def main():
    parser = parse_arguments()
    exit_code = run_tests(parser)
    sys.exit(exit_code)

if __name__ == "__main__":
    main()

使用说明

  • 运行项目中所有测试用例:
python3 run.py
  • 运行 cases/test_mod.py 中所有测试用例:
python3 run.py cases/test_mod.py
  • 运行 cases/test_mod.py 中的 test_case1 测试用例:
python3 run.py cases/test_mod.py::test_case1
  • 运行 cases/test_mod.py 中的 TestCaseGroup 测试用例组:
python3 run.py cases/test_mod.py::TestCaseGroup
  • 运行 cases/test_mod.py 中 TestCaseGroup 中的 test_case1 测试用例:
python3 run.py cases/test_mod.py::TestCaseGroup::test_case1

THEEND



© 转载需要保留原始链接,未经明确许可,禁止商业使用。CC BY-NC-ND 4.0