前置知识: 软件工程与测试

API 自动化测试

3 min中级

API自动化测试:RESTful API测试、工具选型、断言策略与框架设计详解。

1. API 测试概述

1.1 什么是 API 测试

API 测试是在没有用户界面的情况下,直接对应用程序编程接口进行测试,验证接口的功能、性能和安全性。 它绕过浏览器渲染层,执行快、定位准,是自动化测试投入产出比最高的一层。

前置知识:HTTP 协议基础(方法、状态码、请求头)、JSON、pytest 基本用法。

1.2 与 UI 测试对比

对比项API 测试UI 测试
速度快(毫秒)慢(秒)
稳定性高低
覆盖率高中
维护成本低高
反馈速度快慢

1.3 测试层级

契约测试 → 功能测试 → 集成测试 → 端到端测试

2. 工具选型

工具语言特点
PostmanGUI易上手、Collection
REST AssuredJava强大、灵活
Requests + pytestPython轻量、灵活
SuperTestNode.jsJS 生态
KarateDSLBDD 风格
HttpRunnerPython中文友好

3. Python API 测试框架

3.1 基础封装

import requests

class APIClient:
    def __init__(self, base_url):
        self.base_url = base_url
        self.session = requests.Session()
        self.token = None

    def set_token(self, token):
        self.token = token
        self.session.headers.update({"Authorization": f"Bearer {token}"})

    def get(self, path, **kwargs):
        return self.session.get(f"{self.base_url}{path}", **kwargs)

    def post(self, path, json=None, **kwargs):
        return self.session.post(f"{self.base_url}{path}", json=json, **kwargs)

    def put(self, path, json=None, **kwargs):
        return self.session.put(f"{self.base_url}{path}", json=json, **kwargs)

    def delete(self, path, **kwargs):
        return self.session.delete(f"{self.base_url}{path}", **kwargs)

3.2 测试用例

import pytest

@pytest.fixture
def api():
    client = APIClient("https://api.example.com")
    # 登录获取 Token
    response = client.post("/auth/login", json={
        "username": "admin",
        "password": "password123"
    })
    client.set_token(response.json()["token"])
    return client

class TestUserAPI:

    def test_create_user(self, api):
        response = api.post("/api/users", json={
            "name": "Alice",
            "email": "alice@example.com"
        })
        assert response.status_code == 201
        data = response.json()
        assert data["name"] == "Alice"
        assert "id" in data

    def test_get_user(self, api):
        # 先创建
        create_resp = api.post("/api/users", json={
            "name": "Bob", "email": "bob@example.com"
        })
        user_id = create_resp.json()["id"]

        # 再查询
        response = api.get(f"/api/users/{user_id}")
        assert response.status_code == 200
        assert response.json()["name"] == "Bob"

    def test_update_user(self, api):
        create_resp = api.post("/api/users", json={
            "name": "Charlie", "email": "charlie@example.com"
        })
        user_id = create_resp.json()["id"]

        response = api.put(f"/api/users/{user_id}", json={
            "name": "Charlie Updated"
        })
        assert response.status_code == 200
        assert response.json()["name"] == "Charlie Updated"

    def test_delete_user(self, api):
        create_resp = api.post("/api/users", json={
            "name": "David", "email": "david@example.com"
        })
        user_id = create_resp.json()["id"]

        response = api.delete(f"/api/users/{user_id}")
        assert response.status_code == 204

        # 验证已删除
        get_resp = api.get(f"/api/users/{user_id}")
        assert get_resp.status_code == 404

4. 断言策略

4.1 状态码断言

assert response.status_code == 200
assert response.status_code in [200, 201]

4.2 响应体断言

# JSON Schema 验证
from jsonschema import validate

schema = {
    "type": "object",
    "required": ["id", "name", "email"],
    "properties": {
        "id": {"type": "integer"},
        "name": {"type": "string"},
        "email": {"type": "string", "format": "email"}
    }
}

validate(instance=response.json(), schema=schema)

4.3 响应时间断言

assert response.elapsed.total_seconds() < 2.0

4.4 响应头断言

assert "application/json" in response.headers["Content-Type"]
assert "X-Request-Id" in response.headers

5. 数据驱动测试

5.1 YAML 数据文件

# testdata/users.yaml
create_user:
  - name: 'Alice'
    email: 'alice@example.com'
    expected_status: 201
  - name: ''
    email: 'invalid'
    expected_status: 400
  - name: 'Bob'
    email: ''
    expected_status: 400

5.2 参数化测试

import yaml

@pytest.fixture
def user_test_data():
    with open("testdata/users.yaml") as f:
        return yaml.safe_load(f)["create_user"]

@pytest.mark.parametrize("data", user_test_data(), ids=lambda d: d["name"])
def test_create_user_data_driven(api, data):
    response = api.post("/api/users", json={
        "name": data["name"],
        "email": data["email"]
    })
    assert response.status_code == data["expected_status"]

6. 契约测试

6.1 Pact 框架

from pact import Consumer, Provider

pact = Consumer('WebApp').has_pact_with(Provider('API'))

(pact
 .given('user exists')
 .upon_receiving('a request for user')
 .with_request('GET', '/api/users/1')
 .will_respond_with(200, body={
     'id': 1,
     'name': 'Alice'
 }))

with pact:
    result = api.get('/api/users/1')
    assert result.json()['name'] == 'Alice'

7. 常见陷阱

陷阱说明与规避
只断言状态码200 不代表业务正确,必须同时校验响应体关键字段
不设超时requests 默认不超时,一条挂起请求能卡死整个流水线;始终传 timeout=5 这类显式值
用例间数据耦合用例 B 依赖用例 A 创建的数据,并行执行必然互踩;每条用例自建自清
硬编码环境地址base_url、账号一律走配置或环境变量,本地/CI 各跑各的
忽略响应时间以外的性能信号偶发的慢接口要靠多次采样与分位值(P95/P99)观察,单次 elapsed 不可靠

8. 最佳实践

实践描述
分层封装Client → Service → Test
数据工厂自动创建测试数据
清理数据测试后清理
环境隔离测试环境独立
幂等设计重复执行结果一致
并发安全测试间无依赖
日志记录请求/响应完整记录

小结

  • 初学者要点:用 requests.Session 封装一个带 base_url 与鉴权头的 APIClient,每条用例「发请求 → 断状态码 → 断响应体」,配上 pytest 参数化做数据驱动,就是一套可用的 API 自动化框架。
  • 进阶注意:断言要「结构 + 关键值」两层;契约测试(Pact)把「消费者 期望什么」固化为可回归的契约,适合微服务间的接口防护网;性能维度 交给专门的压测工具(JMeter/k6/Locust),不要在功能用例里顺带压测。