pytest 是一个非常灵活且强大的测试框架,它支持简单的单元测试到复杂的功能测试。显著特点是其简洁的语法,可以无需继承 TestCase 类直接使用函数来编写测试用例,并通过 assert语句 进行断言。还支持参数化测试、插件系统以及丰富的 assertion rewriting 功能,使编写测试更加便捷和直观。我们可以使用它进行自动化测试。

pytest 允许创建自定义的标记,以便组织和分类测试。

PS: 本文基于pytest 8.3.3,建议使用PyCharm编写并运行测试套件,方便。

使用文件自定义标记装饰器

pytest 可在 pytest.ini 文件中自定义 mark 标记装饰器:

在 pytest.ini 中定义标记

# pytest.ini
[pytest]
markers =
    user: 标记用户功能冒烟测试。添加用户、编辑用户、删除用户 (deselect with '-m "not user"')
    business_process(serial): 标记业务流程测试。serial为1代表购物流程,serial为2代表理财流程。
    user_detailed: 标记用户功能的字符值及边界测试

(deselect with '-m "not user"') 这个注释的意思是:如果你想跳过所有标记为 user 的测试,你可以在运行 pytest 命令时加上 -m “not user” 选项。not 后面字符需与标记名一致。

还可以定义 business_process(serial): 标记业务流程测试。serial为1代表购物流程,serial为2代表理财流程。 (deselect with '-m "not business_process"') ,运行 pytest 命令时加上 -m "not business_process" 选项来跳过所有标记为 business_process 的测试。

把 pytest.ini 放在 Config 目录下:

Project/
├── Config/
│	└── pytest.ini            # 自定义 mark 标记
├── Package/                  # 程序目录
│   ├── __init__.py           # 包初始化文件,可以定义一些变量或执行一些操作。当然里面什么都不写也可以。
│   ├── module1.py            # 测试程序模块,比如连接数据库操作数据,接口请求等操作,推荐按功能封装成类
│   └── module2.py            # 测试程序模块,比如连接数据库操作数据,接口请求等操作,推荐按功能封装成类
├── Test/                     # 测试用例目录
│   ├── __init__.py           # 包初始化文件
│   ├── test_module1.py       # 测试 module1 的测试用例
│   └── test_module2.py       # 测试 module2 的测试用例
├── app.py                    # 项目启动文件
├── requirements.txt          # 项目依赖项列表
└── README.md                 # 项目说明文档

在app.py中指定 pytest.ini 的路径:

# app.py
import pytest
import sys
import logging
import argparse

# 设置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')


def run_tests(test_target):

    # 指定 pytest.ini 的路径并运行测试用例
    pytest_args = ["-c", "Config/pytest.ini", "-v", test_target]

    try:
        # 运行测试
        exit_code = pytest.main(pytest_args)

        # 根据退出码判断测试是否成功
        exit_messages = {
            0: "全部测试用例通过",
            1: "部分测试用例未通过",
            2: "测试过程中有中断或其他非正常终止。",
            3: "内部错误",
            4: "pytest无法找到任何测试用例",
            5: "pytest遇到了命令行解析错误"
        }
        logging.info(exit_messages.get(exit_code, "未知的退出码"))
    except Exception as e:
        logging.error(f"运行测试时发生错误: {e}")
        return 1

    return exit_code


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="使用指定的命令运行 pytest 测试。")
    parser.add_argument('test_target', nargs='?', type=str, default="Test/", help='指定运行命令。 (默认: Test/)')

    args = parser.parse_args()
    exit_code = run_tests(args.test_target)
    sys.exit(exit_code)

除了在 pytest.ini 自定义外,还有其它方法,这里就不介绍了,感兴趣可以访问官方文档:Working with custom markers

在测试用例中使用自定义标记

编写测试用例并使用自定义标记进行装饰:

# test_module1.py 
import pytest


@pytest.mark.user
def test_weird_simple_case():
    assert [1]


@pytest.mark.business_process(serial=1)
def test_weird_simple_case1():
    assert [1]


@pytest.mark.business_process(serial=2)
def test_weird_simple_case2():
    assert [2]


@pytest.mark.user_detailed
def test_weird_simple_case3():
    assert [2]


@pytest.mark.user
@pytest.mark.user_detailed
def test_weird_simple_case4():
    assert [2]

终端中运行标记为 user 的测试用例:

cd C:\PythonTest\
pytest Test\ -c "Config/pytest.ini" -m user

整合进 app.py 里:

# app.py
import pytest
import sys
import logging
import argparse

# 设置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')


def run_tests(test_target, mark):
    if mark == None:
        # 指定 pytest.ini 的路径并运行测试用例
        pytest_args = ["-c", "Config/pytest.ini", "-v", test_target]
    else:
        # 指定 pytest.ini 的路径并运行指定标记的测试用例
        pytest_args = ["-c", "Config/pytest.ini", "-v", test_target, "-m", mark]

    try:
        # 运行测试
        exit_code = pytest.main(pytest_args)

        # 根据退出码判断测试是否成功
        exit_messages = {
            0: "全部测试用例通过",
            1: "部分测试用例未通过",
            2: "测试过程中有中断或其他非正常终止。",
            3: "内部错误",
            4: "pytest无法找到任何测试用例",
            5: "pytest遇到了命令行解析错误"
        }
        logging.info(exit_messages.get(exit_code, "未知的退出码"))
    except Exception as e:
        logging.error(f"运行测试时发生错误: {e}")
        return 1

    return exit_code


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="使用指定的命令运行 pytest 测试。")
    parser.add_argument('test_target', nargs='?', type=str, default="Test/", help='指定运行命令。 (默认: Test/)')
    #  命令行新增参数-mark,可选参数
    parser.add_argument('-m', '--mark', nargs='?', type=str, help='指定运行命令。 (默认: Test/)')

    args = parser.parse_args()
    exit_code = run_tests(args.test_target, args.mark)
    sys.exit(exit_code)

终端启动:

  • 运行标记为 user 的测试用例:
cd C:\PythonTest\
python .\app.py -m user
  • 运行非 user 标记的测试用例:
cd C:\PythonTest\
python .\app.py -m "not user"
  • 运行标记为 business_process(serial=2) 的测试用例:
cd C:\PythonTest\
python .\app.py -m "business_process(serial=2)"
  • 运行标记为 business_process(serial=2) 的测试用例和标记为 business_process(serial=1) 的测试用例:
cd C:\PythonTest\
python .\app.py -m "business_process(serial=2) or business_process(serial=1)"
  • 运行标记为 user 并同时标记为 user_detailed的测试用例:
cd C:\PythonTest\
python .\app.py -m "user and user_detailed"

PyCharm中启动:

只需要在步骤七输入相关参数,针对不同的测试策略,就可以创建不同的运行配置。

-m 参数不是必须的,可以为空去运行指定文件中的测试用例或指定测试用例,参考测试套件的目录结构

标记测试组和测试模块

同样可以标记测试组或整个模块:

标记测试组:

# test_module1.py
import pytest


# 可以添加多个标记   @pytest.mark.business_process
@pytest.mark.user
class TestCase:

    def test_case1(self):
        assert [1]

    def test_case2(self):
        assert [1]

    def test_case3(self):
        assert [2]

标记整个模块:

# test_module1.py
import pytest

# 可以添加多个标记 pytestmark = [pytest.mark.user, pytest.mark.user_detailed]
pytestmark = pytest.mark.user


def test_case1():
    assert [1]


def test_case2():
    assert [1]


def test_case

将可调用对象传递给自定义标记

pytest 提供方法,使用可调用函数作为标记的参数:

import pytest


def hello_world(*args, **kwargs):
    return "Hello World"


# 自定义标记的使用
@pytest.mark.business_process.with_args(hello_world, serial=1)
def test_custom_mark(request):
    # 在测试函数内部,可以通过 request.node.get_closest_marker 获取标记信息
    marker = request.node.get_closest_marker('business_process')
    if marker:
        func = marker.args[0]  # 获取传递给标记的第一个参数,即我们的hello_world函数
        result = func()  # 调用函数
        assert result == "Hello World"  # 断言结果

python .\app.py -m business_process ,运行结果:

============================= test session starts =============================
collecting ... collected 1 item

test_mod.py::test_custom_mark PASSED                                     [100%]

======================== 1 passed, 1 warning in 0.01s =========================

实际应用中,可以在被调用的函数中构建测试需要的操作,满足个性化要求。

实际测试中运用自定义标记

假设有一个书籍下载应用服务,包含四个模块:用户管理、登录、查询、下载。用户管理包括注册、修改密码、注销等功能,查询支持出版时间、作者、书名、出版社等条件查询,下载则支持各种格式。

就可以这样定义:

# pytest.ini
[pytest]
markers =
	business_process: 标记业务流程测试。包括用户注册、登录、查询书籍和下载书籍。 (deselect with '-m "not business_process"')
    user(serial): 标记用户功能测试。注册用户(1)、修改密码(2)、注销用户(3) (deselect with '-m "not user"')
    select: 标记查询功能测试。 (deselect with '-m "not select"')
	download:标记下载功能测试。 (deselect with '-m "not download"')

以上只值得参考,请根据实际自定义标记。


THEEND



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