软件测试接口测试从入门到精通:Python接口自动化 - pytest测试框架

文章目录

14 Python接口自动化 - pytest测试框架

🎯 本章目标:掌握pytest测试框架,学会用pytest编写专业的接口自动化测试。


14.1 什么是pytest

pytest 是Python最流行的测试框架,简洁、强大、易用。

pytest vs unittest

特性 pytest unittest
语法 简洁(assert) 繁琐(self.assertEqual)
插件 丰富 较少
参数化 内置支持 需额外实现
夹具(Fixture) 强大 setUp/tearDown
社区 活跃 标准库

14.2 pytest 基础

第一个测试

python 复制代码
# test_demo.py

def add(x, y):
    return x + y

def test_add():
    assert add(1, 2) == 3

def test_add_negative():
    assert add(-1, -2) == -3

运行测试

bash 复制代码
# 运行当前目录所有测试
pytest

# 运行指定文件
pytest test_demo.py

# 运行指定函数
pytest test_demo.py::test_add

# 显示详细信息
pytest -v

# 生成HTML报告
pytest --html=report.html

14.3 夹具(Fixture)

什么是Fixture

Fixture 是pytest的核心特性,用于准备测试环境(如创建数据库连接、初始化数据等)。

基础Fixture

python 复制代码
import pytest
import requests

@pytest.fixture
def base_url():
    """提供基础URL"""
    return "https://api.example.com"

@pytest.fixture
def session():
    """创建HTTP会话"""
    s = requests.Session()
    s.headers.update({"Content-Type": "application/json"})
    yield s  # yield前是setup,yield后是teardown
    s.close()

@pytest.fixture
def auth_token(session, base_url):
    """获取认证token"""
    response = session.post(f"{base_url}/api/v1/login", json={
        "username": "admin",
        "password": "123456"
    })
    return response.json()["data"]["token"]

# 使用Fixture
def test_get_user(session, base_url, auth_token):
    session.headers.update({"Authorization": f"Bearer {auth_token}"})
    response = session.get(f"{base_url}/api/v1/users/me")
    assert response.status_code == 200

Fixture作用域

python 复制代码
@pytest.fixture(scope="function")  # 每个测试函数执行一次(默认)
@pytest.fixture(scope="class")     # 每个测试类执行一次
@pytest.fixture(scope="module")    # 每个模块执行一次
@pytest.fixture(scope="session")   # 整个测试会话执行一次

14.4 参数化测试

基础参数化

python 复制代码
import pytest

@pytest.mark.parametrize("username,password,expected", [
    ("admin", "123456", 0),
    ("admin", "wrong", 1001),
    ("", "123456", 1002),
    ("test", "123", 1003),
])
def test_login(username, password, expected):
    """参数化登录测试"""
    result = login(username, password)
    assert result["code"] == expected

多参数参数化

python 复制代码
@pytest.mark.parametrize("user,expected_status,expected_msg", [
    ({"username": "admin", "password": "123456"}, 200, "成功"),
    ({"username": "admin", "password": "wrong"}, 401, "密码错误"}),
    ({"username": "", "password": "123456"}, 400, "用户名不能为空"}),
])
def test_login_complex(user, expected_status, expected_msg):
    response = requests.post(url, json=user)
    assert response.status_code == expected_status
    assert expected_msg in response.json()["message"]

14.5 标记(Marker)

自定义标记

python 复制代码
import pytest

@pytest.mark.smoke  # 冒烟测试
@pytest.mark.api    # API测试
@pytest.mark.login  # 登录相关
def test_login():
    pass

@pytest.mark.slow   # 慢测试
def test_heavy_load():
    pass

运行指定标记

bash 复制代码
# 只运行冒烟测试
pytest -m smoke

# 排除慢测试
pytest -m "not slow"

# 运行多个标记
pytest -m "smoke and api"

配置标记(pytest.ini)

ini 复制代码
[pytest]
markers =
    smoke: 冒烟测试
    api: API接口测试
    login: 登录相关
    slow: 慢测试

14.6 钩子函数(Hook)

常用钩子

python 复制代码
# conftest.py

import pytest

def pytest_collection_modifyitems(config, items):
    """收集测试用例后执行"""
    # 自动添加标记
    for item in items:
        if "login" in item.nodeid:
            item.add_marker(pytest.mark.login)

def pytest_runtest_setup(item):
    """每个测试用例执行前"""
    print(f"\n开始执行: {item.name}")

def pytest_runtest_teardown(item, nextitem):
    """每个测试用例执行后"""
    print(f"执行结束: {item.name}")

14.7 完整的接口自动化项目

项目结构

复制代码
api_test/
├── pytest.ini          # pytest配置
├── conftest.py         # 全局Fixture和钩子
├── requirements.txt    # 依赖
├── config/
│   └── settings.py     # 配置文件
├── api/
│   ├── __init__.py
│   ├── base_api.py     # 基础API封装
│   ├── user_api.py     # 用户模块API
│   └── order_api.py    # 订单模块API
├── tests/
│   ├── __init__.py
│   ├── test_user.py    # 用户测试
│   └── test_order.py   # 订单测试
├── data/
│   ├── users.csv       # 测试数据
│   └── orders.json
└── reports/            # 测试报告

配置文件

config/settings.py

python 复制代码
import os

class Settings:
    BASE_URL = os.getenv("BASE_URL", "https://api.example.com")
    TIMEOUT = int(os.getenv("TIMEOUT", "10"))
    USERNAME = os.getenv("TEST_USERNAME", "admin")
    PASSWORD = os.getenv("TEST_PASSWORD", "123456")

settings = Settings()

基础API封装

api/base_api.py

python 复制代码
import requests
from config.settings import settings

class BaseAPI:
    def __init__(self):
        self.session = requests.Session()
        self.session.headers.update({
            "Content-Type": "application/json",
            "Accept": "application/json"
        })
        self.base_url = settings.BASE_URL
    
    def request(self, method, path, **kwargs):
        url = f"{self.base_url}{path}"
        kwargs.setdefault("timeout", settings.TIMEOUT)
        response = self.session.request(method, url, **kwargs)
        return response
    
    def get(self, path, **kwargs):
        return self.request("GET", path, **kwargs)
    
    def post(self, path, **kwargs):
        return self.request("POST", path, **kwargs)
    
    def put(self, path, **kwargs):
        return self.request("PUT", path, **kwargs)
    
    def delete(self, path, **kwargs):
        return self.request("DELETE", path, **kwargs)

用户模块API

api/user_api.py

python 复制代码
from api.base_api import BaseAPI

class UserAPI(BaseAPI):
    def login(self, username, password):
        return self.post("/api/v1/login", json={
            "username": username,
            "password": password
        })
    
    def create_user(self, user_data):
        return self.post("/api/v1/users", json=user_data)
    
    def get_user(self, user_id):
        return self.get(f"/api/v1/users/{user_id}")
    
    def update_user(self, user_id, user_data):
        return self.put(f"/api/v1/users/{user_id}", json=user_data)
    
    def delete_user(self, user_id):
        return self.delete(f"/api/v1/users/{user_id}")

测试用例

tests/test_user.py

python 复制代码
import pytest
from api.user_api import UserAPI
from config.settings import settings

@pytest.fixture
def user_api():
    api = UserAPI()
    # 登录获取token
    response = api.login(settings.USERNAME, settings.PASSWORD)
    token = response.json()["data"]["token"]
    api.session.headers.update({"Authorization": f"Bearer {token}"})
    return api

class TestUser:
    
    def test_create_user(self, user_api):
        """测试创建用户"""
        data = {
            "username": "testuser001",
            "email": "test001@example.com",
            "password": "123456"
        }
        response = user_api.create_user(data)
        
        assert response.status_code == 201
        result = response.json()
        assert result["code"] == 0
        assert result["data"]["username"] == "testuser001"
        
        # 保存用户ID
        pytest.user_id = result["data"]["id"]
    
    def test_get_user(self, user_api):
        """测试获取用户"""
        user_id = getattr(pytest, "user_id", 1)
        response = user_api.get_user(user_id)
        
        assert response.status_code == 200
        assert response.json()["data"]["id"] == user_id
    
    @pytest.mark.parametrize("username,expected_code", [
        ("ab", 400),      # 太短
        ("a" * 21, 400),  # 太长
        ("", 400),        # 为空
    ])
    def test_create_user_invalid_username(self, user_api, username, expected_code):
        """测试无效用户名"""
        data = {
            "username": username,
            "email": "test@example.com",
            "password": "123456"
        }
        response = user_api.create_user(data)
        assert response.status_code == expected_code

conftest.py

python 复制代码
import pytest

def pytest_configure(config):
    """pytest配置"""
    config.addinivalue_line("markers", "smoke: 冒烟测试")
    config.addinivalue_line("markers", "api: API测试")

def pytest_collection_modifyitems(config, items):
    """自动添加标记"""
    for item in items:
        if "test_" in item.nodeid:
            item.add_marker(pytest.mark.api)

@pytest.fixture(scope="session", autouse=True)
def setup_session():
    """会话级setup"""
    print("\n=== 测试会话开始 ===")
    yield
    print("\n=== 测试会话结束 ===")

pytest.ini

ini 复制代码
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short --strict-markers
markers =
    smoke: 冒烟测试
    api: API接口测试
    slow: 慢测试

14.8 生成测试报告

HTML报告

bash 复制代码
# 安装插件
pip install pytest-html

# 生成报告
pytest --html=reports/report.html --self-contained-html

Allure报告

bash 复制代码
# 安装
pip install allure-pytest

# 运行测试并生成Allure数据
pytest --alluredir=reports/allure

# 生成并打开报告
allure serve reports/allure

14.9 本章小结

pytest核心特性

#mermaid-svg-fq6jSU1fDazaMQM4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fq6jSU1fDazaMQM4 .error-icon{fill:#552222;}#mermaid-svg-fq6jSU1fDazaMQM4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fq6jSU1fDazaMQM4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fq6jSU1fDazaMQM4 .marker.cross{stroke:#333333;}#mermaid-svg-fq6jSU1fDazaMQM4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fq6jSU1fDazaMQM4 p{margin:0;}#mermaid-svg-fq6jSU1fDazaMQM4 .edge{stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .section--1 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section--1 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section--1 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section--1 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section--1 text{fill:#ffffff;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth--1{stroke-width:17;}#mermaid-svg-fq6jSU1fDazaMQM4 .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-0 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-0 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-0 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-0 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-0 text{fill:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon-0{font-size:40px;color:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth-0{stroke-width:14;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-1 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-1 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-1 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-1 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-1 text{fill:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon-1{font-size:40px;color:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth-1{stroke-width:11;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-2 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-2 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-2 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-2 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-2 text{fill:#ffffff;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth-2{stroke-width:8;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-3 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-3 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-3 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-3 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-3 text{fill:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon-3{font-size:40px;color:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth-3{stroke-width:5;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-4 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-4 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-4 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-4 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-4 text{fill:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon-4{font-size:40px;color:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth-4{stroke-width:2;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-5 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-5 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-5 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-5 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-5 text{fill:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon-5{font-size:40px;color:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth-5{stroke-width:-1;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-6 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-6 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-6 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-6 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-6 text{fill:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon-6{font-size:40px;color:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth-6{stroke-width:-4;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-7 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-7 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-7 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-7 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-7 text{fill:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon-7{font-size:40px;color:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth-7{stroke-width:-7;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-8 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-8 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-8 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-8 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-8 text{fill:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon-8{font-size:40px;color:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth-8{stroke-width:-10;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-9 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-9 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-9 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-9 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-9 text{fill:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon-9{font-size:40px;color:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth-9{stroke-width:-13;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-10 rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-10 path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-10 circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-10 polygon,#mermaid-svg-fq6jSU1fDazaMQM4 .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-10 text{fill:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .node-icon-10{font-size:40px;color:black;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .edge-depth-10{stroke-width:-16;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled circle,#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:lightgray;}#mermaid-svg-fq6jSU1fDazaMQM4 .disabled text{fill:#efefef;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-root rect,#mermaid-svg-fq6jSU1fDazaMQM4 .section-root path,#mermaid-svg-fq6jSU1fDazaMQM4 .section-root circle,#mermaid-svg-fq6jSU1fDazaMQM4 .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-fq6jSU1fDazaMQM4 .section-root text{fill:#ffffff;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-root span{color:#ffffff;}#mermaid-svg-fq6jSU1fDazaMQM4 .section-2 span{color:#ffffff;}#mermaid-svg-fq6jSU1fDazaMQM4 .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-fq6jSU1fDazaMQM4 .edge{fill:none;}#mermaid-svg-fq6jSU1fDazaMQM4 .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-fq6jSU1fDazaMQM4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} pytest
基础
assert断言
自动发现
详细报告
Fixture
作用域控制
setup/teardown
依赖注入
参数化
多组数据
多参数
自动展开
标记
自定义marker
选择性执行
分类管理
插件
HTML报告
Allure报告
覆盖率

课后练习 📝

  1. 基础题:用pytest编写一个计算器测试,包含加、减、乘、除。

  2. 进阶题:按照上面的项目结构,搭建一个完整的接口自动化项目。


14.10 下章预告

下一章我们将学习Java + RestAssured进行接口自动化!


"pytest让Python测试变得简单而优雅,它是接口自动化测试的最佳搭档。"

相关推荐
DXM05211 小时前
第14期|高阶分割模型:Transformer/SegFormer遥感应用
人工智能·python·神经网络·算法·计算机视觉·cnn·ageo
程序员龙叔1 小时前
从 0 开始学习 AI 测试 - 从接口测试来教你如何用 AI 来生成自动化测试代码
自动化测试·软件测试·python·软件测试工程师·测试工具·性能测试·ai测试
ZHW_AI课题组1 小时前
Python 调用百度智能云 API 实现地址识别
开发语言·人工智能·python·机器学习·百度·数据挖掘
cypking1 小时前
从零搭建 Claude Code + Chrome MCP 浏览器自动化:前端 E2E 端到端测试完整教程(包含增量测试)
前端·chrome·自动化
志栋智能2 小时前
从固定周期到动态触发:超自动化巡检的智能调度
运维·网络·自动化
MemoriKu2 小时前
Flutter 本地 AI 相册工程收口:从屏幕常亮、标签体系到照片属性后台队列
大数据·人工智能·python·flutter·elasticsearch·搜索引擎·数据库架构
曦尧2 小时前
GitHub - NVIDIA/SkillSpector: AI agent skill 安全扫描器。检测漏洞、恶意模式和安全风险。· GitHub
ai·自动化
2401_885665192 小时前
基于OpenCV的模板匹配OCR实战:银行卡与身份证数字识别完整教程
人工智能·python·opencv·计算机视觉·ocr
装不满的克莱因瓶2 小时前
了解3D卷积原理——从空间感知到时空建模的深度学习核心算子
人工智能·pytorch·python·深度学习·机器学习·3d·ai