【接口自动化测试】pytest 框架

文章目录

  • [1. pytest 介绍](#1. pytest 介绍)
  • [2. 安装](#2. 安装)
  • [3. 用例运行规则](#3. 用例运行规则)
  • [4. pytest 命令参数](#4. pytest 命令参数)
    • [4.1 示例一](#4.1 示例一)
    • [4.2 示例二](#4.2 示例二)
    • [4.3 示例三](#4.3 示例三)
  • [5. pytest 配置文件](#5. pytest 配置文件)
  • [6. 前后置](#6. 前后置)
  • [7. 断言](#7. 断言)
    • [7.1 基本数据类型的断言](#7.1 基本数据类型的断言)
    • [7.2 数据结构断言](#7.2 数据结构断言)
    • [7.3 函数断言](#7.3 函数断言)
    • [7.4 接口返回值断言](#7.4 接口返回值断言)
  • [8. 参数化](#8. 参数化)
    • [8.1 在用例上使用参数化](#8.1 在用例上使用参数化)
    • [8.2 在类上使用参数化](#8.2 在类上使用参数化)
    • [8.3 自定义参数化数据源](#8.3 自定义参数化数据源)
  • [9. fixture](#9. fixture)
    • [9.1 基本使用](#9.1 基本使用)
      • [9.1.1 使用与不使用 fixture 标记](#9.1.1 使用与不使用 fixture 标记)
      • [9.1.2 访问列表页和详情页之前都需要执行登录操作](#9.1.2 访问列表页和详情页之前都需要执行登录操作)
    • [9.2 fixture 嵌套](#9.2 fixture 嵌套)
    • [9.3 请求多个 fixture](#9.3 请求多个 fixture)
    • [9.4 yield fixture](#9.4 yield fixture)
    • [9.5 带参数的 fixture](#9.5 带参数的 fixture)
      • [9.5.1 scope 的使用](#9.5.1 scope 的使用)
      • [9.5.2 module 与 session 实现全局的前后置应用](#9.5.2 module 与 session 实现全局的前后置应用)
      • [9.5.3 autouse 的使用](#9.5.3 autouse 的使用)
      • [9.5.4 通过 params 实现参数化](#9.5.4 通过 params 实现参数化)

支持 Python 语言的接口自动化框架有很多,以下是支持 Python 的接口自动化主流框架对比分析。

主流Python接口自动化框架对比

维度 unittest(Python内置) pytest Robot Framework
安装方式 无需安装(Python标准库) pip install pytest pip install robotframework
语法风格 基于类(需继承TestCase 函数式或面向对象(无需样板代码) 关键字驱动(表格化用例)
断言方法 self.assertEqual() 原生assert表达式 关键字断言(如Should Be Equal
参数化支持 subTest或第三方库 内置@pytest.mark.parametrize 数据驱动(Test Template
插件生态 少(依赖扩展库如HTMLTestRunner 丰富(如pytest-htmlpytest-xdistallure-pytest 一般(需安装额外库如RequestsLibrary
测试报告 需插件生成报告 支持多格式报告(HTML、Allure等) 自带详细日志和报告
学习曲线 中等(需熟悉xUnit模式) 低(语法简洁) 高(需掌握关键字和语法)
BDD支持 不支持 支持(通过pytest-bdd插件) 支持(通过robotframework-bdd
适用场景 简单项目或遗留系统维护 复杂项目、高扩展性需求 团队协作、非技术人员参与

1. pytest 介绍

pytest 官方文档:docs.pytest.org

pytest 是一个非常流行且高效的 Python 测试框架,它提供了丰富的功能和灵活的用法,使得编写和运行测试用例变得简单而高效。

为什么选择 pytest:

  • 简单易用:pytest 的语法简洁清晰,对于编写测试用例非常友好,几乎可以在几分钟内上手。
  • 强大的断言库:pytest 内置了丰富的断言库,可以轻松地进行测试结果的判断。
  • 支持参数化测试:pytest 支持参数化测试,允许使用不同的参数多次运行同一个测试函数,这大大提高了测试效率。
  • 丰富的插件生态系统:pytest 有着丰富的插件生态系统,可以通过插件扩展各种功能,比如覆盖率测试、测试报告生成(如 pytest-html 插件可以生成完美的 HTML 测试报告)、失败用例重复执行(如 pytest-rerunfailures 插件)等。此外,pytest 还支持与 selenium、requests、appinum 等结合,实现 Web 自动化、接口自动化、App 自动化测试。
  • 灵活的测试控制:pytest 允许跳过指定用例,或对某些预期失败的 case 标记成失败,并支持重复执行失败的 case。

2. 安装

安装 pytest==8.3.2 要求 Python 版本在 3.8 及以上。

bash 复制代码
# 建议跟我这里统一版本,避免版本差异
pip install pytest==8.3.2

若 Python 版本低于3.8,可参考对应版本支持的 Python 版本:

pytest版本 最低Python版本
8.0+ 3.8+
7.1+ 3.7+
6.2 - 7.0 3.6+
5.0 - 6.1 3.5+
3.3 - 4.6 2.7, 3.4+

安装成功如下所示:

安装好 pytest 后,确认 PyCharm 中 Python 解释器已更新,可看到有无 pytest 框架编写代码的区别:

其中:

  • 未安装 pytest:需编写 main 函数,手动调用测试用例 test01
  • 安装 pytest:方法名前有直接运行标志。

3. 用例运行规则

测试用例的命名规则,如下所示:

  • 文件名 必须以 test_ 开头或者 _test 结尾;
  • 测试类 必须以 Test 开头,并且不能有 __init__ 方法;
  • 测试方法 必须以 test 开头。

如下所示:

当满足以上要求后,可通过命令行参数 pytest 直接运行符合条件的用例。

注意:Python 类中不可以添加 init 方法,如下所示

python 复制代码
class Test():
    def __init__(self):
        print("-----init-------")
    def test_a(self):
        print("-----test_a----")

如果添加了,那么就会报错:

由于 pytest 的测试收集机制,测试类中不可以定义 __init__ 方法。

pytest 采用自动发现机制来收集测试用例,它会自动实例化测试类并调用其所有以 test 结尾的方法作为测试用例,如果测试类中定义了 __init__ 方法,那么当 pytest 实例化该类时,__init__ 方法会被调用,这可能会掩盖测试类的实际测试逻辑,并引入额外的副作用,影响测试结果的准确性。

若测试类中存在初始化操作该采取什么方案?

  • 为了避免使用 __init__ 方法,建议在 pytest 中使用其他替代方案,如使用 setUp() 和 tearDown() 方法、使用类属性、使用 fixture 函数等待。

4. pytest 命令参数

pytest 提供了丰富的命令行选项来控制测试的执行,以下是一些常用的 pytest 命令行参数及其使用说明。

如下所示:

命令 描述 备注
pytest 在当前目录及其子目录中搜索并运行测试。
pytest -v 增加输出的详细程度。
pytest -s 显示测试中的print语句。
pytest test_module.py 运行指定的测试模块。
pytest test_dir/ 运行指定目录下的所有测试。
pytest -k 只运行测试名包含指定关键字的测试。
pytest -m 只运行标记为指定标记的测试。
pytest -q 减少输出的详细程度。
pytest --html=report.html 生成HTML格式的测试报告。 需要安装pytest-html插件
pytest --cov 测量测试覆盖率。 需要安装pytest-cov插件

4.1 示例一

代码如下所示:

python 复制代码
import requests

class Test():

    def test01(self):
        url = "http://121.196.200.210:8080/user/login"
        data = {
            "userName": "zhangsan",
            "password": "123456"
        }
        r = requests.request(method="POST",url=url, data=data)
        print(r.json())

直接运行符合运行规则的用例:

shell 复制代码
# 命令行输入
pytest

运行结果如下:

注意,这里不会输出测试用例中 printf 内容。

4.2 示例二

代码如下所示:

python 复制代码
import requests

class Test():

    def test01(self):
        url = "http://121.196.200.210:8080/user/login"
        data = {
            "userName": "zhangsan",
            "password": "123456"
        }
        r = requests.request(method="POST",url=url, data=data)
        print(r.json())

详细打印,并输入 print 内容

shell 复制代码
# 命令行输入
pytest -s -v 

#或者可以连写
pytest -sv

运行结果如下所示:

4.3 示例三

代码如下所示:

python 复制代码
import requests

class Test():
    def test01(self):
        url = "https://account.cnblogs.com/user/userinfo"
        cookie = {
            ".Cnblogs.AspNetCore.Cookies": "CfDJ8OBZrxGJLy5MvghieKIKXCxbUmIkNTWX8sFnVgjSYCK2xJeRRRkWqJpqml3XWNmu3h_h2ZF3JQfhpPEwc8yqABU5rTqRfr-02D0yGdZBQaGyWq2-ArdENUlfGM81xX6aGUq3rcpt84qAyN0ybJF9ZR5VSiedSoHJJV8VXFvsUSJkWiHbdITWfR0ZpmxvcGkEsxMDu0lYKxcn_BdaSC-RgZyOLM9KX28IgLqwHXcw-Js-YjSYZT68CpMs6awhj8wmxzRxztaHetu_zZHq-zevyw_N9037Z6XNfBTox8M_bManwl5oV6dY8G9PrhKbadQIfvvX-AKP8mZLLyCVvUDooEGWLYgCb1wyc9FBWMua0y-96Sf2rv_-RBsJPZFCR0EuHlWoHqvct7fa2Z9JdnAMFzUdjc0Yv3coRcwRg8k2n-72RsgMLCy7BmYe0puqFS8QqGNrpCcUOOkIO85-aNr0fbywN1ErrMdx283X7B8ROPTtJ8vr_AsDFItcPUdLiZFBzVnWibu-UWJ5vGGl-WdnkEch46v1NaqYjDkOBzE-NmM5hB9ZKHeVmMlHNIFNzaQAHA;"
        }
        r = requests.request(method="GET", url=url, cookies=cookie)
        print(r.text)

我们可以指定文件/测试用例

python 复制代码
# 指定文件:pytest 包名/文件名
pytest test_02.py

# 指定测试用例:pytest 包名/文件名::类名::方法名
pytest test_03.py::Test::test01

运行结果如下:

问题:当我们既要详细输出,又要指定文件时,命令会又臭又长,而且每次运行都需要手动输入命令,如何解决?

很简单,将需要的相关配置参数统一放到 pytest 配置文件中。

5. pytest 配置文件

在当前项目下创建 pytest.ini 文件,该文件为 pytest 的配置文件,以下为常见的配置选项:

参数 解释
addopts 指定在命令行中默认包含的选项。
testpaths 指定搜索测试的目录。
python_files 指定发现测试模块时使用的文件匹配模式。
python_classes 指定发现测试类时使用的类名前缀或模式。
python_functions 指定发现测试函数和方法时使用的函数名前缀或模式。
norecursivedirs 指定在搜索测试时应该避免递归进入的目录模式。
markers 定义测试标记,用于标记测试用例。

示例:详细输出 cases 包下文件名以 test_ 开头且方法名以 Test 开头的所有用例

ini 复制代码
[pytest]
addopts = -vs
testpaths = ./cases
python_files = test_*.py
python_classes = A_

配置好 pytest.ini 文件后,命令行执行 pytest 命令即可,无需再额外指定其他参数。

可以看到,此时运行了 cases 目录下的 test_case01.py 文件里面的 A_test 类的所有方法:

pytest.ini 文件通常位于项目的根目录下。通过在 pytest.ini 中定义配置项,可以覆盖 pytest 的默认行为,以满足项目的需求。

6. 前后置

遗留问题:使用 pytest 框架,测试类中不可以添加 init() 方法,如何进行数据的初始化?

在测试框架中,前后置是指在执行测试用例前和测试用例后执行一些额外的操作,这些操作可以用于设置测试环境、准备测试数据等,以确保测试的可靠性。

pytest框架提供三种方法做前后置的操作:

  • setup_methodteardown_method:这两个方法用于类中的每个测试方法的前置和后置操作。
  • setup_classteardown_class:这两个方法用于整个测试类的前置和后置操作。
  • fixture:这是 pytest 推荐的方式来实现测试用例的前置和后置操作,它提供了更灵活的控制和更强大的功能。

示例一:setup_methodteardown_method

代码如下所示:

python 复制代码
import pytest

class Test:
    def setup_method(self):
        print("Setup: Before each test")

    def test01(self):
        print("test01方法")

    def test02(self):
        print("test02方法")

    def teardown_method(self):
        print("Teardown: After each test")

运行结果如下所示:可以看到,每个测试方法之前都会调用 setup_method,之后都会调用 teardown_method

示例二:setup_classteardown_class

python 复制代码
class Test:
    def setup_class(self):
        print("Setup: Before all test")

    def test01(self):
        print("test01方法")

    def test02(self):
        print("test02方法")

    def teardown_class(self):
        print("Teardown: After all test")

运行结果如下所示:可以看到,所有测试方法之前都会调用 setup_class,之后都会调用 teardown_class

7. 断言

断言(assert)是一种调试辅助工具,用于检查程序的状态是否符合预期。如果断言失败(即条件为假),Python解释器将抛出一个AssertionError异常。断言通常用于检测程序中的逻辑错误。

pytest 允许你在 Python 测试中使用标准的 Python assert语句来验证预期和值。

基本语法:

python 复制代码
assert 条件,错误信息

其中:

  • 条件:必须是一个布尔表达式。
  • 错误信息:当条件为假时显示的错误信息,可选。

免费学习 API 资源:JSONPlaceholder

7.1 基本数据类型的断言

代码如下所示:

python 复制代码
def test_base():
    # 断言整数
    a = 1
    b = 2
    assert a == b

    # 断言字符串
    str = "hello"
    assert "hello" == str

运行结果如下:

7.2 数据结构断言

代码如下所示:

python 复制代码
def test_ds():
    # 断言列表
    expect_list = [1, 'apple', 3.14]
    actual_list = [1, 'apple', 3.14]

    # 断言元组
    expect_tuple = (1, 'apple', 3.14)
    actual_tuple = (1, 'apple', 3.14)

    # 断言字典
    expect_dict = {'name': 'Alice', 'age': 25}
    actual_dict = {'name': 'Alice', 'age': 25}

    # 断言集合
    expect_set = {1, 2, 3, 'apple'}
    actual_set = {1, 2, 3, 'apple'}

    assert expect_list == actual_list
    assert expect_tuple == actual_tuple
    assert expect_dict == actual_dict
    assert expect_set == actual_set

结果如下所示:

7.3 函数断言

代码如下所示:

python 复制代码
def divide(a, b):
    assert b != 0, "除数不能为0"
    return a / b

def test():
    # 正常情况
    print(divide(10, 2))  # 输出 5.0

    # 触发断言
    print(divide(10, 0))  # 抛出 AssertionError: 除数不能为0

结果如下所示:

7.4 接口返回值断言

代码如下所示:

python 复制代码
#断言接口返回值完整字段和值
def test1():
    url = "http://jsonplaceholder.typicode.com/posts/1"
    r = requests.get(url=url)

    expect_data = {
        "userId": 1,
        "id": 1,
        "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
        "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
    }
    print(r.json())
    assert r.json() == expect_data
    assert r.json()['userId'] == 1

#断言接口返回值重要字段
def test2():
    url = "http://jsonplaceholder.typicode.com/comments?postId=1"
    r = requests.get(url=url)

    print(r.json())
    assert r.json()[1]['id'] == 2

#断言接口html返回值
def test3():
    url = "http://jsonplaceholder.typicode.com/"
    r = requests.get(url=url)

    assert "Use your own data" in r.text

结果如下所示:

8. 参数化

参数化设计是自动化设计中的一个重要组成部分,它通过定义设计参数和规则,使得设计过程更加灵活和可控。

pytest 中内置的 pytest.mark.parametrize 装饰器允许对测试函数的参数进行参数化。

8.1 在用例上使用参数化

代码如下所示:

python 复制代码
import pytest

# 单个参数
@pytest.mark.parametrize("data", (1,2,3))
def test(data):
    print(data)

# 多组参数
@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

运行结果如下:

这里,@parametrize 装饰器定义了三个不同的 (test_input,expected) 元组,以便 test_eval 函数将依次使用它们运行三次。

也可以在类或模块上使用 parametrize 标记,这将使用参数集调用多个函数。

8.2 在类上使用参数化

代码如下所示:

python 复制代码
import pytest

@pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
    def test_simple_case(self, n, expected):
        assert n + 1 == expected

    def test_weird_simple_case(self, n, expected):
        assert (n * 1) + 1 == expected

结果如下所示:

要对模块中的所有测试进行参数化,也可以将 pytestmark 全局变量赋值:

python 复制代码
import pytest

pytestmark = pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])

class TestClass:
    def test_simple_case(self, n, expected):
        assert n + 1 == expected

    def test_weird_simple_case(self, n, expected):
        assert (n * 1) + 1 == expected

运行结果如下:

除了使用 @parametrize 添加参数化外,pytest.fixture() 允许对 fixture 函数进行参数化。

8.3 自定义参数化数据源

代码如下所示:

python 复制代码
def data_provider():
    return ["a", "b"]

# 定义一个测试函数,它依赖于上面函数的返回值
@pytest.mark.parametrize("data", data_provider())
def test_data(data):
    assert data != None
    print(f"Testing with data provider: {data}")

运行结果如下:

9. fixture

pytest 中的 fixture 是一种强大的机制,用于提供测试函数所需的资源或上下文。它可以用于设置测试环境、准备数据等。

以下是 fixture 的一些核心概念和使用场景。

9.1 基本使用

9.1.1 使用与不使用 fixture 标记

未标记 fixture 方法的调用:

python 复制代码
def fixture_01():
    print("第一个fixture标记的方法")

def test_01():
    fixture_01()
    print("第一个测试用例")

结果如下所示:

标记 fixture 的方法调用:

python 复制代码
import pytest

@pytest.fixture
def fixture_01():
    print("第一个fixture标记的方法")

def test_01(fixture_01):
    print("第一个测试用例")

结果如下所示:

未标记 fixture 方法的调用与 fixture 标记的方法调用完全不一样,前者需要在方法体中调用,而后者可以将函数名作为参数进行调用。

测试脚本中存在的很多重复的代码、公共的数据对象时,使用 fixture 最为合适。

9.1.2 访问列表页和详情页之前都需要执行登录操作

代码如下所示:

python 复制代码
import pytest

@pytest.fixture
def login():
    print("---执行登陆操作-----")

def test_list(login):
    print("---访问列表页")

def test_detail(login):
    print("---访问详情页")

结果如下所示:

通过使用 @pytest.fixture 装饰器来告诉 pytest 一个特定函数是一个 fixture,通过运行结果可见,在执行列表页和详情页之前都会先执行 login 方法。

9.2 fixture 嵌套

代码如下所示:

python 复制代码
import pytest

# 比较两个列表是否相同

# 安排
@pytest.fixture
def first_entry():
    return "a" # 返回字符串"a"

# 安排
@pytest.fixture
def order(first_entry):
    return [first_entry] # 返回列表["a"]

def test_string(order):
    # 行动
    order.append("b") # 在列表["a"]后添加"b"

    # 断言
    assert order == ["a", "b"]

结果如下所示:

测试不必局限于单个 fixture,它们可以依赖于您想要的任意数量的 fixture,并且 fixture 也可以使用其他 fixture。

pytest 最伟大的优势之一是其极其灵活的 fixture 系统,它允许我们将测试的复杂需求简化为更简单和有组织的函数,我们只需要每个函数描述它们所依赖的事物。

9.3 请求多个 fixture

代码如下所示:

python 复制代码
import pytest

class Fruit:
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name

@pytest.fixture
def my_fruit():
    return Fruit("apple")

@pytest.fixture
def your_fruit(my_fruit):
    return [my_fruit, Fruit("banana")]

#你的水果里面是否包含我的水果
def test_fruit(my_fruit, your_fruit):
    assert my_fruit in your_fruit

运行结果如下所示:

测试和 fixture 不仅限于一次请求单个 fixture,它们可以请求任意多个。

9.4 yield fixture

当我们运行测试时,我们希望确保它们能够自我清理,以便它们不会干扰其他测试(同时也避免留下大量测试数据来膨胀系统)。pytest 中的 fixture 提供了一个非常有用拆卸系统,它允许我们为每个 fixture 定义具体的清理步骤。

"Yield" fixture 使用 yield 而不是 return。有了这些 fixture,我们可以运行一些代码,并将对象返回给请求的 fixture/test,就像其他 fixture 一样。

唯一的不同是:

  • return 被替换为 yield。
  • 该 fixture 的任何拆卸代码放置在 yield 之后。

一旦 pytest 确定了 fixture 的线性顺序,它将运行每个 fixture 直到它返回或 yield,然后继续执行列表中的下一个 fixture 做同样的事情。

测试完成后,pytest 将逆向遍历 fixture 列表,对于每个 yield 的 fixture,运行 yield 语句之后的代码。

示例一:

python 复制代码
import pytest

@pytest.fixture
def operator():
    print("前置操作: 数据的初始化...")

    yield

    print("后置操作: 数据的清理...")

def test_01(operator):
    print("第一个测试用例")

运行结果如下:

示例二:创建文件句柄与关闭文件

python 复制代码
import pytest

@pytest.fixture
def file_read():
    print("打开文件句柄")
    fo = open("./test.txt", "r", encoding="utf-8")

    yield fo

    print("关闭打开的文件")
    fo.close()

@pytest.fixture
def file_write():
    print("打开文件句柄")
    fo = open("./test.txt", "a", encoding="utf-8") # 追加写入

    return fo
    # yield fo
    # print("关闭打开的文件")
    # fo.close()

def test_file(file_write, file_read):
    # 写入数据
    w = file_write
    w.write("今天的天气真不错!")
    w.close()  # 写入后关闭文件句柄,以便读取

    # 读取数据
    r = file_read
    str = r.read()
    print("文件内容: ", str)

运行结果如下:

9.5 带参数的 fixture

接口如下所示:

python 复制代码
pytest.fixture(scope='', params='', autouse='', ids='', name='')

参数详解:

  • scope 参数用于控制 fixture 的作用范围,决定了 fixture 的生命周期。可选值有:
    • function(默认):每个测试函数都会调用一次 fixture。
    • class:在同一个测试类中共享这个 fixture。
    • module:在同一个测试模块中共享这个 fixture(一个文件里)。
    • session:整个测试会话中共享这个 fixture。
  • autouse 参数默认为 False。如果设置为 True,则每个测试函数都会自动调用该 fixture,无需显式传入。
  • params 参数用于参数化 fixture,支持列表传入。每个参数值都会使 fixture 执行一次,类似于 for 循环。
  • ids 参数与 params 配合使用,为每个参数化实例指定可读的标识符(给参数取名字)。
  • name 参数用于为 fixture 显式设置一个名称。如果使用了 name,则在测试函数中需要使用这个名称来引用 fixture(给 fixture 取名字)。

9.5.1 scope 的使用

scope = "function" 代码如下:

python 复制代码
import pytest

@pytest.fixture(scope="function")
def fixture_01():
    print("初始化")
    yield
    print("清理")

class TestCase():
    def test_01(self, fixture_01):
        print("第一个测试用例")
    def test_02(self, fixture_01):
        print("第二个测试用例")

运行结果如下:可以看到每个用例都会调用一次 fixture_01 函数

scope="class"代码如下:

python 复制代码
import pytest

@pytest.fixture(scope="class")
def fixture_01():
    print("初始化")
    yield
    print("清理")

class TestCase():
    def test_01(self, fixture_01):
        print("第一个测试用例")
    def test_02(self, fixture_01):
        print("第二个测试用例")

结果如下所示:

结论:

  • scope="function" 时,每个测试函数都会调用一次 fixture。(scope 默认为 function,这里的 function 可以省略不写)
  • scope="class" 时,在同一个测试类中,fixture 只会在类中的第一个测试函数开始前执行一次,并在类中的最后一个测试函数结束后执行清理。
  • scope="module"scope="session" 时可用于实现全局的前后置应用,这里需要多个文件的配合。

9.5.2 module 与 session 实现全局的前后置应用

@pytest.fixtureconftest.py 文件结合使用,可以实现在多个测试模块(.py)文件中共享前后置操作,这种结合的方式使得可以在整个测试项目中定义和维护通用的前后置逻辑,使测试代码更加模块化和可维护。

规则:

  • conftest.py 是一个单独存放的夹具配置文件,名称是固定的不能修改。
  • 你可以在项目中的不同目录下创建多个 conftest.py 文件,每个 conftest.py 文件都会对其所在目录及其子目录下的测试模块生效。
  • 在不同模块的测试中需要用到 conftest.py 的前后置功能时,不需要做任何的 import 导入操作。
  • 作用:可以在不同的 .py 文件中使用同一个 fixture 函数。

conftest.py 代码如下所示:

python 复制代码
import pytest

@pytest.fixture(scope="module")
def fixture_01():
    print("初始化")
    yield
    print("清理")

test_case06.py 代码如下所示:

python 复制代码
class TestCase01():
    def test_01(self, fixture_01):
        print("第一个测试用例")

    def test_02(self, fixture_01):
        print("第二个测试用例")

class TestCase02():
    def test_01(self, fixture_01):
        print("第一个测试用例")

    def test_02(self, fixture_01):
        print("第二个测试用例")

test_case07.py 代码如下所示:

python 复制代码
class TestCase03():
    def test_01(self, fixture_01):
        print("第一个测试用例")

    def test_02(self, fixture_01):
        print("第二个测试用例")

此时,在命令行输入 pytest 命令,结果如下所示:

此时,我们再修改 scope="session" 代码如下:

python 复制代码
import pytest

@pytest.fixture(scope="session")
def fixture_01():
    print("初始化")
    yield
    print("清理")

在命令行输入 pytest 命令,结果如下所示:

scope="session" 时,以上两个文件的所有测试用例,会在整个测试会话开始前执行一次 fixture,所有测试结束后执行一次清理。

9.5.3 autouse 的使用

代码如下所示:

python 复制代码
import pytest

@pytest.fixture(scope="session", autouse=True)
def fixture_01():
    print("初始化")
    yield
    print("清理")

class TestCase():
    def test_01(self):
        print("第一个测试用例")
    def test_02(self):
        print("第二个测试用例")

结果如下所示:

autouse=True 会让该 scope 下的所有测试自动使用这个 fixture,无需在测试函数中传入。

9.5.4 通过 params 实现参数化

代码如下所示:

python 复制代码
import pytest

# 定义一个参数化的fixture
@pytest.fixture(params=[1, 2, 3])
def data_provider(request):
    return request.param

# 定义一个测试函数,它依赖于上面的参数化fixture
def test_data(data_provider):
    assert data_provider != None
    print(f"data provider: {data_provider}")

运行结果如下:

前面我们已经学过 pytest 中通过 @pytest.mark.parametrize 实现参数化,现在通过 fixture 也可以实现参数化,那么到底哪一种更好呢?

  • 如果测试场景主要涉及简单的参数传递,且不需要复杂的资源管理,建议使用 parametrize,因为它更简单直接;
  • 如果测试需要动态加载外部数据,或者需要管理复杂的测试资源(如数据库连接、文件操作等),建议使用 fixture,在某些情况下,也可以结合使用 parametrize 和 fixture,以充分利用两者的优点。

总结来说,parametrize 更适合简单场景,而 fixture 更适合需要动态数据和资源管理的复杂场景。