Python高级技术之:`pytest`的`markers`:如何对测试用例进行分组和筛选。

各位观众,早上好/下午好/晚上好! 欢迎来到今天的“Python高级技术之pytestmarkers”讲座。今天我们来聊聊pytest中一个非常实用,但又经常被忽视的功能——markers。用好了它,你的测试用例管理和执行效率绝对能上一个台阶。

什么是markers?简单粗暴的定义

markers,你可以把它理解为测试用例的“标签”或者“分组标识”。就像给文件打标签一样,你可以给你的测试用例打上各种各样的标签,比如“性能测试”、“数据库测试”、“UI测试”、“冒烟测试”等等。

markers有什么用?

有了这些标签,你就可以:

  • 分组执行测试用例:只运行打着特定标签的测试用例。
  • 排除特定测试用例:跳过打着某些标签的测试用例。
  • 为测试用例添加元数据:比如,标记某个测试用例需要特定的环境或者参数。
  • 生成测试报告:根据标签对测试结果进行分类和统计。

总而言之,markers能让你更灵活、更有条理地管理和执行你的测试用例。

markers的基本用法:上手非常容易

  1. 注册markers (可选但推荐)

    虽然pytest允许你直接使用未注册的markers,但强烈建议你先在pytest.inipyproject.toml文件中注册你的markers。这样做的好处是:

    • pytest会检查你的markers是否拼写正确。
    • 你可以为markers添加描述信息,方便其他人理解它的作用。

    pytest.ini中注册markers的格式如下:

    [pytest]
    markers =
       smoke: 冒烟测试
       regression: 回归测试
       database: 数据库测试
       ui: UI测试
       performance: 性能测试

    如果你喜欢用pyproject.toml,格式如下:

    [tool.pytest.ini_options]
    markers = [
       "smoke: 冒烟测试",
       "regression: 回归测试",
       "database: 数据库测试",
       "ui: UI测试",
       "performance: 性能测试",
    ]
  2. 在测试用例上添加markers

    使用@pytest.mark.<marker_name>装饰器来给测试用例添加markers。例如:

    import pytest
    
    @pytest.mark.smoke
    def test_login_success():
       """测试登录成功场景"""
       assert True
    
    @pytest.mark.regression
    def test_add_to_cart():
       """测试添加商品到购物车"""
       assert True
    
    @pytest.mark.database
    def test_query_user_info():
       """测试查询用户信息"""
       assert True
  3. 运行指定markers的测试用例

    使用-m选项来指定要运行的markers

    • 运行所有带有smoke标记的测试用例:

      pytest -m smoke
    • 运行所有带有smokeregression标记的测试用例:

      pytest -m "smoke or regression"
    • 运行所有带有smoke但没有database标记的测试用例:

      pytest -m "smoke and not database"
    • 运行所有没有标记的测试用例

      pytest -m "not <any registered marker>"
      # 例如:
      pytest -m "not smoke"

    注意: 表达式里面的 andornot 是大小写敏感的。

markers的高级用法:让你的测试更强大

  1. 带参数的markers

    markers不仅可以作为一个简单的标签,还可以携带参数。这在需要为测试用例提供不同的配置或者数据时非常有用。

    import pytest
    
    @pytest.mark.parametrize("browser", ["chrome", "firefox", "safari"])
    def test_open_browser(browser):
       """测试打开不同浏览器"""
       print(f"Opening browser: {browser}")
       assert True
    
    @pytest.mark.timeout(seconds=5)
    def test_long_running_task():
       """测试长时间运行的任务"""
       import time
       time.sleep(3) # 模拟耗时操作
       assert True

    在这个例子中,@pytest.mark.parametrize@pytest.mark.timeout 都是带参数的markersparametrize 用于参数化测试,timeout 用于设置测试用例的超时时间。

  2. 自定义markers

    除了pytest内置的markers(比如parametrizexfailskip),你还可以自定义markers来实现更复杂的功能。

    比如,你可以创建一个needs_dbmarker,用于标记需要数据库连接的测试用例。

    import pytest
    
    @pytest.fixture(scope="session")
    def db_connection():
       """创建数据库连接"""
       print("Connecting to database...")
       conn = ... # 这里替换为你的数据库连接代码
       yield conn
       print("Closing database connection...")
       conn.close()
    
    def pytest_configure(config):
       config.addinivalue_line(
           "markers", "needs_db: Mark test as needing a database connection."
       )
    
    @pytest.mark.needs_db
    def test_query_data(db_connection):
       """测试查询数据"""
       data = db_connection.query("SELECT * FROM users")
       assert len(data) > 0

    在这个例子中,pytest_configure 是一个钩子函数,用于在pytest启动时进行配置。我们使用 config.addinivalue_line 函数注册了 needs_db 这个markerdb_connection 是一个fixture,用于提供数据库连接。 在test_query_data测试用例中,我们使用 @pytest.mark.needs_db 标记该用例需要数据库连接,并且使用了 db_connection fixture。

    注意事项:

    • pytest_configure函数必须在conftest.py文件中定义,或者在插件中定义。
    • conftest.py文件应该放在你的测试根目录下,或者任何包含测试用例的子目录下。
  3. markersfixture的结合

    markersfixture结合使用,可以实现更强大的测试控制。例如,你可以根据marker来选择不同的fixture

    import pytest
    
    @pytest.fixture
    def slow_operation():
       """模拟一个耗时操作"""
       import time
       time.sleep(2)
       return "Slow result"
    
    @pytest.fixture
    def fast_operation():
       """模拟一个快速操作"""
       return "Fast result"
    
    def pytest_runtest_setup(item):
       """根据marker选择不同的fixture"""
       if "slow" in item.keywords:
           item.fixturenames.append("slow_operation")
       else:
           item.fixturenames.append("fast_operation")
    
    @pytest.mark.slow
    def test_slow_task(slow_operation):
       """测试耗时任务"""
       print(f"Result: {slow_operation}")
       assert True
    
    def test_fast_task(fast_operation):
       """测试快速任务"""
       print(f"Result: {fast_operation}")
       assert True

    在这个例子中,pytest_runtest_setup 是一个钩子函数,用于在每个测试用例运行之前进行设置。我们检查测试用例是否带有 slow 这个marker,如果有,则将 slow_operation 这个fixture添加到测试用例的fixture列表中;否则,添加 fast_operation 这个fixture。

    解释:

    • item.keywords:包含了测试用例的所有markers和名称。
    • item.fixturenames:是一个列表,包含了测试用例需要使用的所有fixture的名称。
    • pytest_runtest_setup:在每个测试用例执行前都会被调用。
  4. 使用mark装饰器堆叠多个markers

    有时,你可能需要为一个测试用例添加多个markers。可以使用mark装饰器堆叠多个markers

    import pytest
    
    @pytest.mark.smoke
    @pytest.mark.regression
    def test_important_feature():
       """测试重要功能"""
       assert True

    这个测试用例同时被打上了smokeregression两个markers

  5. 动态添加markers

    有时候,你可能需要在运行时根据某些条件动态地添加markers

    import pytest
    
    def pytest_collection_modifyitems(config, items):
       """动态添加markers"""
       for item in items:
           if item.name.startswith("test_api_"):
               item.add_marker(pytest.mark.api)
           if item.getparent(pytest.Module).name == "test_slow.py":
               item.add_marker(pytest.mark.slow)
    
    def test_api_get_data():
       """测试API获取数据"""
       assert True
    
    def test_normal_flow():
       """测试正常流程"""
       assert True

    在这个例子中,pytest_collection_modifyitems 是一个钩子函数,用于在测试用例收集完成后修改测试用例列表。我们遍历所有测试用例,如果测试用例的名称以 test_api_ 开头,则添加 api 这个marker。如果测试用例位于test_slow.py文件中,则添加 slow 这个marker

    解释:

    • pytest_collection_modifyitems:在测试用例收集完成后被调用。
    • items:是一个列表,包含了所有收集到的测试用例。
    • item.name:是测试用例的名称。
    • item.getparent(pytest.Module).name:是测试用例所在的模块的名称。
    • item.add_marker():用于向测试用例添加marker

markers的常见使用场景:让测试更有针对性

使用场景 描述 示例
冒烟测试 快速验证核心功能是否正常,用于快速排查问题。 @pytest.mark.smoke
回归测试 验证修改后的代码是否引入新的问题,确保原有功能正常运行。 @pytest.mark.regression
UI测试 测试用户界面是否符合设计规范,用户交互是否流畅。 @pytest.mark.ui
数据库测试 测试数据库连接是否正常,数据读写是否正确。 @pytest.mark.database
性能测试 测试系统的性能指标,比如响应时间、吞吐量等。 @pytest.mark.performance
兼容性测试 测试系统在不同平台、浏览器、设备上的兼容性。 @pytest.mark.platform("windows"), @pytest.mark.browser("chrome")
安全性测试 测试系统的安全性,比如是否存在漏洞、是否容易被攻击。 @pytest.mark.security
需要特定环境 标记测试用例需要在特定的环境下运行,比如需要特定的操作系统或者硬件设备。 @pytest.mark.env("staging"), @pytest.mark.hardware("GPU")
测试不同的数据 使用parametrize marker参数化测试,用不同的数据运行相同的测试逻辑。 @pytest.mark.parametrize("username, password", [("user1", "pass1"), ("user2", "pass2")])
标记预期失败 使用xfail marker标记预期失败的测试用例,如果测试用例确实失败了,pytest不会将其视为错误。 @pytest.mark.xfail(reason="Known issue with feature X")
跳过测试 使用skip marker跳过某些测试用例,比如某些功能尚未实现或者不适用于当前环境。 @pytest.mark.skip(reason="Feature not yet implemented")
标记耗时测试 标记耗时较长的测试,可以单独运行或者跳过。 @pytest.mark.slow
依赖测试 使用第三方插件 (例如 pytest-dependency) 标记测试用例之间的依赖关系,确保某些测试用例在其他测试用例通过后才能运行。 需要安装 pytest-dependency 插件,然后可以使用 @pytest.mark.dependency(depends=["test_login"])

最佳实践:让markers发挥最大价值

  1. 保持markers的命名规范:使用清晰、简洁、易懂的名称,避免使用模糊不清的缩写或者术语。
  2. markers添加描述信息:在pytest.inipyproject.toml文件中为markers添加详细的描述信息,方便其他人理解它的作用。
  3. 避免过度使用markers:不要为每个测试用例都添加markers,只为那些需要分组或者筛选的测试用例添加。
  4. 定期清理不再使用的markers:随着项目的演进,某些markers可能不再使用,应该及时清理,避免造成混乱。
  5. 利用markers生成更丰富的测试报告:可以使用pytest-html或者其他报告生成插件,根据markers对测试结果进行分类和统计,生成更易于理解的测试报告。
  6. 使用markers控制测试环境:可以结合fixturemarkers,根据marker来选择不同的测试环境配置,比如使用不同的数据库或者不同的API endpoint。

markers的局限性

虽然markers非常强大,但也存在一些局限性:

  1. 无法实现复杂的依赖关系markers只能简单地分组和筛选测试用例,无法表达复杂的依赖关系,比如A测试必须在B测试之后运行。对于复杂的依赖关系,可以考虑使用pytest-dependency插件。
  2. 容易被滥用:如果过度使用markers,可能会导致测试用例管理混乱,难以维护。
  3. 需要手动维护markers需要手动添加和维护,如果忘记添加或者添加错误,可能会导致测试结果不准确。

总结:markerspytest的瑞士军刀

pytestmarkers是一个非常实用且强大的功能,它可以让你更灵活、更有条理地管理和执行你的测试用例。掌握markers的用法,可以极大地提高你的测试效率和质量。把它想象成pytest的瑞士军刀,用对了地方,绝对能让你事半功倍。

希望今天的讲座对你有所帮助! 祝大家测试愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注