Pytest单元测试框架

第一章、pytest概述

Pytest is a framework that makes building simple and scalable tests easy. Tests are expressive and readable—no boilerplate code required. Get started in minutes with a small unit test or complex functional test for your application or library.

Pytest 是一个功能强大且易于使用的 Python 测试框架,用于编写和运行单元测试、集成测试和功能测试。以下是 Pytest 测试框架的一些主要特点和用法:

一、概述

  • pytest是一个非常成熟的python的单元测试框架,比unittest更灵活,容易上手
  • pytest可以和selenium,requests,appium等第三方库结合实现web自动化,借口自动化,app自动化
  • pytest可以实现测试用例的跳过以及rerun失败用例重试
  • pytest可以和allure生成非常美观的测试报告
  • pytest可以和jenkins持续集成
  • pytest有很多非常强大的插件,并且这些插件能够实现很多的实用的操作
    • pytest
    • pytest-html(生成html格式的自动化测试报告)
    • pytest-xdist(测试用例分布式执行,多CPU分支)
    • pytest-ordering(用于改变测试用例的执行顺序)
    • pytest-rerunfailures(用例失败后重跑)
    • allure-pytest(用于生成美观的测试报告)

1、简单易用

  • Pytest 的语法简单直观,容易上手。
  • 不需要大量的样板代码,测试用例可以以函数形式定义。

2、自动发现测试用例

  • Pytest 能够自动发现和收集项目中的测试用例,无需手动配置测试套件。

3、丰富的断言

  • Pytest 提供了丰富的断言(assert)机制,使得编写测试用例更加灵活和清晰。

4、参数化测试

  • 使用 @pytest.mark.parametrize 装饰器,可以轻松实现参数化测试,运行多组输入进行测试。

5、Fixture 支持

  • Pytest 支持 Fixture,用于提供测试用例所需的资源和环境。
  • Fixture 可以在测试用例运行前进行 setup 操作,在测试用例运行后进行 teardown 操作。

6、插件系统

  • Pytest 具有丰富的插件系统,可以通过插件扩展其功能,满足各种不同需求。

7、并行测试

  • Pytest 支持并行执行测试,提高测试效率。

二、安装pytest

环境:

python:3.10.11

pytest:7.0.1

1、安装

pip install pytest==7.0.1

2、验证安装

pytest --version

3、pytest文档

https://docs.pytest.org/en/latest/contents.html

三、默认命名规则

在执行pytest命令时,会自动从当前目录及子目录中寻找符合下述约束的测试函数来执行。

1、测试文件以test_ 开头(以 _test结尾也可以)

所有的单测文件名都需要满足test_*.py格式或*_test.py格式。

2、测试类以Test开头,并且不能带有init方法

在单测文件中,测试类以Test开头,并且不能带有 init 方法(注意:定义class时,需要以T开头,不然pytest是不会去运行该class的)

        Pytest 不支持在测试类中定义 __init__ 方法的主要原因是,Pytest 依赖于 Python 的反射机制来发现和收集测试用例。如果测试类定义了 __init__ 方法,那么在实例化测试类时,__init__ 方法会被调用,这可能会导致 Pytest 的测试用例收集出现意外行为或错误。

        具体来说,Pytest 会在执行测试之前,通过检查模块中的符合特定命名规则的函数和类来收集测试用例。如果测试类中定义了 __init__ 方法,Pytest 在实例化测试类时可能会出现意外的副作用,例如可能会影响到测试用例的状态或者执行顺序。

        另外,Pytest 更倾向于使用夹具(fixtures)来设置测试环境和共享资源,而不是依赖于 __init__ 方法来初始化测试类。使用夹具可以更灵活地管理测试环境,并且可以在需要时延迟初始化和清理操作,以提高测试的效率和可维护性。

        因此,为了避免潜在的问题,Pytest 不建议在测试类中定义 __init__ 方法。如果你需要在测试类中进行一些初始化操作,可以考虑使用 setup_methodteardown_method 等 Pytest 提供的装饰器方法,或者使用夹具来完成初始化和清理操作。

3、测试函数以test开头

在单测类中,可以包含一个或多个test_开头的函数。

四、pytest.ini 配置文件

Pytest 的配置文件名为 pytest.ini,是pytest单元测试框架的核心配置文件,它用于配置 pytest 的行为,可以在项目的根目录下创建该文件。

1、保存位置

pytest.ini配置文件必须和main主函数模式和命令行模式的工作目录在同一层级

pytest.ini 文件并不一定非要在项目的根目录下,它可以放置在项目的任何子目录中。Pytest 会在运行时自动搜索项目目录及其子目录中的 pytest.ini 文件,并应用其中的配置。

这意味着您可以根据项目的组织结构和需求,在不同的子目录下放置不同的 pytest.ini 文件,以覆盖或补充特定子目录中的测试配置。

通常情况下,将 pytest.ini 文件放置在项目的根目录下是最常见的做法,因为这样可以使配置文件更容易找到并应用于整个项目。但是,在某些情况下,如果您希望特定的配置仅适用于某个子目录及其子目录中的测试,那么将 pytest.ini 文件放置在该子目录下可能会更合适。

2、编码格式:utf-8

pytest.ini 文件的编码格式通常应该是 UTF-8,因为 UTF-8 是一种广泛支持的字符编码格式,在不同的操作系统和软件平台上都能够良好地兼容和显示。

虽然大多数文本编辑器默认会使用 UTF-8 编码保存文件,但有时可能会因为特定的编辑器或配置而使用其他编码格式,比如 ANSI、GBK 等。然而,建议您在编写 pytest.ini 文件时手动确保将其保存为 UTF-8 编码格式,以避免出现编码问题。

在大多数情况下,Pytest 不会对 pytest.ini 文件的编码格式做出严格的要求,它可以识别并解析多种不同的编码格式。然而,为了确保最佳的兼容性和稳定性,建议您遵循标准的编码规范,将 pytest.ini 文件保存为 UTF-8 编码格式。

3、常用配置选项

pytest.ini 文件是 Pytest 测试框架的配置文件,用于指定测试运行时的各种选项和参数。下面是 pytest.ini 文件的主要组成部分和常用的配置选项:

1、[pytest] 部分
  • addopts:用于指定额外的命令行选项和标记。
    • 例如,addopts = -v -s 表示在运行 pytest 时使用 -v(增加详细输出)和 -s(输出标准输出流)选项。
  • markers:用于定义自定义的标记(marker),以便在测试函数或测试模块中使用。
    • 例如,markers = slow: mark tests as slow 可以定义一个名为 slow 的标记,并提供其说明。
  • testpaths :选项指定测试文件或目录的路径
  • python_files:匹配测试文件的文件名模式。
  • python_classes:匹配测试类的名称模式。
  • python_functions:匹配测试函数的名称模式。
  • norecursedirs:排除指定的目录,不进行递归查找测试文件。
  • cache_dir :选项指定缓存文件的目录
2、[pytest-marks] 部分
  • 在此部分中,您可以定义标记(marker)的别名和说明。例如,slow: mark tests as slow 定义了 slow 标记的别名和说明。
3、[pytest-rerunfailures] 部分
  • reruns:指定在测试失败时重新运行测试的次数。
  • reruns_delay:指定在重新运行失败测试之间等待的时间(秒)。
4、[pytest-cov] 部分
  • cov:指定要计算代码覆盖率的模块或路径。
  • cov-report:指定生成代码覆盖率报告的格式。
5、[pytest-html] 部分
  • html_report_title:指定生成的 HTML 报告的标题。

        配置文件的使用可以让您在项目中统一管理 pytest 的配置选项,从而使测试运行更加方便和可控。您可以根据项目的需要,选择性地配置不同的选项,以满足不同的测试需求

4、作用

改变pytest默认的行为

5、运行的规则

不管是主函数的模式运行,还是命令行模式运行,都会去读取这个配置文件。

五、pytest执行顺序

pytest执行顺序总结:

  1.查询当前目录下的conftest.py

  2.查询当前目录下的pytest.ini文件,找到测试用例

  3.查询用例目录下的conftest.py文件。

  4.查询用例中是否有setup,teardown,setup_class,teardown_class

  5.执行测试用例,默认按照从上到下顺序执行,可用标记修改  @pytest.mark.run(order=1)

第二章、pytest运行方式

一、相关参数(适用于所有运行方式)

1、-v, --verbose

指定要在运行测试时使用的命令行选项。例如:pytest.main(["-v", "-s"]) 表示运行测试时增加详细输出和输出标准输出流。

  • 运行测试时增加详细输出。通常情况下,Pytest 只输出测试结果的摘要信息,使用 -v 选项可以使 Pytest 输出更加详细的信息,包括每个测试用例的执行结果、测试函数的名称等。
  • 显示更详细的信息
  • -vs可以一起使用

2、-s, --capture=no

  • 输出标准输出流。Pytest 默认会捕获测试过程中的标准输出流,使用 -s 选项可以禁止捕获标准输出流,从而在测试过程中实时看到打印到标准输出的信息。
  • 表示输出调试信息,包括print打印的信息

3、-k EXPRESSION

  • 通过表达式来选择要运行的测试用例。例如:-k test_login 表示运行名称中包含 "test_login" 的测试用例。

4、-m MARKEXPR

使用 -m 参数可以指定要运行的测试用例的标记(marker)。例如:pytest.main(["-m", "slow"]) 表示运行标记为 slow 的测试用例。

  • 通过标记(marker)来选择要运行的测试用例。例如:-m slow 表示运行标记为 "slow" 的测试用例。

5、-n 数字:执行并行测试的进程数

-n 是 Pytest 的一个命令行选项,用于指定并行运行测试的进程数。它允许您在多个进程中同时执行测试用例,从而加快测试的运行速度。下面是 -n 参数的详细说明:

  • 格式-n <num>
  • 含义:指定并行运行测试的进程数。<num> 表示要启动的进程数量。
  • 示例pytest -n 4
  • 默认值:默认情况下,Pytest 是单进程运行的,不使用并行处理。

        实际上,Pytest 的 -n 参数是指定并行运行测试的进程数,而不是线程数。每个指定的进程将会独立地运行测试用例,而不是在同一个进程中启动多个线程来执行测试。

        Pytest 使用多进程而不是多线程的原因之一是 Python 的全局解释器锁(Global Interpreter Lock,GIL)。由于 GIL 的存在,Python 解释器在同一时间只允许一个线程执行 Python 字节码,这就限制了多线程并发执行的效果。因此,为了充分利用多核处理器的优势,Pytest 使用多进程的方式来并行运行测试用例。

        因此,当您指定 -n <num> 时,<num> 是要启动的进程数量,每个进程将在独立的 Python 解释器中运行测试用例。

        使用 -n 参数可以充分利用多核处理器的优势,在较大的测试套件中可以显著提高测试的运行速度。然而,需要注意以下几点:

  1. 资源消耗:每个并行运行的进程都会消耗额外的系统资源(如 CPU 和内存),因此在选择进程数量时需要考虑系统的资源限制,避免过度消耗系统资源。

  2. 资源竞争:并行运行多个进程可能会导致资源竞争,特别是在测试中涉及到共享资源(如数据库、文件系统等)的情况下。确保您的测试用例是线程安全的,并且能够正确处理并发访问。

  3. 测试用例的并行性:某些测试用例可能会相互依赖或者涉及到共享状态,这些测试用例可能无法并行执行。在这种情况下,您可能需要使用 -n 参数的变体,如 -n auto,它可以自动检测系统的 CPU 核心数量,并设置适当的并行度。

        总之,-n 参数是 Pytest 中一个非常有用的选项,可以通过并行执行测试来提高测试的运行效率,但在使用时需要注意上述的注意事项,以确保测试的正确性和稳定性。

6、--reruns=数字:失败用例重跑

--reruns=<num> 是 Pytest 的一个命令行选项,用于指定在测试用例失败时重新运行失败的测试次数。它允许您在测试过程中对失败的测试用例进行多次重试,以提高测试的稳定性和可靠性。下面是 --reruns=<num> 参数的详细说明:

  • 格式--reruns=<num>
  • 含义:指定在测试用例失败时重新运行失败的测试的次数。<num> 表示重新运行的次数。
  • 示例pytest --reruns=3
  • 默认值:默认情况下,Pytest 不会对失败的测试用例进行重试。

        使用 --reruns=<num> 参数可以帮助您解决一些因为随机因素导致的偶发性失败的测试用例。例如,当测试涉及到网络请求、浏览器交互等外部因素时,偶发性的网络延迟或者浏览器加载时间可能会导致测试用例失败。通过多次重试这些失败的测试用例,可以降低因为随机性因素导致的测试失败的概率。

        需要注意的是,使用 --reruns=<num> 参数会增加测试的执行时间,因为每个失败的测试用例都会重新运行指定的次数。因此,在使用时需要权衡测试的稳定性和测试执行时间之间的关系,避免过度使用重试功能。

7、-x:遇到第一个测试用例失败时停止测试的执行

-x 是 Pytest 的一个命令行选项,用于在遇到第一个测试用例失败时停止测试的执行。它通常用于在测试套件中发现第一个失败的测试用例后立即停止测试,以便及早发现问题并减少不必要的测试时间。下面是 -x 参数的详细说明:

  • 格式-x
  • 含义:在遇到第一个测试用例失败时立即停止测试的执行。
  • 示例pytest -x
  • 默认值:默认情况下,Pytest 不会在遇到失败的测试用例时停止测试的执行,而是会继续执行剩余的测试用例。

        使用 -x 参数可以帮助您尽早发现测试中的问题,并提高测试的效率。当测试套件非常庞大时,如果遇到第一个失败的测试用例后立即停止测试,可以节省大量的测试时间,并且减少不必要的资源浪费。

        需要注意的是,使用 -x 参数会导致测试在第一个失败的测试用例后立即停止,因此可能会造成一些测试用例未执行。在某些情况下,您可能希望在发现失败后继续执行剩余的测试用例,以便收集更多的测试结果信息

8、--maxfail=数字

--maxfail=<num> 是 Pytest 的一个命令行选项,用于指定在测试执行过程中允许的最大失败测试用例数量。当达到指定的最大失败数时,Pytest 将停止执行剩余的测试用例。这个选项常用于控制测试执行的停止条件,以便在测试用例失败数达到一定阈值时停止测试的执行。下面是 --maxfail=<num> 参数的详细说明:

  • 格式--maxfail=<num>
  • 含义:指定在测试执行过程中允许的最大失败测试用例数量。<num> 表示最大失败数。
  • 示例pytest --maxfail=2
  • 默认值:默认情况下,Pytest 不会设置最大失败数,即所有的测试用例都会执行。

使用 --maxfail=<num> 参数可以帮助您控制测试执行的停止条件。当测试套件非常庞大时,设置最大失败数可以防止测试执行过程中出现大量的失败测试用例,从而减少不必要的测试时间和资源浪费。

需要注意的是,当达到最大失败数时,Pytest 将停止执行剩余的测试用例,并输出失败的测试用例的信息。您可以根据这些信息来定位和修复测试中的问题。

9、--html=path/to/report.html

使用生成报告的所有文件路径中不能包含任何中文字符,否则会报UnicodeEncodeError

--html 参数是 Pytest 的一个命令行选项,用于生成 HTML 格式的测试报告。通过指定该参数,Pytest 将会在测试运行结束后自动生成一个包含测试结果的 HTML 文件,该文件可以用于展示测试结果、分析测试覆盖率等。

以下是 --html 参数的详细说明:

  • 格式--html=path/to/report.html
  • 含义:指定生成 HTML 格式的测试报告,并指定报告的输出路径。
  • 示例pytest --html=report.html
  • 默认值:Pytest 默认不生成 HTML 格式的测试报告,需要通过该选项指定报告的输出路径才会生成。

使用 --html 参数可以方便地生成可视化的测试报告,帮助您更直观地查看测试结果、失败原因等信息。HTML 格式的测试报告通常包含测试用例的执行状态、运行时间、失败信息、错误堆栈等详细信息,并且可以在浏览器中方便地进行查看和分析。

需要注意的是,--html 参数生成的 HTML 报告可能会包含敏感信息,如测试用例名称、测试数据等,因此在分享或发布报告时需要注意处理敏感信息,确保信息安全。

二、主函数模式(相关语法同时适合于主函数模式和命令行模式)

可以在 Python 脚本中调用 Pytest 提供的 main() 函数来运行测试用例。例如:

import pytest

pytest.main()
pytest.main(["test_file.py", "-vs"])

1、默认执行当前目录及其子目录中所有符合命名条件的模块的用例

这将会执行当前目录及其子目录中所有以 test_*.py*_test.py 命名的文件中的测试用例

2、不传参数 

实际上,pytest.main() 方法默认情况下不需要传递任何参数,它会自动识别项目中的测试文件并执行测试。通常情况下,您可以简单地调用 pytest.main() 而不传递任何参数来运行测试。

这将会执行当前目录及其子目录中所有以 test_*.py*_test.py 命名的文件中的测试用例

如果需要传递参数,可以像在命令行中一样将参数作为字符串列表传递给 pytest.main() 方法。但通常情况下,不需要手动传递参数,除非您需要在代码中动态设置一些特定的参数。

3、传入参数(传入的参数同时适合于主函数模式和命令行模式)

pytest.main() 方法可以接受一个包含命令行参数的列表作为参数,这些参数将会被传递给 Pytest 运行测试用例的过程。常见的参数包括:

这些选项可以通过命令行传递给 Pytest,以控制测试的执行行为和输出结果。

1、测试文件或目录(默认为当前目录及其子目录下所有符合条件的文件)

指定要运行的测试文件或目录。例如:pytest.main(["test_file.py"])pytest.main(["test_directory/"])

2、通过nodeid指定用例运行

nodeid有模块名,分隔符,类名,方法名,函数名组成。

分隔符(::)

pytest.main(["-vs", "./demo2/test_demo21.py::test_func"])
pytest.main(["-vs", "./demo2/test_demo21.py::TestDemo21::test_01"])

总之,pytest.main() 方法可以接受任意数量和类型的参数,用于配置 Pytest 的运行行为,这些参数可以通过列表的形式传递给该方法。

三、命令行模式

可以在命令行中使用 pytest 命令来运行测试用例。例如:

pytest test_file.py  # 运行指定文件中的测试用例
pytest test_directory/  # 运行指定目录下所有文件中的测试用例
pytest  #运行当前工作目录下符合条件文件中的测试用例

四、通过读取pytest.ini配置文件运行

1、pytest.ini配置文件的优先级最高

pytest.ini配置文件的优先级大于主函数模式和命令行模式

当主函数模式或命令行模式的参数设置与pytest.ini配置文件不符时,以pytest.ini文件为准

[pytest]
# 命令行参数,多个参数间使用空格分隔
addopts = -vs
# 测试用例文件夹,可自己配置
testpaths = ./pytest运行用例顺序
# 配置测试搜索的模块文件名称
python_files = test*.py
# 配置测试搜索的测试类名
python_classes = Test*
# 配置测试搜索的测试函数(方法)名
python_functions = test_04

五、常见运行错误

1、UnicodeDecodeError解决方案

报错:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc3 in position 12: invalid continuation byte

解决方法:

用什么字符集进行编码,就得用什么字符集解码

pytest读取配置文件时一般采用系统默认的字符编码

例如:win11的默认编码为GBK,需要将pytest.ini文件修改为GBK编码

将pytest.ini配置文件的字符编码修改为 utf-8

第三章、pytest运行顺序

一、概述

unittest:按照ascii的大小来决定执行的顺序

pytest:默认从上到下依次执行

二、改变默认的执行顺序:使用mark标记

1、pytest.mark.run(order = 数字)

pytest-orderding插件的装饰器

pytest.mark.run 装饰器是 Pytest 提供的一个标记(marker),用于自定义测试用例的执行顺序。它可以帮助您指定测试函数的执行顺序,从而控制测试用例的执行流程。具体来说,pytest.mark.run 装饰器有以下参数:

  • order:指定测试函数的执行顺序。可以是一个整数值,表示测试函数的顺序。

您可以在测试函数上使用 pytest.mark.run(order=<number>) 来指定测试函数的执行顺序。例如:

import pytest

@pytest.mark.run(order=1)
def test_first():
    assert True

@pytest.mark.run(order=2)
def test_second():
    assert True

@pytest.mark.run(order=3)
def test_third():
    assert True

        在上面的示例中,test_first()test_second()test_third() 分别被指定了执行顺序为 1、2 和 3。当运行这些测试用例时,Pytest 将按照指定的顺序依次执行它们。

        需要注意的是,pytest.mark.run 装饰器仅在某些 Pytest 插件或者配置选项启用时才生效。默认情况下,Pytest 不保证测试用例的执行顺序是固定的,因此建议在编写测试用例时不依赖于执行顺序,以确保测试的可重复性和稳定性。

第四章、pytest分组执行

一、概述

在 Pytest 中,您可以使用标记(markers)来对测试进行分组,并可以在运行测试时选择性地运行特定标记的测试。

二、在pytest配置文件中定义标记

首先,您需要定义一些标记以用于分组。您可以在 pytest.ini 文件中定义标记,也可以在测试文件中使用 @pytest.mark 装饰器定义标记。例如,在 pytest.ini 文件中定义一些标记:

[pytest]
markers =
    slow: marks tests as slow
    smoke: marks tests as smoke tests

三、使用pytest.mark在用例中进行标记

然后,在测试文件中使用这些标记对测试进行分组:

import pytest

@pytest.mark.slow
def test_slow_function():
    assert True

@pytest.mark.smoke
def test_smoke_function():
    assert True

四、使用 -m 参数运行标记

接下来,您可以使用 -m 命令行选项来选择性地运行特定标记的测试。例如,要运行所有 slow 标记的测试,可以执行以下命令:

pytest -m slow

类似地,您也可以同时指定多个标记来运行特定标记的测试。例如,要同时运行所有 slowsmoke 标记的测试,可以执行以下命令:

pytest -m "slow or smoke"

使用标记进行分组可以帮助您组织和管理测试,使得在运行时可以灵活地选择性地运行特定组的测试。

第五章、pytest跳过用例

一、无条件跳过

1、@pytest.mark.skip

import pytest

class TestSkip(object):

    @pytest.mark.run(order=5)
    @pytest.mark.skip
    def test_05(self):
        print("test_05")

    @pytest.mark.skip(reason = "跳过用例")
    @pytest.mark.run(order=1)
    def test_01(self):
        print("test_01")

    @pytest.mark.run(order=4)
    def test_04(self):
        print("test_04")

    @pytest.mark.run(order=3)
    def test_03(self):
        print("test_03")

    @pytest.mark.run(order=2)
    def test_02(self):
        print("test_02")

2、@pytest.mark.skip(reason = "原因描述")

import pytest

@pytest.mark.skip(reason="test is not implemented yet")
def test_something():
    assert False

def test_another():
    assert True

        在这个示例中,test_something() 测试用例使用了 @pytest.mark.skip 装饰器,所以这个测试用例会被跳过。而 test_another() 测试用例没有被跳过,会正常执行。

二、有条件跳过

有条件跳过是指在测试用例中根据特定条件来决定是否跳过测试。您可以使用 @pytest.mark.skipif 装饰器来实现有条件跳过。

import pytest

@pytest.mark.skipif(condition, reason="Skipping this test due to condition")
def test_something():
    assert False

def test_another():
    assert True

        在这个示例中,test_something() 测试用例会根据 condition 变量的值来决定是否跳过测试。如果 condition 为真,则会跳        过测试用例;如果 condition 为假,则会正常执行测试用例。

        通过无条件跳过和有条件跳过,您可以根据不同的情况来灵活地管理测试用例的执行,从而满足测试的需求。

age = int(input())
@pytest.mark.skipif(age <= 18, reason = "未成年")
@pytest.mark.run(order=4)
def test_04(self):
   print("test_04")

第六章、前后置固件(夹具)

一、概述

        Pytest 中的前后置固件(Fixture)是一种机制,用于在测试用例执行前后进行初始化和清理操作。通过使用固件,您可以确保测试用例在执行时处于正确的环境中,并且可以在测试用例执行前后进行一些必要的准备和清理工作。

        Pytest 的固件机制基于装饰器 pytest.fixture 实现,您可以使用这个装饰器创建固件函数,并将其应用到测试用例中。固件函数可以返回测试用例所需的资源,例如数据库连接、文件句柄、模拟对象等。

1、三大固件的区别

  • setup teardown setup_class teardown_class
    • 作用于所有用例或者所有类
  • @pytest.fixture()
    • 既可以部分也可以全部前后置
  • conftest.py和@pytest.fixture()结合使用
    • 作用于全局的前后置

二、pytest.fixture 装饰器

1、概述

  pytest.fixture 装饰器是 Pytest 中用于创建测试 fixture(夹具)的装饰器。fixture 是一种用于提供测试运行所需资源的机制,可以用来初始化测试环境、准备测试数据、模拟外部服务等。使用 fixture 可以帮助确保测试用例在执行时处于预期的环境中,提高了测试的可维护性和可重复性。

        pytest.fixture装饰器是pytest测试框架中的一个重要特性,用于定义测试用例中的共享资源或者测试环境的初始化和清理操作。通过使用fixture装饰器,我们可以在测试用例中方便地使用这些共享资源。

        fixture装饰器可以应用在函数、类或者模块级别上,它的作用是将被装饰的函数或者方法转变为一个fixture对象。fixture对象可以在测试用例中作为参数进行调用,pytest会自动根据参数名匹配相应的fixture对象,并将其传递给测试用例。

        fixture装饰器可以接受一些参数来定制其行为,例如scope参数用于指定fixture的作用域,autouse参数用于指定是否自动使用fixture等。

        使用 pytest.fixture 装饰器创建 fixture 很简单,只需要将一个函数标记为 fixture 并返回所需的资源即可。

2、自动调用fixture

1、传入一个fixture函数

直接将fixture函数作为参数传入测试函数中

import pytest

@pytest.fixture(scope="function")
def my_setup():
    # 初始化操作
    print("\nStarting")
    yield
    # 清理操作
    print("\nCleaning")

def setup():
    print("\n模块开始")

def teardown():
    print("\n模块结束")

def test_example01(my_setup):
    # 使用setup fixture
    assert 1 + 1 == 2

def test_example02(my_setup):
    # 使用setup fixture
    assert 1 + 1 == 2

if __name__ == '__main__':
    pytest.main()
    """
    模块开始
    Starting
    PASSED
    Cleaning
    Starting
    PASSED
    Cleaning
    模块结束
    """
2、传入多个fixture函数 

传入多个fixture函数时,fixture函数的执行顺序以传入的顺序为准,先传入的先执行,后传入的后执行。

import pytest


# fixture函数(类中) 作为多个参数传入
@pytest.fixture()
def login():
    print("\n打开浏览器")
    a = "account"
    return a


@pytest.fixture()
def logout():
    print("\n关闭浏览器")


class TestLogin:
    # 传入lonin fixture
    def test_001(self, login):
        print("001传入了loging fixture")
        assert login == "account"

    # 传入logout fixture
    def test_002(self, logout):
        print("002传入了logout fixture")

    def test_003(self, login, logout):
        print("003传入了两个fixture")

    def test_004(self):
        print("004未传入仍何fixture哦")


if __name__ == '__main__':
    pytest.main()
    """
    打开浏览器
    001传入了loging fixture
    PASSED
    
    关闭浏览器
    002传入了logout fixture
    PASSED
    
    打开浏览器
    关闭浏览器
    003传入了两个fixture
    PASSED
    
    004未传入仍何fixture哦
    PASSED
    """

3、手动调用fixture

需要使用 @pytest.mark.usefixtures("fixture函数名")修饰测试函数

import pytest

@pytest.fixture(scope="function")
def my_setup():
    # 初始化操作
    print("\nStarting")
    yield
    # 清理操作
    print("\nCleaning")

def setup():
    print("\n模块开始")

def teardown():
    print("\n模块结束")

@pytest.mark.usefixtures("my_setup")
def test_example01():
    # 使用setup fixture
    assert 1 + 1 == 2
@pytest.mark.usefixtures("my_setup")
def test_example02():
    # 使用setup fixture
    assert 1 + 1 == 2

if __name__ == '__main__':
    pytest.main()
    """
    模块开始
    Starting
    PASSED
    Cleaning
    Starting
    PASSED
    Cleaning
    模块结束
    """

4、fixture接收返回值

如果说fixtrue有通过return或vield返回值的话,那么可以把这个值传递到测试用例当中。值是通过固件的名字传递的

import pytest

@pytest.fixture
def some_data1():
    return [1, 2, 3]

@pytest.fixture()
def some_data2():
    print("\n开始返回数据")
    yield 666
    print("\n已经返回数据")

def test_function01(some_data1):
    assert some_data1 == [1, 2, 3]

def test_function02(some_data2):
    print(f"接收的返回值:{some_data2}")

if __name__ == '__main__':
    pytest.main()
    """
    test_接收返回值.py::test_function01 PASSED
    test_接收返回值.py::test_function02 
    开始返回数据
    接收的返回值:666
    PASSED
    已经返回数据
    """

5、fixture做为参数传入时,会在执行函数之前执行该fixture函数

import pytest


# fixture函数(类中) 作为多个参数传入
@pytest.fixture()
def login():
    print("\n打开浏览器")
    a = "account"
    return a


@pytest.fixture()
def logout():
    print("\n关闭浏览器")


class TestLogin:
    # 传入lonin fixture
    def test_001(self, login):
        print("001传入了loging fixture")
        assert login == "account"

    # 传入logout fixture
    def test_002(self, logout):
        print("002传入了logout fixture")

    def test_003(self, login, logout):
        print("003传入了两个fixture")

    def test_004(self):
        print("004未传入仍何fixture哦")


if __name__ == '__main__':
    pytest.main()
    """
    打开浏览器
    001传入了loging fixture
    PASSED
    
    关闭浏览器
    002传入了logout fixture
    PASSED
    
    打开浏览器
    关闭浏览器
    003传入了两个fixture
    PASSED
    
    004未传入仍何fixture哦
    PASSED
    """

6、fixture互相调用

import pytest

# fixtrue作为参数,互相调用传入
@pytest.fixture()
def account():
    a = "account"
    print("\n第一层fixture")
    return a


# Fixture的相互调用一定是要在测试类里调用这层fixture才会生次,普通函数单独调用是不生效的
@pytest.fixture()
def login(account):
    print("\n第二层fixture")
    return


class TestLogin:
    def test_1(self, login):
        print("直接使用第二层fixture,返回值为{}".format(login))

    def test_2(self, account):
        print("只调用account fixture,返回值为{}".format(account))


if __name__ == '__main__':
    pytest.main()
    """
    第一层fixture
    第二层fixture
    直接使用第二层fixture,返回值为None
    PASSED
    
    第一层fixture
    只调用account fixture,返回值为account
    PASSED
    """

5、scope:作用域

默认作用域为function级别

在 Pytest 中,scope 参数用于控制 fixture 的作用范围,即 fixture 的生命周期。scope 参数可以指定为以下四种值之一:

1、"function"(函数级别)

每个测试函数调用时都会创建一个新的 fixture 实例,并在测试函数执行完毕后销毁。这是默认的作用范围,适用于大多数情况。

既适用于函数又适用于方法

import pytest

@pytest.fixture(scope="function")
def my_fixture():
    print("\nSetup")
    yield
    print("\nTeardown")

# def test_example1(my_fixture):
#     print("Test 1")
#
# def test_example2(my_fixture):
#     print("Test 2")

class TestFixtureScopeFunction():
    def test_example1(self, my_fixture):
        print("Test 1")

    def test_example2(self, my_fixture):
        print("Test 2")

if __name__ == '__main__':
    pytest.main()
    """
    Setup
    Test 1
    PASSED
    Teardown
    Setup
    Test 2
    PASSED
    Teardown
    """

2、"class"(类级别)

每个测试类调用时创建一个 fixture 实例,并在测试类的所有测试方法执行完毕后销毁。如果多个测试方法需要共享相同的资源,可以使用该作用范围。

import pytest

@pytest.fixture(scope="class")
def my_fixture():
    print("\nSetup_class")
    yield
    print("\nTeardown_class")

class TestExample:
    def test_example1(self, my_fixture):
        print("Test 1")

    def test_example2(self, my_fixture):
        print("\nTest 2")

if __name__ == '__main__':
    pytest.main()
    """
    Setup_class
    Test 1
    Test 2
    Teardown_class
    """

测试类下面只有一些测试方法使用了fixture函数名,这样的话,fixture只在该class下第一个使用fixture函数的测试用例位置开始算,后面所有的测试用例执行前只执行一次。而该位置之前的测试用例就不管。

import pytest
@pytest.fixture(scope='class')
def login():
    a = '123'
    print("\n输入账号密码登陆")
    yield
    print("\n退出账号密码登陆")

class TestLogin:
    def test_1(self):
        print("\n用例1")

    def test_2(self, login):
        print("用例2")

    def test_3(self, login):
        print("\n用例3")

    def test_4(self):
        print("\n用例4")

if __name__ == '__main__':
    pytest.main()
    """
    用例1
    PASSED
    
    输入账号密码登陆
    用例2
    PASSED
    
    用例3
    PASSED
    
    用例4
    PASSED
    退出账号密码登陆
    """
3、"module"(模块级别)

在整个测试模块运行期间只创建一个 fixture 实例,并在测试模块执行完毕后销毁。适用于多个测试函数需要共享相同的资源,且这些资源在测试模块级别不会发生变化的情况。

import pytest

@pytest.fixture(scope="module")
def my_fixture():
    print("\nSetup_module")
    yield
    print("\nTeardown_module")

def test_01(my_fixture):
    print("Test 1")

def test_02(my_fixture):
    print("Test 2")

class TestFixtureScopeModule():
    def test_03(self, my_fixture):
        print("Test 3")

    def test_04(self, my_fixture):
        print("Test 4")

if __name__ == '__main__':
    pytest.main()
    """
    Setup_module
    Test 1
    Test 2
    Test 3
    Test 4
    Teardown_module
    """

与class相同,只从.py文件开始引用fixture的位置生效 

import pytest
# fixture scope = 'module'
@pytest.fixture(scope='module')
def login():
    print("fixture范围为module")


def test_01():
    print("用例01")


def test_02(login):
    print("用例02")


class TestLogin():
    def test_1(self):
        print("用例1")

    def test_2(self):
        print("用例2")

    def test_3(self):
        print("用例3")

if __name__ == '__main__':
    pytest.main()
    """
    用例01
    
    fixture范围为module
    用例02
    
    用例1
    
    用例2
    
    用例3
    """
4、"session"(会话级别)

在整个测试会话运行期间只创建一个 fixture 实例,并在测试会话结束后销毁。适用于整个测试套件需要共享相同的资源,且这些资源在测试会话级别不会发生变化的情况。

import pytest

@pytest.fixture(scope="session")
def my_fixture():
    print("\nSetup_session")
    yield
    print("\nTeardown_session")

def test_01(my_fixture):
    print("Test 1")

def test_02(my_fixture):
    print("Test 2")

class TestFixtureScopeSession():
    def test_03(self, my_fixture):
        print("Test 3")

    def test_04(self, my_fixture):
        print("Test 4")

if __name__ == '__main__':
    pytest.main()
    """
    Setup_session
    Test 1
    Test 2
    Test 3
    Test 4
    Teardown_session
    """

6、autouse:用于指定是否自动应用 fixture

autouse 参数是 pytest.fixture 装饰器中的一个可选参数,用于指定是否自动应用 fixture。当设置为 True 时,fixture 将会自动应用于测试函数,而不需要显式地在测试函数中声明 fixture 的使用。

下面是 autouse 参数的一些详解:

1、默认值:False

autouse 参数的默认值为 False,即默认情况下,fixture 不会自动应用于测试函数,需要通过在测试函数的参数列表中显式声明来使用 fixture。

import pytest

@pytest.fixture()
def my_fixture():
    print("\n前置夹具")
    yield 666
    print("\n后置夹具")

def test_01(my_fixture):
    print("test_01")

if __name__ == '__main__':
    pytest.main()
    """
    前置夹具
    test_01
    PASSED
    后置夹具
    """
2、设置为 True

autouse 参数设置为 True,可以使得 fixture 在每个测试函数中自动应用,而无需显式声明。

import pytest

@pytest.fixture(autouse=True)
def my_fixture1():
    print("\n前置夹具1")
    yield 666
    print("\n后置夹具1")

@pytest.fixture(autouse=True)
def my_fixture2():
    print("\n前置夹具2")
    yield 666
    print("\n后置夹具2")

def test_01():
    print("test_01")

if __name__ == '__main__':
    pytest.main()
    """
    前置夹具1

    前置夹具2
    test_01
    PASSED
    后置夹具2
    
    后置夹具1
    """
3、自动应用的顺序

如果存在多个 autouse 设置为 True 的 fixture,它们的执行顺序与定义顺序一致,即先定义的 fixture 先执行,后定义的 fixture 后执行。

4、适用场景

autouse 参数适用于那些对于每个测试函数都需要自动执行的 fixture,比如一些全局配置、初始化操作等。

5、注意事项

使用 autouse=True 可能会导致 fixture 在某些情况下过度使用,因此需要谨慎使用,确保不会影响测试的准确性和可维护性。

总的来说,autouse 参数是 pytest.fixture 装饰器的一个可选参数,用于指定是否自动应用 fixture。通过合理设置 autouse 参数,可以简化测试代码,提高代码的可读性和可维护性。

7、params:参数化

Fixture的可选形参列表,支持列表传入
默认None,每个param的值
fixture都会去调用执行一次,类似for循环

可与参数ids一起使用,作为每个参数的标识,详见ids
被Fixture装饰的函数要调用是采用:Request.param(固定写法)

params 参数是 pytest.fixture 装饰器的一个可选参数,用于实现 fixture 的参数化。通过在 params 参数中指定多个参数值,可以使得同一个 fixture 在不同的测试函数中使用不同的参数值。

下面是 params 参数的一些详解:

1、可传入参数类型

params 参数可以是一个包含多个值的可迭代对象,比如列表、元组或集合。每个值都会作为一个参数值传递给 fixture 函数。

支持:列表[ ],元祖( ),字典列表[{}, {}, {}],字典元祖({}, {}, {})

2、语法

固定写法:在pytest中有一个内建的fixture叫做request,代表fixture的调用状态。request有一个字段param,可以使用类似于@pytest.fixture(params = tasks_list)的方式,在fixture中使用request.param的方式作为返回值供测试函数调用,其中task_list 中包含多少元素,该fixture就会被调用几次,分别作用在每个用到的测试函数上。

import pytest

@pytest.fixture(params=[1, 2, {"a": 1, "b": 2}, (4, 5, 6)])
def demo(request):
    return request.param

def test_demo(demo):
    print(f"\n值 --> {demo}")
    """
    值 --> 1
    值 --> 2
    值 --> {'a': 1, 'b': 2}
    值 --> (4, 5, 6)
    """
3、示例

pytest.fixture 装饰器是 Pytest 中用于定义测试夹具的功能。夹具可以在测试函数中被调用,以提供预置的数据或者执行一些准备工作。params 参数是 @pytest.fixture 装饰器的一个关键参数,它允许你为夹具定义多组参数,从而使测试函数可以多次运行,每次使用不同的参数。

下面是一个简单的例子来说明 params 参数的使用:

import pytest

@pytest.fixture(params=[1, 2, 3])
def my_fixture(request):
    return request.param

def test_my_fixture(my_fixture):
    assert my_fixture > 0

在这个例子中,my_fixture 是一个夹具,它有三个不同的参数值:1、2、3。test_my_fixture 函数接受 my_fixture 夹具作为参数,并断言该夹具的值大于 0。

当运行这个测试时,test_my_fixture 函数将会运行三次,每次使用一个不同的参数值来执行。这就是 params 参数的作用。

你也可以使用不同的方式来指定参数,例如使用元组的列表或者字典。这允许你定义更加复杂的参数组合。

import pytest

@pytest.fixture(params=[(1, 2), (3, 4)])
def my_fixture(request):
    return request.param

def test_my_fixture(my_fixture):
    x, y = my_fixture
    assert x + y == 3

在这个例子中,my_fixture 夹具有两个参数组合:(1, 2) 和 (3, 4)。test_my_fixture 函数将分别使用这两组参数来运行两次测试。

8、ids:与params联合使用,给参数命名

用例标识ID
与params配合使用,一对一关系

举个栗子:
未配置ids之前,用例:

配置了IDS后:

ids 参数是 pytest.fixture 装饰器的另一个关键参数,用于为夹具的不同参数值指定自定义标识符。这在测试报告中可以提供更清晰的夹具标识,使得当测试失败时更容易理解失败的原因。

下面是一个使用 ids 参数的示例:

import pytest

@pytest.fixture(params=[1, 2, 3], ids=["param_a", "param_b", "param_c"])
def my_fixture(request):
    return request.param

def test_my_fixture(my_fixture):
    assert my_fixture > 0

在这个例子中,my_fixture 夹具有三个不同的参数值:1、2、3。通过 ids 参数,为这些参数值指定了自定义的标识符:"param_a"、"param_b" 和 "param_c"。当测试运行时,测试报告中会使用这些标识符来标识每次测试的参数值,而不是默认的数字索引。

这样的话,如果测试失败,你就能更容易地理解失败是由哪个参数值引起的,因为测试报告会显示使用了哪个标识符。这对于理解测试失败的原因非常有帮助,特别是当参数值比较复杂或者多个参数时。

9、name参数:对fixture函数重命名

fixture的重命名
通常来说使用 fixture 的测试函数会将 fixture 的函数名作为参数传递,但是 pytest 也允许将fixture重命名
如果使用了name,那只能将name传如,函数名不再生效
调用方法:@pytest.mark.usefixtures(‘fixture1’,‘fixture2’)
举栗:

import pytest
@pytest.fixture(name="new_fixture")
def test_name():
    pass
    
#使用name参数后,传入重命名函数,执行成功
def test_1(new_fixture):
    print("使用name参数后,传入重命名函数,执行成功")

#使用name参数后,仍传入函数名称,会失败
def test_2(test_name):
    print("使用name参数后,仍传入函数名称,会失败")

三、setup相关固件

1、setup

pytest7.0.1中:

setup和teardown要是在类中,就相当于是setup_method和teardown_method,优先执行set_method和teardown_method

  • 作用范围:可以灵活控制作用范围,可以是模块级别、函数级别、类级别或方法级别。
  • 调用时机:可以根据使用的方式来确定调用时机,可以在测试函数、测试类或测试模块中使用。
  • 定义方式:即可定义在模块中,又可定义在类中,使用 pytest.fixture 装饰器定义函数,可以根据需要自定义函数名。
    • 定义在类中:相当于setup_method
    • 定义在模块中:相当于setup_module

2、setup_method

  • 作用范围:方法级别。
  • 调用时机:在测试类中的每个测试方法执行前调用。
  • 定义方式作为测试类的方法定义,方法名必须为 setup_method

3、setup_class

  • 作用范围:类级别。
  • 调用时机:在测试类中的所有测试方法执行前调用,仅执行一次。
  • 定义方式作为测试类的方法定义,方法名必须为 setup_class

4、setup_function

  • 作用范围:函数级别。
  • 调用时机:在每个测试函数执行前调用。
  • 定义方式
    • setup_function只能定义在类外,不能定义在类中
    • 使用 pytest 提供的 pytest.fixture 装饰器定义函数,函数名可以自定义。

5、setup_module

  • 作用范围:模块级别。
  • 调用时机:在模块中的所有测试用例执行前调用,仅执行一次。
  • 定义方式:使用 pytest 提供的 pytest.fixture 装饰器定义函数,函数名必须为 setup_module
    • 必须定义在模块中,不能定义在类中

6、联系

  • setup_functionsetup_classsetup_method 是针对不同级别的测试对象(函数、类和方法)进行初始化操作的。
  • setup_module 则是在整个测试模块执行前执行的初始化操作。
  • setup 则可以根据需要进行灵活的初始化操作,可以控制初始化的作用范围。

四、teardown相关固件

1、teardown

  • 作用范围:可以灵活控制作用范围,可以是模块级别、函数级别、类级别或方法级别。
  • 调用时机:可以根据使用的方式来确定调用时机,可以在测试函数、测试类或测试模块中使用。
  • 定义方式:定义为 pytest.fixture 装饰器的函数,可以根据需要自定义函数名。
    • 定义在类中:相当于teardown_method
    • 定义在模块中:相当于teardown_module

2、teardown_method

  • 作用范围:方法级别。
  • 调用时机:在测试类中的每个测试方法执行后调用。
  • 定义方式定义为测试类中的方法,方法名必须为 teardown_method

3、teardown_class

  • 作用范围:类级别。
  • 调用时机:在测试类中的所有测试方法执行后调用,仅执行一次。
  • 定义方式定义为测试类中的方法,方法名必须为 teardown_class

4、teardown_function

  • 作用范围:函数级别。
  • 调用时机:在每个测试函数执行后调用。
  • 定义方式
    • setup_function只能定义在类外,不能定义在类中
    • 定义为模块中的函数,函数名必须为 teardown_function

5、teardown_module

  • 作用范围:模块级别。
  • 调用时机:在模块中的所有测试用例执行后调用,仅执行一次。
  • 定义方式:定义为模块中的函数,函数名必须为 teardown_module
    • 必须定义在模块中,不能定义在类中

6、联系

  • teardown_functionteardown_classteardown_method 是针对不同级别的测试对象(函数、类和方法)进行清理操作的。
  • teardown_module 则是在整个测试模块执行后执行的清理操作。
  • teardown 则可以根据需要进行灵活的清理操作,可以控制清理的作用范围。

五、conftest.py文件

conftest.py文件详解

1、概述

conftest.py 文件是 Pytest 中一个特殊的文件,用于定义测试项目中的共享夹具(fixtures)、插件、hook 函数等。当 Pytest 运行测试时,它会自动查找项目中的 conftest.py 文件,并加载其中定义的内容。

  conftest.py文件是Pytest框架里面一个很重要的东西,它可以在这个文件里面编写Fixture函数,这个Fixture函数的作用,就相当于Unittest框架里面的setup()前置函数和teardown()后置函数,虽然Pytest框架也有setup()前置函数和teardown()后置函数,但是在实际工作中没必要写在测试用例文件中,直接写在conftests.py里面就好了,Pytest框架会自动去找conftest.py文件里面的东西,这样更灵活。
        总结:在实际工作中,通常conftest.py@pytest.fixture()结合使用,实现全局的前后置应用。 

2、常见用途 

下面是一些 conftest.py 文件的常见用途:

  1. 定义共享夹具(fixtures):你可以在 conftest.py 文件中定义多个夹具,这些夹具可以被项目中的所有测试模块使用。这样可以避免在每个测试模块中重复定义相同的夹具。

  2. 自定义命令行选项:通过定义 pytest_addoption 函数,你可以在 conftest.py 文件中添加自定义的命令行选项,以扩展 Pytest 的命令行功能。

  3. 注册插件:如果你编写了自己的 Pytest 插件,可以在 conftest.py 文件中注册这些插件,以便在测试项目中使用。

  4. 定义钩子函数:可以在 conftest.py 文件中定义 Pytest 的钩子函数,以响应测试运行过程中的不同事件,例如在测试开始或结束时执行特定的操作。

  5. 共享测试数据:有时候你可能需要在测试项目中共享一些测试数据,你可以在 conftest.py 文件中定义这些数据,并在测试中引用它们。

总的来说,conftest.py 文件提供了一个集中管理测试项目配置和共享资源的地方,它使得测试代码更加模块化、可维护,并且能够更好地组织和重用测试代码。

3、conftest.py文件特点

  • 所有同目录测试文件运行前都会执行conftest.py文件 不需要import导入
  • conftest.py,pytest用例会自动识别该文件,放到项目的根目录下就可以全局目录调用了,如果放到某个package下,那就在该package内有效,可有多个conftest.py
  • conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
  • 可以跨.py文件调用,有多个.py文件调用时,可让conftest.py只调用了一次fixture,或调用多次fixture

4、conftest.py的注意事项

  1. conftest.py文件是单独存放的一个夹具(Fixture)配置文件,名称是不能更改。
  2. 可以在不同的.py文件中使用同一个Fixture函数。
  3. 原则上conftest.py需要和运行的用例放到同一目录中,并且有__init__.py文件,那么conftest.py作用于整个目录。
  4. 如果希望Fixture(夹具)共享给所有测试,则可以把conftest.py文件放在测试框架的根目录下。
  5. conftest.py文件中的内容,不需要做任何的import导入的操作就能够读取到,因为Pytest用例会自动查找。
  6. 建议把测试项目的所有Fixture都存放在conftest.py文件中,把conftest.py当作Pytest的Fixture仓库。

5、使用案例

假设我们有一个测试项目,其中包含多个测试模块,每个模块都需要使用到相同的夹具(fixture)。我们可以使用 conftest.py 文件来定义这些共享的夹具。

假设我们的测试项目包含以下目录结构:

tests/
├── conftest.py
├── test_module1.py
└── test_module2.py

现在,我们在 conftest.py 文件中定义一个名为 login_fixture 的夹具,用于模拟用户登录功能。这个夹具将在每个测试模块中都可用。

# conftest.py

import pytest

@pytest.fixture
def login_fixture():
    # 模拟用户登录操作
    user = "test_user"
    password = "test_password"
    # 假设这里有一些登录逻辑
    yield {"username": user, "password": password}
    # 在测试运行完后,可以在这里添加清理代码,例如退出登录操作
    # 如果有必要的话
    print("User logged out.")

conftest.py 文件中定义了一个名为 login_fixture 的夹具,它模拟了用户登录过程。在夹具的定义中,使用了 yield 关键字来标记夹具的起始和结束点。在 yield 关键字之前的代码用于夹具的初始化操作,在 yield 关键字之后的代码用于夹具的清理操作。

现在,我们可以在测试模块中使用这个夹具。例如,在 test_module1.py 中:

# test_module1.py

def test_login(login_fixture):
    user = login_fixture["username"]
    password = login_fixture["password"]
    # 在这里编写测试逻辑,使用登录的用户
    assert user == "test_user"
    assert password == "test_password"

test_module2.py 中也可以使用相同的夹具,无需重新定义。

这样,通过 conftest.py 文件,我们可以在测试项目中定义共享的夹具,并在多个测试模块中重复使用,避免了重复编写相同的代码,提高了测试代码的可维护性。

6、不同位置conftest.py文件的优先级

其作用范围是当前目录包括子目录里的测试模块。

  • 比如在测试框架的根目录创建conftest.py文件,文件中的Fixture的作用范围是所有测试模块。
  • 比如在某个单独的测试文件夹里创建conftest.py文件,文件中Fixture的作用范围,就仅局限于该测试文件夹里的测试模块。
  • 该测试文件夹外的测试模块,或者该测试文件夹外的测试文件夹,是无法调用到这个conftest.py文件中的Fixture。
  • 如果测试框架的根目录和子包中都有conftest.py文件,并且这两个conftest.py文件中都有一个同名的Fixture,实际生效的是测试框架中子包目录下的conftest.py文件中配置的Fixture。

六、conftest.py中Fixture(夹具)的作用域

Fixture的scope参数也适用conftest.py文件中Fixture的特性:

  • conftest.py文件中Fixture的scope参数为session,那么所有的测试文件执行前(后)执行一次conftest.py文件中的Fixture。
  • conftest.py文件中Fixture的scope参数为module,那么每一个测试文件执行前(后)都会执行一次conftest.py文件中Fixture。
  • conftest.py文件中Fixture的scope参数为class,那么每一个测试文件中的测试类执行前(后)都会执行一次conftest.py文件中Fixture。
  • conftest.py文件中Fixture的scope参数为function,那么所有文件的测试用例执行前(后)都会执行一次conftest.py文件中Fixture。

七、总结:

  • Pytest框架中的setup()/teardown()函数,setup_class()/teardown_class()函数。他们是作用于所有用例或者所有类的。
  • @pytest.fixtrue()的作用域是既可以部分用例,也可以全部用例的前后置。
  • conftest.py文件和@pytest.fxtrue()装饰器结合使用,作用于全局用例的前后置。

第七章、生成allure报告

一、概述

Allure 是一个用于生成漂亮、交互式测试报告的开源测试报告框架。它提供了丰富的功能,能够帮助测试团队更好地理解测试结果、跟踪缺陷,并与团队共享测试执行的情况。

下面是一些 Allure 报告的主要特点和功能:

  1. 可读性强:Allure 报告以图形化的方式呈现测试结果,包括测试用例的执行状态、用例名称、描述、步骤、参数等信息,使得测试结果更易于理解和分析。

  2. 丰富的信息展示:Allure 报告提供了丰富的信息展示,包括用例执行时间、失败截图、日志输出等,帮助测试人员更全面地了解测试过程中发生的情况。

  3. 交互式报告:Allure 报告是交互式的,用户可以通过图形界面轻松地查看测试用例、过滤结果、跟踪缺陷等。

  4. 多语言支持:Allure 支持多种编程语言和测试框架,包括 Java、Python、C# 等,并且可以与各种 CI/CD 工具集成,方便地集成到自动化测试流程中。

  5. 自定义扩展:Allure 提供了丰富的 API 和插件系统,允许用户根据自己的需求进行定制和扩展,满足不同团队的特定需求。

  6. 历史趋势分析:Allure 报告还可以提供历史趋势分析功能,帮助团队跟踪测试执行结果的变化,及时发现和解决问题。

总的来说,Allure 报告是一个功能强大、易于使用的测试报告框架,可以帮助测试团队提高测试结果的可视化程度,加快缺陷的定位和修复速度,提高测试工作的效率和质量。

二、部署环境

1、安装allure-pytest 2.13.2

pip install allure-pytest == 2.13.2

2、安装jdk,部署java环境

部署jdk1.8.0_321

3、下载allure,并配置环境变量

Releases · allure-framework/allure2 · GitHub

三、生成报告

生成临时的ison报告,在pytestini文件里面加入以下内容:
addopts=-vs --alluredir=./temps --clean-alluredir

在main文件输入:os.system("allure  generate  ./temps  -o  ./reports   --clean")

第八章、pytest参数化测试@pytest.mark.parametrize

一、概述

        参数化测试是一种软件测试技术,它允许测试人员在同一个测试用例中使用不同的输入参数进行测试。通常情况下,相同的测试逻辑会被重复执行,但是使用不同的输入参数值。这样可以更全面地覆盖不同的测试场景,减少测试代码的重复编写,并提高测试代码的可维护性。

参数化测试通常包括以下几个步骤:

  1. 定义测试用例:首先,定义一个测试用例或测试函数,用于测试特定的功能或组件。

  2. 指定参数化数据:确定需要参数化的测试数据,并将其组织成数据结构,通常是一个列表、元组或字典。这些数据结构中的每个元素代表一组参数值。

  3. 编写测试逻辑:编写测试逻辑或测试函数,将参数作为输入,并执行相应的测试操作。

  4. 运行参数化测试:运行测试时,测试框架会根据指定的参数化数据,多次执行相同的测试逻辑,每次使用不同的参数值。

  5. 分析测试结果:分析测试结果,查看每组参数值的测试结果,以及哪些参数值导致了测试失败。

参数化测试通常用于以下几种情况:

  • 覆盖不同的测试场景:当需要测试一个功能在不同条件下的行为时,可以使用参数化测试来覆盖不同的测试场景,以确保功能的正确性。

  • 减少重复编写测试用例:当需要测试多组类似的场景时,可以使用参数化测试来减少重复编写相似的测试用例,提高测试代码的复用性和可维护性。

  • 快速定位问题:通过参数化测试,可以快速定位哪组参数值导致了测试失败,从而更容易地定位和解决问题。

总的来说,参数化测试是一种非常实用的测试技术,可以帮助测试人员更高效地编写和管理测试代码,提高测试覆盖率和质量。

二、快速入门

1、介绍

以前使用unittest的时候,我们参数化是用@ddt,但是使用pytest之后,pytest内置了pytest.mark.parametrize装饰器来对测试函数的参数进行参数化

2、代码示例

import pytest


@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
    assert eval(test_input) == expected

3、运行结果

4、结果分析

我们通过@parametrize装饰器定义了三个不同的(test_input,expected)的元祖,然后test_eval函数就依次使用了他们三次.这就是一个简单的参数化使用案例.

5、案例

@pytest.mark.parametrize 装饰器是 Pytest 中用于参数化测试的功能之一。它允许你在测试函数中定义多组参数,并自动运行测试函数多次,每次使用不同的参数组合。

下面是一个简单的例子来说明 @pytest.mark.parametrize 装饰器的使用:

import pytest

@pytest.mark.parametrize("input1, input2, expected", [
    (1, 1, 2),
    (2, 3, 5),
    (10, -5, 5)
])
def test_addition(input1, input2, expected):
    result = input1 + input2
    assert result == expected

        在这个例子中,test_addition 函数是一个加法测试函数,它接受两个输入参数 input1input2,并断言它们的和是否等于预期的结果 expected@pytest.mark.parametrize 装饰器的参数指定了测试函数的参数名称,以及多组参数值组成的列表。

        在这个装饰器中,每个元组代表了一组参数值,例如 (1, 1, 2) 表示 input1=1, input2=1, expected=2,而 (2, 3, 5) 表示 input1=2, input2=3, expected=5,依此类推。

        当运行测试时,Pytest 将自动运行 test_addition 函数三次,每次使用一个不同的参数组合。这样可以更方便地编写和管理测试用例,特别是当需要测试多组参数时。并且,如果测试失败,Pytest 会显示哪一组参数导致了失败,便于快速定位问题。

        总的来说,@pytest.mark.parametrize 装饰器是一个非常实用的工具,可以帮助你轻松地实现参数化测试,提高测试代码的灵活性和可维护性

三、参数

@pytest.mark.parametrize 装饰器函数接收两个参数:参数名称和参数值列表。具体来说,它的签名如下:

@pytest.mark.parametrize(argnames, argvalues)

其中:

1、argnames

一般情况下argnames都为字符串,当参数值只有一个值时,必须为字符串

是一个字符串,用于指定测试函数的参数名称它可以是一个逗号分隔的字符串,也可以是一个列表或元组,每个元素代表一个参数名称。例如,"input1, input2, expected"("input1", "input2", "expected")

2、argvalues

支持列表,元祖,字典列表,字典元祖类型,有多少个值就会执行多少次用例

是一个列表或元组,用于指定测试函数的参数值组合。每个元素都代表一组参数值。它可以是一个包含元组的列表,每个元组代表一组参数值;也可以是一个二维数组,其中每一行代表一组参数值。例如,[(1, 1, 2), (2, 3, 5), (10, -5, 5)][[1, 1, 2], [2, 3, 5], [10, -5, 5]]

        在参数化测试中,argnames 定义的参数名称将会被用于测试函数的参数列表中,而 argvalues 定义的参数值组合将会被用于测试函数的多次执行。每次执行测试函数时,都会使用 argvalues 中的一个参数值组合作为输入参数。

        总的来说,@pytest.mark.parametrize 装饰器函数的参数类型为一个字符串和一个列表或元组,分别用于指定测试函数的参数名称和参数值组合。

四、装饰测试类

1、介绍

我们可以对类进行参数化,但这限制比较高,比如你类下面的所有函数,参数名称和个数都要和你写的参数名称个数一致,否则就会报错.

2、示例代码

import pytest

@pytest.mark.parametrize("name,age,sex", [("微光", 18, "男"), ("杨过", 20, "男"), ("小龙女", 27, "女")])
class TestDemo:
    """
    必须要在该测试类中的所有方法上都传入全部参数,否则就会报错
    """
    def test_demo01(self, name, age, sex):
        print(name, age, sex)

    # def test_demo02(self, sex):
    #     print(sex)

    # def test_demo03(self):
    #     print("我没有参数,我会执行几次")

    def test_demo04(self, name, age, sex):
        print(name, age, sex)

3、运行结果

4、结果分析

可以看到,我们装饰在类上,下面的所有测试用例,只要符合要求的,都会进行参数化,有多少个参数就会执行多少次

五、全局变量方式进行参数化

1、介绍

我们可以通过将参数化的值赋值给全局变量,然后也能实现装饰类的效果

2、示例代码

import pytest

pytestmark = pytest.mark.parametrize("name, age", [("杨过", 18), ("小龙女", 27)])

class TestDemo:
    def test_demo01(self, name, age):
        print(name, age)

    def test_demo02(self, name, age):
        print(name, age)

def test_demo03(name, age):
    print(name, age)

if __name__ == '__main__':
    pytest.main(["-vs"])

3、运行结果

4、结果分析

        通过全局变量的方式来进行参数化,它相较于在类上进行装饰器的范围更广,可以针对当前模块进行参数化,但同样的也要受到局限,如test_demo03必须要接收参数且参数名和个数都要和参数化的一模一样。

六、堆叠parametrize装饰器

1、介绍

我们可以在测试函数或者测试类进行堆叠parametrize装饰器,实现一些特殊效果.

2、示例代码

import pytest

@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_demo(x, y):
    print("\nx -->", x)
    print("y -->", y)

3、运行结果

4、结果分析


        查看打印的结果,可以看到它们的执行顺序是x=0/y=2、x=1/y=2、 x=0/y=3、x=1/y=3.
这里我格外注意,堆叠的方式,不会是执行两次测试用例,分别传值,而是执行4次,然后顺序就是从下面的y开始2,一次将上面的x执行一轮,然后又从y的3开始,在执行一次x一轮才算结束.

七、参数为字典的方式

1、介绍

我们在传递参数的时候有时候也希望传递一个字典,这样会更加省事一些.下面就来具体使用一下.

2、示例代码

import pytest

dict1 = [{
    "name": "杨过",
    "age": 18
}, {
    "name": "小龙女",
    "age": 27
}, {
    "name": "黄蓉",
    "age": 29
}]


@pytest.mark.parametrize("dict1", dict1)
def test_demo(dict1):
    print(dict1)

3、运行结果

4、结果分析


通过字段的方式来进行传值的话,我们的参数将更灵活,不用局限于必须传递里面的参数名和参

数个数一样,我们只需要保证外面接收的参数一样就可以了.例如我们有两个测试函数test1,test2,test1需要的参数有name和age,test2只需要sex,如果我们用直接列表套元祖是无法完成的,会报参数错误,但是我们用字段的形式就能完全解决这个问题,如我们传递的参数是dict,我们要用name和age就,dict.name,dict.age,同理取sex就是dict.sex,所以这种方式也是我们常用的方式.

八、ids参数用例描述

1、介绍

我们可以通过ids对参数用例进行描述,如果不设置ids,就默认参数值表示.

2、示例代码

import pytest


# 没有传ids的,默认用参数值
@pytest.mark.parametrize("x, y", [(0, 1), (2, 3), (4, 5)])
def test_demo01(x, y):
    pass


# 传入了ids的,使用ids中的名称
@pytest.mark.parametrize("x, y", [(0, 1), (2, 3), (4, 5)], ids=("first", "second", "third"))
def test_demo02(x, y):
    pass

if __name__ == '__main__':
    pytest.main()

3、运行结果

4、结果分析

我们可以看到,没有设置ids的就是默认用的参数值,看左侧列表.设置了ids的就是我们设置的内容,只不过pytest会转义unicode字符串中用于参数化的任何非ascii字符.

九、解决unicode编码问题

1、问题描述

如上图中右侧,我们可以看到我们设置的ids为中文时,它出现了乱码,因为pytest会转义unicode字符串中用于参数化的任何非ascii字符,我们下面就来解决这个问题.

2、通过配置pytest.ini处理

  • 介绍

我们在根目录下的pytest.ini文件中添加如下代码:

  • 示例代码
    文件名: pytest.ini(没有就新建)
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
import pytest


# 没有传ids的,默认用参数值
@pytest.mark.parametrize("x, y", [(0, 1), (2, 3), (4, 5)])
def test_demo01(x, y):
    pass


# 传入了ids的,使用ids中的名称
@pytest.mark.parametrize("x, y", [(0, 1), (2, 3), (4, 5)], ids=("第一次", "第二次", "第三次"))
def test_demo02(x, y):
    pass

运行结果

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值