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 ===================================
条件表达式可以如下:
- 比较表达式
# 等于
assert 10 == 5 # False
# 不等于
assert 10 != 5 # True
# 大于
assert 10 > 5 # True
# 小于
assert 10 < 5 # False
# 大于等于
assert 10 >= 5 # True
# 小于等于
assert 10 <= 5 # False
- 逻辑表达式
# 逻辑与
assert (10 > 5) and (5 < 10) # True
# 逻辑或
assert (10 > 5) or (5 > 10) # True
# 逻辑非
assert not (10 > 5) # False
- 成员表达式
# in 操作符
assert 5 in [1, 2, 3, 4, 5] # True
# not in 操作符
assert 6 not in [1, 2, 3, 4, 5] # True
- 身份表达式
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 是不同的对象
- 布尔值的隐式转换
# 空列表、空字符串、零等会被视为 False,而非空列表、非空字符串、非零等会被视为 True
# 空列表
assert [] # False
# 非空列表
assert [1, 2, 3] # True
# 空字符串
assert "" # False
# 非空字符串
assert "Hello" # True
# 零
assert 0 # False
# 非零
assert 10 # True
- 布尔值
# 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.py、mod_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