1. 项目概述:为什么是pytest?

如果你已经跟着这个系列走到了第十一天,那说明你已经跨过了Selenium操作浏览器、理解了PO设计模式,甚至可能已经用unittest写过一些脚本了。现在,我们来到了接口自动化测试的门口,而手里拿着的钥匙,就是 pytest 。很多新手会问,unittest不是Python自带的吗,为什么还要学pytest?这就像问“为什么有了手动挡还要开自动挡”一样。unittest是基础,它教会你测试的结构和思想;但pytest是生产力工具,它能让你写得更快、更爽、更优雅。

pytest不是一个简单的测试框架,它更像是一个测试平台。它几乎兼容unittest,但提供了更简洁的语法、更强大的功能(如参数化、夹具、插件生态)和更友好的输出。在接口自动化中,我们面对的是大量的、结构化的请求与响应断言。pytest的灵活性,比如用 @pytest.mark.parametrize 轻松实现数据驱动,用 fixture 来管理测试前后的资源(如数据库连接、登录态),能让我们从繁琐的重复代码中解放出来,专注于测试逻辑本身。今天,我们就从零开始,彻底搞懂pytest的用法、规则、配置和标记,为你搭建一个坚实、高效的接口自动化测试框架打下基础。

2. pytest核心规则与项目结构

在开始写第一个测试用例之前,我们必须先理解pytest的“游戏规则”。它有一套默认的发现机制,遵循这些规则,你的测试才能被自动找到并执行。

2.1 默认的测试发现规则

pytest会递归搜索你指定的目录(默认是当前目录),寻找符合以下规则的文件和函数/方法:

  1. 文件命名 :测试文件应以 test_ 开头,或以 _test.py 结尾。例如, test_login.py login_test.py 都是有效的。
  2. 测试函数/方法命名 :测试函数(在类外部)或测试方法(在类内部)应以 test_ 开头。这是最重要的标识。
  3. 测试类命名 :测试类应以 Test 开头,且该类不能有 __init__ 方法。类中的测试方法同样需要以 test_ 开头。

注意 :这些是默认规则。虽然可以通过配置修改,但在团队协作中,严格遵守这些约定能极大降低沟通成本,避免“为什么我的用例没跑?”这类问题。

2.2 推荐的项目目录结构

一个清晰的目录结构是维护大型自动化项目的基石。对于接口自动化,我推荐如下结构:

your_project/
├── common/           # 公共模块
│   ├── __init__.py
│   ├── logger.py     # 日志模块
│   ├── request_util.py # 封装的请求工具类
│   └── db_util.py    # 数据库操作工具(如需)
├── config/           # 配置文件
│   ├── __init__.py
│   ├── config.py     # 基础配置(如环境URL)
│   └── pytest.ini    # pytest配置文件(核心!)
├── test_data/        # 测试数据文件
│   ├── login_data.yaml
│   └── order_data.json
├── test_cases/       # 测试用例目录
│   ├── __init__.py
│   ├── test_login.py
│   └── test_order.py
├── conftest.py       # 全局夹具定义文件(核心!)
├── requirements.txt  # 项目依赖
└── run.py            # 主运行入口(可选)

关键文件解析

  • conftest.py :这是pytest的“魔法”文件。你可以在这里定义 夹具(fixture) ,这些夹具可以被同一目录及子目录下的所有测试文件使用。它是实现测试前置后置操作(setup/teardown)复用的核心。
  • pytest.ini :pytest的配置文件。在这里可以修改默认行为,比如指定命令行参数、注册标记、配置日志等。
  • requirements.txt :列出所有依赖包,如 pytest , requests , pytest-html 等,方便团队环境统一。

2.3 第一个pytest测试用例

让我们从一个最简单的例子开始,感受pytest的简洁。假设我们要测试一个简单的计算函数。

首先,在 test_cases 目录下创建 test_demo.py

# test_cases/test_demo.py

def add(a, b):
    return a + b

def test_add_two_positive_numbers():
    """测试两个正数相加"""
    result = add(3, 5)
    assert result == 8

def test_add_positive_and_negative():
    """测试正数与负数相加"""
    result = add(10, -4)
    assert result == 6

class TestAddFunction:
    """将add函数的测试用例组织在一个类中"""
    
    def test_add_with_zero(self):
        """测试与0相加"""
        result = add(7, 0)
        assert result == 7
    
    def test_add_two_negative_numbers(self):
        """测试两个负数相加"""
        result = add(-2, -3)
        # 这里故意写一个错误断言,看看pytest如何报告失败
        assert result == -4  # 实际结果是-5,此断言会失败

打开终端,切换到项目根目录,运行命令: pytest test_cases/test_demo.py -v -v 参数表示输出详细信息。你会看到清晰的通过和失败报告,对于失败的用例,pytest会清晰地指出期望值和实际值,这比unittest的 assertEqual 直观得多。

实操心得 :从一开始就养成用 assert 语句的习惯。pytest会重写 assert ,在断言失败时提供丰富的上下文信息,这是它的一大优势。不要再用 self.assertEqual(expected, actual) 这种unittest风格的写法了。

3. pytest核心功能深度解析

掌握了基本规则,我们来深入pytest最强大的几个功能,这些是构建高效接口自动化框架的利器。

3.1 夹具(Fixture):测试的脚手架

Fixture是pytest的灵魂。你可以把它理解为测试用例的“前置条件”和“后置清理”的提供者。它比unittest的 setUp / tearDown 更灵活,可以跨文件、跨类共享,并且支持作用域控制。

定义一个简单的Fixture (通常在 conftest.py 中定义):

# conftest.py
import pytest
import requests

@pytest.fixture(scope="function")
def get_login_token():
    """获取用户登录token的夹具,每个测试函数执行一次。"""
    print("\n--- 开始执行登录,获取token ---")
    login_url = "https://api.example.com/login"
    payload = {"username": "test_user", "password": "test123"}
    
    # 模拟登录请求
    response = requests.post(login_url, json=payload)
    token = response.json().get("access_token")
    
    yield token  # 将token提供给测试用例使用
    
    print("\n--- 测试结束,可在此处执行清理(如登出)---")
    # yield之后的代码,无论测试成功还是失败,都会执行,用于清理。

在测试用例中使用Fixture

# test_cases/test_user_info.py
def test_get_user_profile(get_login_token):
    """测试获取用户信息,需要登录token"""
    headers = {"Authorization": f"Bearer {get_login_token}"}
    # 使用获取到的token发起请求...
    # assert ...
    print(f"使用的Token是:{get_login_token}")

Fixture的作用域(scope)

  • function (默认):每个测试函数运行一次。
  • class :每个测试类运行一次,该类中的所有方法共享同一个fixture实例。
  • module :每个.py文件运行一次。
  • package :每个包(目录)运行一次。
  • session :整个测试会话(一次pytest运行)只运行一次。

注意事项 :对于像数据库连接、HTTP会话(如 requests.Session )这类创建成本较高的资源,使用 scope="session" 可以显著提升测试速度。但务必注意,如果测试会修改共享资源的状态(比如向共享数据库插入数据),就需要考虑测试隔离性,可能不适合用session作用域。

3.2 参数化(Parametrize):数据驱动的核心

接口测试中,我们经常需要用多组数据测试同一个接口。手动复制粘贴用例是低效且容易出错的。 @pytest.mark.parametrize 装饰器完美解决了这个问题。

基础参数化

import pytest

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

在接口自动化中的实战应用 : 假设我们测试一个登录接口,需要验证正常登录、密码错误、用户不存在等多种情况。

# test_cases/test_login.py
import pytest
import requests

class TestLoginAPI:
    
    base_url = "https://api.example.com"
    
    @pytest.mark.parametrize("username, password, expected_code, expected_msg", [
        ("correct_user", "correct_pwd", 200, "登录成功"),
        ("correct_user", "wrong_pwd", 401, "密码错误"),
        ("non_exist_user", "any_pwd", 404, "用户不存在"),
        ("", "some_pwd", 400, "用户名不能为空"),
    ])
    def test_login(self, username, password, expected_code, expected_msg):
        """数据驱动测试登录接口"""
        url = f"{self.base_url}/login"
        payload = {"username": username, "password": password}
        
        response = requests.post(url, json=payload)
        
        # 断言状态码
        assert response.status_code == expected_code
        # 断言返回消息
        response_json = response.json()
        assert response_json.get("message") == expected_msg

实操心得 :将测试数据与测试逻辑分离是最佳实践。对于复杂的数据,可以将其存放在YAML或JSON文件中,然后在参数化时读取。这样,当测试数据需要更新时,你不需要去修改Python代码。

3.3 标记(Mark):给测试用例分类

当你有成百上千个测试用例时,如何有选择地运行它们?比如只运行冒烟测试、或者跳过某个已知问题的用例?pytest的标记系统提供了解决方案。

内置标记

  • @pytest.mark.skip(reason=“...” ) :无条件跳过该测试。
  • @pytest.mark.skipif(condition, reason=“...” ) :如果条件为真,则跳过。
  • @pytest.mark.xfail(reason=“...” ) :预期该测试会失败,如果它失败了,pytest会报告为“xfailed”(预期失败);如果它通过了,则报告为“xpassed”(意外通过),这常用于测试尚未修复的Bug。

自定义标记 : 你可以在 pytest.ini 文件中注册自定义标记,并赋予它们意义。

# pytest.ini
[pytest]
markers =
    smoke: 冒烟测试用例(核心功能)
    regression: 回归测试用例
    slow: 运行缓慢的测试用例
    api: 接口测试用例

在测试用例上使用标记:

# test_cases/test_order.py
import pytest

@pytest.mark.smoke
@pytest.mark.api
def test_create_order():
    """创建订单 - 冒烟测试 & 接口测试"""
    pass

@pytest.mark.regression
@pytest.mark.slow
def test_query_order_history():
    """查询历史订单 - 回归测试 & 慢速测试"""
    pass

通过标记运行测试

  • 只运行冒烟测试: pytest -m smoke
  • 运行冒烟测试和接口测试: pytest -m “smoke or api”
  • 运行非慢速的回归测试: pytest -m “regression and not slow”

重要提示 :使用自定义标记前, 必须在 pytest.ini 中注册 ,否则运行时会收到警告。这是一个很好的实践,它让标记的定义对团队可见,避免混淆。

4. pytest配置与插件生态

pytest的强大,一半来自于其高度可配置性,另一半来自于其丰富的插件生态。

4.1 核心配置文件:pytest.ini

pytest.ini 应该放在项目根目录。下面是一个功能丰富的配置示例:

# pytest.ini
[pytest]
# 1. 指定测试文件搜索路径(可配置多个)
testpaths = test_cases

# 2. 指定Python文件命名模式
python_files = test_*.py *_test.py

# 3. 指定测试类和函数的命名模式
python_classes = Test*
python_functions = test_*

# 4. 注册自定义标记(防止运行时警告)
markers =
    smoke: 冒烟测试
    regression: 回归测试
    slow: 运行缓慢的测试
    api: 接口测试

# 5. 添加默认命令行参数
# -v: 详细输出
# --tb=short: 当测试失败时,只显示简短的追溯信息,更清晰
# --strict-markers: 严格检查标记,如果使用了未注册的标记会报错
# --html=report.html: 使用pytest-html插件生成HTML报告(需先安装)
addopts = -v --tb=short --strict-markers --html=reports/report.html --self-contained-html

# 6. 配置日志(需配合logging模块)
log_cli = true
log_cli_level = INFO
log_file = logs/pytest_run.log
log_file_level = DEBUG

4.2 必备插件推荐

通过 pip install 安装这些插件,能让你如虎添翼。

  1. pytest-html :生成美观的HTML测试报告。

    • 安装: pip install pytest-html
    • 使用:在 pytest.ini addopts 中添加 --html=reports/report.html ,或命令行直接运行 pytest --html=report.html
  2. pytest-xdist :实现测试的分布式运行,多CPU并行,大幅缩短测试时间。

    • 安装: pip install pytest-xdist
    • 使用: pytest -n auto auto 会自动检测CPU核心数)
  3. pytest-rerunfailures :对失败的测试用例进行重跑。在UI自动化或网络不稳定的接口测试中非常有用。

    • 安装: pip install pytest-rerunfailures
    • 使用: pytest --reruns 3 (失败后重跑3次)或 pytest --reruns 3 --reruns-delay 2 (每次重跑间隔2秒)
  4. pytest-ordering :控制测试用例的执行顺序(谨慎使用!测试用例在理想状态下应该是独立的)。

    • 安装: pip install pytest-ordering
    • 使用:用 @pytest.mark.run(order=1) 装饰器标记用例。
  5. pytest-base-url (或 pytest-variables ):方便地管理不同环境(测试/预发/生产)的基础URL。

    • 安装: pip install pytest-base-url
    • pytest.ini 中配置: base_url = https://test.env.com
    • 在fixture或测试用例中通过 request.config.getoption(“--base-url”) 获取。

实操心得 :不要过度依赖 pytest-ordering 。如果测试用例之间有强依赖,说明你的测试设计可能有问题。优先考虑使用fixture来管理状态和依赖。对于环境配置,我更推荐使用独立的 config.py 文件,通过环境变量来切换,这样更灵活。

5. 接口自动化实战:构建测试框架骨架

现在,我们把所有知识串联起来,搭建一个简易但完整的接口自动化测试框架骨架。这个骨架包含了配置管理、请求封装、夹具管理和测试用例。

5.1 步骤一:环境与依赖准备

创建项目目录,并安装核心依赖。

# 创建项目目录
mkdir api_auto_framework && cd api_auto_framework
# 创建虚拟环境(推荐)
python -m venv venv
# 激活虚拟环境
# Windows: venv\Scripts\activate
# Mac/Linux: source venv/bin/activate

# 创建requirements.txt并写入内容
echo “pytest>=7.0.0
requests>=2.28.0
pyyaml>=6.0
pytest-html>=3.2.0
pytest-xdist>=3.2.0
pytest-rerunfailures>=10.3” > requirements.txt

# 安装依赖
pip install -r requirements.txt

5.2 步骤二:编写配置文件与工具类

1. 环境配置 ( config/config.py )

# config/config.py
import os

class Config:
    """配置类,根据环境变量加载不同配置"""
    
    # 基础URL,通过环境变量切换
    ENV = os.getenv(“TEST_ENV”, “test”).lower()  # 默认测试环境
    
    if ENV == “prod”:
        BASE_URL = “https://api.production.com”
    elif ENV == “staging”:
        BASE_URL = “https://api.staging.com”
    else:
        BASE_URL = “https://api.test.com”
    
    # 超时时间
    TIMEOUT = 10
    
    # 日志级别
    LOG_LEVEL = “INFO”
    
    # 数据库配置(示例)
    DB_HOST = os.getenv(“DB_HOST”, “localhost”)
    DB_PORT = int(os.getenv(“DB_PORT”, 3306))

2. 请求工具封装 ( common/request_util.py ) : 封装requests库,加入日志、异常处理、通用断言,是接口自动化的关键一步。

# common/request_util.py
import requests
import logging
from config.config import Config

class RequestUtil:
    """HTTP请求工具类"""
    
    session = None
    
    def __init__(self):
        self.logger = logging.getLogger(__name__)
        if RequestUtil.session is None:
            RequestUtil.session = requests.Session()  # 使用会话保持,提升性能
            # 可以在这里为session添加默认请求头,如User-Agent
            RequestUtil.session.headers.update({
                “User-Agent”: “ApiAutoTestFramework/1.0”
            })
    
    def send_request(self, method, url, **kwargs):
        """发送请求的核心方法
        
        Args:
            method: 请求方法,'get', 'post', 'put', 'delete'
            url: 请求地址,可以是相对路径(会自动拼接BASE_URL)
            **kwargs: 传递给requests.request的其他参数,如json, params, headers
        
        Returns:
            requests.Response对象
        """
        # 如果url不是以http开头,则拼接基础URL
        if not url.startswith(“http”):
            url = Config.BASE_URL + url
        
        self.logger.info(f“请求方法: {method.upper()}”)
        self.logger.info(f“请求URL: {url}”)
        if ‘json’ in kwargs:
            self.logger.info(f“请求体: {kwargs[‘json’]}”)
        if ‘params’ in kwargs:
            self.logger.info(f“请求参数: {kwargs[‘params’]}”)
        
        try:
            response = RequestUtil.session.request(method, url, timeout=Config.TIMEOUT, **kwargs)
            self.logger.info(f“响应状态码: {response.status_code}”)
            # 注意:对于大响应体,谨慎打印,可以只打印前N个字符或特定字段
            self.logger.info(f“响应体: {response.text[:500]}...”)  # 只打印前500字符
            return response
        except requests.exceptions.RequestException as e:
            self.logger.error(f“请求发生异常: {e}”)
            raise
    
    # 提供便捷方法
    def get(self, url, **kwargs):
        return self.send_request(‘get’, url, **kwargs)
    
    def post(self, url, **kwargs):
        return self.send_request(‘post’, url, **kwargs)
    
    def put(self, url, **kwargs):
        return self.send_request(‘put’, url, **kwargs)
    
    def delete(self, url, **kwargs):
        return self.send_request(‘delete’, url, **kwargs)

5.3 步骤三:定义全局夹具(conftest.py)

在项目根目录创建 conftest.py ,定义测试用例共享的资源。

# conftest.py
import pytest
from common.request_util import RequestUtil

@pytest.fixture(scope=“session”)
def api_client():
    """提供一个全局的API请求客户端"""
    client = RequestUtil()
    yield client
    # session结束后可以做一些清理,比如关闭session(但requests.Session通常不需要)
    # 如果使用了其他需要关闭的资源,如数据库连接,在这里关闭
    print(“\n所有测试执行完毕,可进行全局清理。”)

@pytest.fixture(scope=“function”)
def login_token(api_client):
    """获取登录token,每个测试函数执行一次。这是一个依赖其他fixture的fixture。"""
    # 假设登录接口
    login_url = “/auth/login”
    login_data = {
        “username”: “standard_user”,  # 在实际项目中,应从配置或数据文件读取
        “password”: “secret_sauce”
    }
    response = api_client.post(login_url, json=login_data)
    # 这里应该做更健壮的断言,确保登录成功
    assert response.status_code == 200
    token = response.json().get(“access_token”)
    if not token:
        pytest.fail(“登录失败,未能获取到token”)
    yield token
    # 如果需要,可以在这里调用登出接口
    # api_client.post(“/auth/logout”, headers={“Authorization”: f“Bearer {token}”})

5.4 步骤四:编写并运行测试用例

现在,我们可以用上面搭建的框架来写一个真实的测试用例了。

测试用例文件 ( test_cases/test_demo_api.py )

# test_cases/test_demo_api.py
import pytest
import allure  # 可选:使用allure报告增强描述

class TestUserAPI:
    """用户相关接口测试"""
    
    @pytest.mark.smoke
    @pytest.mark.api
    def test_get_current_user(self, api_client, login_token):
        """测试获取当前用户信息(冒烟测试)"""
        headers = {“Authorization”: f“Bearer {login_token}”}
        response = api_client.get(“/user/me”, headers=headers)
        
        # 断言状态码
        assert response.status_code == 200
        # 断言响应体结构
        user_info = response.json()
        assert “id” in user_info
        assert “username” in user_info
        assert user_info[“username”] == “standard_user”  # 根据登录用户断言
    
    @pytest.mark.parametrize(“user_id, expected_code”, [
        (1, 200),
        (99999, 404),  # 不存在的用户
        (“invalid”, 400),  # 无效的用户ID格式
    ])
    def test_get_user_by_id(self, api_client, login_token, user_id, expected_code):
        """参数化测试:根据用户ID查询用户"""
        headers = {“Authorization”: f“Bearer {login_token}”}
        response = api_client.get(f“/user/{user_id}”, headers=headers)
        assert response.status_code == expected_code

运行测试 : 在项目根目录下执行:

# 运行所有测试
pytest

# 运行带有smoke标记的测试
pytest -m smoke

# 运行测试并生成HTML报告(已在pytest.ini配置)
pytest
# 报告会生成在 reports/report.html

# 使用2个worker并行运行测试
pytest -n 2

# 运行失败重试3次
pytest --reruns 3

6. 常见问题与排查技巧实录

在实际使用pytest进行接口自动化的过程中,你一定会遇到各种各样的问题。这里记录了一些典型问题和我踩过的坑。

6.1 问题一:测试用例发现了,但为什么不执行?

现象 :运行 pytest 命令后,输出显示“collected 0 items”。 排查

  1. 检查命名 :首先确认你的测试文件和测试函数/方法是否遵循了命名规则( test_ 开头或 _test.py 结尾)。
  2. 检查 __init__.py :确保测试文件所在的目录或其父目录中存在 __init__.py 文件(可以是空文件)。虽然新版本Python的命名空间包不一定需要,但pytest的某些发现机制下,有它会更保险。
  3. 检查导入路径 :如果测试文件在子目录中,确保项目根目录在Python的模块搜索路径中。通常,在项目根目录下运行 pytest 即可。
  4. 使用 pytest --collect-only :这个命令会显示pytest发现了哪些测试项,但不执行。你可以用它来确认你的测试是否被正确识别。

6.2 问题二:Fixture找不到或报错“fixture ‘xxx‘ not found”

现象 :测试用例中引用的fixture名称报错。 排查

  1. 检查fixture定义位置 :确保fixture定义在测试文件内,或者定义在 conftest.py 中,并且该 conftest.py 位于测试文件的当前目录或父目录中。pytest的发现机制是向上查找的。
  2. 检查fixture作用域 :如果你在一个 scope=”session” 的fixture中,引用了另一个 scope=”function” 的fixture,这是可以的(session的生命周期覆盖function)。但反过来不行(function不能引用session?这里表述有误,function可以引用session,因为session生命周期更长)。实际上,fixture可以引用作用域相同或更广的fixture。
  3. 检查fixture名称拼写 :最简单也最容易犯的错误。

6.3 问题三:参数化测试时,大量测试数据导致报告混乱

现象 :用了 @pytest.mark.parametrize 后,测试报告里每个数据组合都显示为一条独立的测试,名字是 test_func[param1] test_func[param2] ,当数据很多时,报告可读性差。 解决方案 : 使用 pytest.param ids 参数来美化测试ID。

import pytest

@pytest.mark.parametrize(“username, password, expected”, [
    pytest.param(“admin”, “admin123”, 200, id=“correct_admin”),
    pytest.param(“admin”, “wrong”, 401, id=“wrong_pwd”),
    pytest.param(“”, “admin123”, 400, id=“empty_user”),
], ids=[“正确管理员”, “错误密码”, “空用户名”]) # ids参数可以覆盖pytest.param中的id
def test_login(username, password, expected):
    pass

这样在报告中,测试项会显示为 test_login[正确管理员] ,而不是 test_login[admin-admin123-200] ,一目了然。

6.4 问题四:如何优雅地处理测试数据?

痛点 :测试数据硬编码在Python文件中,难以维护,非技术人员无法参与数据准备。 最佳实践 :数据与代码分离。

  1. 使用YAML/JSON文件 :对于结构化的数据,YAML的可读性更好。
    # test_data/login_cases.yaml
    - case_id: TC_LOGIN_001
      title: “正常登录”
      username: “standard_user”
      password: “secret_sauce”
      expected_code: 200
      expected_msg: “登录成功”
    - case_id: TC_LOGIN_002
      title: “密码错误”
      username: “standard_user”
      password: “wrong”
      expected_code: 401
      expected_msg: “密码错误”
    
  2. 在Fixture中读取数据
    # conftest.py
    import pytest
    import yaml
    import os
    
    @pytest.fixture(scope=“session”)
    def login_test_data():
        data_file = os.path.join(os.path.dirname(__file__), “test_data”, “login_cases.yaml”)
        with open(data_file, ‘r’, encoding=‘utf-8’) as f:
            data = yaml.safe_load(f)
        return data
    
  3. 在参数化中引用
    # test_login.py
    import pytest
    
    class TestLogin:
        
        @pytest.mark.parametrize(“case”, login_test_data(), ids=lambda case: case[‘title’])
        def test_login(self, api_client, case):
            response = api_client.post(“/login”, json={
                “username”: case[‘username’],
                “password”: case[‘password’]
            })
            assert response.status_code == case[‘expected_code’]
            assert response.json()[‘message’] == case[‘expected_msg’]
    
    注意:这里 login_test_data 需要是一个返回列表的fixture,并且作用域至少是 module session

6.5 问题五:测试依赖外部服务不稳定,导致偶发性失败

场景 :被测接口依赖第三方服务或数据库,有时响应慢或超时,导致测试不是由于业务逻辑错误,而是由于环境问题失败。 解决方案

  1. 使用 pytest-rerunfailures 插件 :这是最简单直接的方法,给不稳定的用例一次“复活”机会。 pytest --reruns 3 --reruns-delay 2
  2. Mock(模拟) :对于非核心的、不稳定的依赖,可以在单元测试或集成测试中将其Mock掉。使用 unittest.mock pytest-mock 插件。这要求你对代码结构有一定控制力。
  3. 增加超时和重试逻辑 :在你封装的请求工具类(如 RequestUtil )中,加入重试机制。可以使用 tenacity 库或 requests 的适配器来实现。
  4. 环境隔离 :尽可能搭建稳定、独立的测试环境。对于接口自动化,使用Mock Server(如WireMock, Mockoon)来模拟依赖的上下游服务,是当前非常流行的做法。

踩过这些坑之后,我的体会是,pytest框架本身并不复杂,难的是如何利用它的特性,结合良好的工程实践(如分层设计、数据分离、依赖管理),构建出一个健壮、可维护、高效的自动化测试框架。今天的这些内容,从规则到配置,从标记到实战,希望能为你铺平接口自动化测试的道路。记住,框架是工具,清晰的测试思路和良好的代码结构才是灵魂。

更多推荐