python之pytest学习笔记

  • test的本质:
    1. Arrange
    2. Act
    3. Assert
    4. Cleanup
  • pytest执行测试用例的一般步骤:
    1. 测试发现:pytest从命令行或代码中指定的位置开始,查找以test_开头的文件、类和函数。
    2. Fixture解析:pytest分析fixture依赖关系,构建依赖图。对于autouse fixture,它会自动应用到其作用域内的所有测试,而不需要测试函数显式声明。
    3. 测试执行:对于每个测试项,pytest按照fixture的作用域和依赖关系执行setup、测试调用和teardown。
  • 如何调用Pytest
    • 指定特定的测试用例
      • 模块(.py文件)、目录、关键字、类、函数方法、特定参数的函数方法
      • 特定标签
      • 导入包名、读取txt文件
      • 执行时间最慢的文件--slow
    • 预加载内、外部插件运行
    • 使用python 命令
    • pytest.main在代码内部调用
    • 查看选中的测试用例:pytest --collect-only
  • 如何编写和报告断言
    • 使用断言语句断言:assert f() == 4
    • 近似相等的断言:pytest.approx()
    • 预期异常的断言: pytest.raises()
      • 匹配异常消息    with pytest.raises(ValueError, match=r".* 123 .*")
      • 异常组的断言:with pytest.RaisesGroup(ValueError)       它允许将多个异常包装成一个单一的“组异常”一起抛出。这就像一个“异常容器”,可以同时携带多个不同类型的异常。
        • 传统异常模型:“一个错误,一个异常”。
        • 新的异常组模型:“一次操作,多个异常;一并抛出,统一处理”。
      • xfail 标记类异常   pytest.mark.xfail
    • 预期警告的断言: pytest.warns
    • 上下文比较类(context-sensitive)的断言:assert set1 == set2
    • 自定义失败断言:pytest_assertrepr_compare(config, op, left, right)
      • 可以在conftest文件中作为钩子函数使用
    • 重写断言并存入缓存:register_assert_rewrite 、pyc files
  • 如何使用属性标记测试函数
    • 需注册标签(最好限制为强制注册) 
    • 内置标签:   
      • usefixtures - use fixtures on a test function or class           @pytest.mark.usefixtures("cleandir")
      • filterwarnings - filter certain warnings of a test function    @pytest.mark.filterwarnings("ignore:api v1")
      • skip - always skip a test function                                         @pytest.mark.skip(reason="no way of currently testing this")
      • skipif - skip a test function if a certain condition is met      @pytest.mark.skipif(sys.version_info < (3, 13), reason="requires python3.13 or higher")
      • xfail - produce an “expected failure” outcome if a certain condition is met              @pytest.mark.xfail
      • parametrize - perform multiple calls to the same test function.                                    @pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
  • 关于Fixture
    • 它的主要作用是创建可复用的测试数据和准备测试环境,并以“依赖注入”的方式将它们提供给测试函数。
    • In testing, a fixture provides a defined, reliable and consistent context for the tests.  主要是提供上下文环境,即Arrange和Cleanup的部分
    • 为什么使用 Fixture?(它的巨大价值)
      1. 消除重复代码:如果多个测试用例都需要“苹果”和“果篮”,你只需要定义一次 fixture,然后在不同测试中声明使用即可。修改数据只需修改一处。
      2. 逻辑清晰,关注点分离:测试函数内部只关注测试逻辑(断言 my_fruit in fruit_basket),而不需要关心数据是如何构造的。数据构造的细节被封装在 fixture 中。
      3. 高效的依赖管理:如图中所示,fixture 之间可以相互依赖(fruit_basket依赖 my_fruit)。pytest 会自动处理这些依赖关系,并按正确的顺序执行它们,构建出复杂的测试场景。
      4. 灵活性高:你可以通过组合不同的 fixture 来轻松构建各种测试场景,比如一个测试用“苹果”,另一个测试可以用“橘子”,只需创建不同的 fixture 或进行参数化。
    • 可以使用第三方库来加载测试数据,如pytest-datadir、 pytest-datafiles
  • 如何使用Fixture
    • 作为被测函数中的一个参数声明来使用fixture
    • fixture 函数也可以通过声明参数来使用另一个 fixture
    • 同一个fixture可以被调用多次
    • 一个test函数/fixture可以一次调用多个fixtures
    • 在模块和类中有多种调用方式。如用mark标签来调用多个fixture     @pytest.mark.usefixtures("cleandir", "anotherfixture")
    • 使用pytest_plugins = "mylibrary.fixtures"来调用外部的fixture
    • pytest fixture缓存机制
      • 你可以把每个 fixture 在一个测试函数内的生命周期,理解为一个 “单例”或 “一次性计算”。
      • 核心规则是:在同一个测试函数执行过程中,同一个 fixture 只会被实际执行一次,其返回值会被缓存起来,后续所有对该 fixture 的请求都会直接拿到这个缓存值,而不会重复执行 fixture 函数。
      • 所有依赖方访问的是同一个缓存对象
      • 缓存机制的关键价值:
        1. 保证状态一致性:这是最重要的好处。所有依赖方操作的是同一个对象。在这个例子中,append_firstfixture 修改了 order列表,测试函数中请求的 order看到的是被修改后的同一个列表。
        2. 提升性能:如果 fixture 执行的是创建数据库连接、启动浏览器等耗时操作,缓存机制避免了重复执行,极大提升了测试速度。
        3. 避免副作用:如果 fixture 包含不可重复的操作(例如,在数据库中插入一条唯一记录),缓存机制防止了因重复执行而导致的错误。
    • Autouse fixtures
      • Autouse fixtures 是那些你无需在测试函数参数中显式声明,但 pytest 会自动为每个测试函数执行的 fixture
        • 普通 Fixture:测试函数需要“主动请求”(通过函数参数声明)才会执行。
        • Autouse Fixture:只要它被定义,就会“自动运行”,作用于其作用域内的所有测试。
      • 特别适合用于那些全局性、基础性的设置和清理工作
        • 测试数据初始化/清理
        • 全局 Mock
        • 临时目录/环境设置
      • 重要注意事项与最佳实践:
        • 谨慎使用:不要滥用 autouse。只有真正被所有或绝大多数测试需要的 fixture 才应该设置为 autouse=True。滥用会使测试行为变得不透明,难以理解测试的真正依赖。
        • 作用域管理:使用 scope参数(如 scope="session", scope="module")来控制 autouse fixture 的执行频率,避免不必要的重复设置。
        • 避免状态残留:如图中示例所示,append_first修改了 order的状态,影响了后续的 test_string_and_int。在测试中,应极力避免这种有状态的、会产生副作用的 autouse fixture,因为它破坏了测试的独立性,导致测试结果不可预测且难以调试。每个测试都应该是隔离的。
    • 固定作用域 :scope="module"
      • autouse fixture在其作用域内自动执行。对于class作用域,它会在类的测试方法之前执行一次。
    • 动态作用域:Dynamic scope  @pytest.fixture(scope=determine_scope)
    • 参数化的Fixture
      • 当使用 params参数化一个 fixture 时,pytest 会为 params列表中的每一个值都创建并缓存一个独立的 fixture 实例。
      • 参数化可以自定义ids,来方便定位问题。
      • 参数化时,也可以单独为某个参数来打标签  
      • 使用 pytest_generate_tests钩子(hook)进行动态参数化
    • 参数化执行时,testcase会自动分组:(Automatic grouping)
      • 一个参数一个参数的运行的,即一个参数运行setup并且teardown之后,才会再运行下一个参数。
      • 同一个测试case中,先执行最大的scope参数,将scope参数内的其他参数组合遍历执行完毕后,再用最大参数的其他scope参数继续遍历。
      • 使用最少的测试资源来将所有case遍历完全。但仍旧会先teardown上一个参数后,才会setup下一个参数。“参数化的模块作用域 modarg 资源导致了测试执行顺序的调整,从而实现了尽可能少的“活跃”资源。
    • subtests是 pytest 的一个内置 fixture
    • Teardown/Cleanup
      • 使用yield fixture
        • 执行顺序:fixtureA---yieldA---fixtureB---yieldB---teardownB--teardownA的顺序执行  
        • the first teardown code to run is from the right-most fixture
        • 在yieldA之前报错,则不再执行teardownA
        • 终止teardown时最先运行的是最后一个yield之后的teardown
        • 即使执行顺序正确,但也有可能会本身报错,故需要safe teardowns
      • 使用终止器(“finalizer” functions)
        • first-in-last-out,无论之前有没有报错,均会执行。
        • request.addfinalizer(empty_mailbox)
        • 终止时的顺序最先运行的,是最后调用request.addfinalizer的
      • Safe fixture structure
        • 要保证fixture 结构设计的原子性,即每一个都可独立建立,独立拆卸。隔离性、原子性、复用性、一致性都会更好。
        • 每个fixture都是一个独立的、可复用的模块。
        • 测试的真正动力和依赖来源是 pytest 的 fixture 系统,它通过参数注入的方式为测试提供所需的一切,而不是通过类的实例属性(self.xxx)来传递状态。
    • Fixture可以使用request对象检查测试用例的上下文环境
      • 使用request对象  def smtp_connection(request):  server = getattr(request.module, "smtpserver", "smtp.gmail.com")
    • 利用标记(Markers)将数据从测试函数“向上”传递到 fixture。
      • “使用标记向固件传递数据”的规则,本质上是开辟了一条从测试用例到其依赖环境的“上行”通信渠道。它通过 request对象和自定义标记,实现了测试函数对 fixture 行为的精细化控制,是构建高度灵活和可复用测试基础设施的强大工具。@pytest.mark.fixt_data(42)
    • Fixture可以直接return一个函数工厂而不只是单纯的一个值
    • 覆盖重写Fixture
      • 文件级别,子文件夹同名覆盖
      • 模块中重定义同名的fixture 函数
      • 测试函数参数化重定义fixture:  @pytest.mark.parametrize('username', ['directly-overridden-username'])
    • tmp_path  fixture
      • 为每一个测试函数提供一个临时文件夹
      • tmp_path_factory是一个session级别的fixture,可用于从任何其他 fixture 或测试中创建任意临时目录。
    • Monkeypatching动态补丁
      • 在程序运行时动态地修改或替换代码中的属性、方法、类甚至模块,以达到测试的目的。
      • 为了隔离外部依赖,使用场景有:mock函数、mock返回对象、全局patch、环境变量和字典的修改
    • doctests“文档即测试”
      • Doctest 是 Python 的一个标准库模块,它允许你将可执行的测试用例直接嵌入在代码的文档字符串(docstring)中。
      • 通过 pytest --doctest-modules命令,提供了更强大、更灵活的 doctest 执行能力。
    • 重跑失败的用例  re-run failed tests
      • --lf, --last-failed - to only re-run the failures.
      • --ff, --failed-first - to run the failures first and then the rest of the tests.
    • 缓存的使用及清除
      • pytestconfig.cache.get("example/value", None)   
      • pytest --cache-show
      • pytest --cache-clear
posted @ 2025-11-19 15:58  韩、饭饭  阅读(3)  评论(0)    收藏  举报