API 测试(API Testing)

API 测试(API Testing)

一句话总结:测试应用程序接口的功能、性能、安全性和可靠性。

🌟 快速理解(小白入门)

用生活化类比

就像测试餐厅的点餐系统

  • 前端(UI) = 顾客看到的菜单和服务员
  • API = 厨房和前台之间的对讲机
  • 后端 = 厨房

API 测试就是测试对讲机

1
2
3
4
5
6
7
8
顾客点餐:"一份宫保鸡丁"

服务员通过对讲机告诉厨房:"一份宫保鸡丁"

厨房收到:"一份宫保鸡丁" ✅
厨房做好后通过对讲机回复:"宫保鸡丁做好了"

服务员收到:"宫保鸡丁做好了" ✅

API 测试要验证

  • ✅ 对讲机能否正常通话(功能测试)
  • ✅ 对讲机响应速度(性能测试)
  • ✅ 对讲机是否加密(安全测试)
  • ✅ 对讲机能否同时处理多个订单(并发测试)

真实场景

电商系统 API 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
用户操作:点击"加入购物车"按钮

前端发送 API 请求:
POST /api/cart/add
{
"product_id": 123,
"quantity": 1
}

后端处理并返回:
{
"success": true,
"cart_count": 5,
"message": "商品已加入购物车"
}

前端显示:"购物车(5)"

API 测试要验证

  • ✅ 请求格式正确
  • ✅ 响应格式正确
  • ✅ 数据库更新正确
  • ✅ 响应时间 < 500ms
  • ✅ 错误处理正确

📌 核心概念

什么是 API?

API(Application Programming Interface):应用程序接口,是不同软件系统之间通信的桥梁。

通俗解释

  • 前端:用户看到的界面(网页、App)
  • 后端:服务器、数据库
  • API:前端和后端之间的”翻译官”

什么是 API 测试?

API 测试:测试 API 的功能、性能、安全性和可靠性。

关键特征

特征 说明 示例
黑盒测试 不关心内部实现 只测试输入输出
独立于 UI 不需要界面 直接测试接口
快速 比 UI 测试快 10 倍 毫秒级响应
易于自动化 适合 CI/CD 每次提交自动测试
早期测试 前端开发前就能测试 并行开发

🎯 为什么需要 API 测试?

真实案例

案例 1:Cloudflare 宕机事件

时间:2020年7月17日

问题:API 配置错误导致全球网站宕机。

原因

  • API 测试不充分
  • 配置变更未经测试
  • 错误的路由规则

影响:全球数百万网站无法访问,持续27分钟

教训:API 测试必须包含配置测试。


案例 2:Twitter API 限流问题

时间:2023年7月

问题:API 限流导致第三方应用无法使用。

原因

  • API 限流策略变更
  • 未充分测试限流逻辑
  • 未通知第三方开发者

影响:数千个第三方应用停止工作

教训:API 变更必须充分测试并提前通知。


行业数据

研究机构 数据 说明
SmartBear 82% 的公司使用 API API 是现代软件的核心
Postman API 调用量每年增长 143% API 使用量快速增长
Google API 测试比 UI 测试快 10 倍 API 测试更高效

✅ API 测试的优势

优势 1:快速反馈 ⚡

通俗解释

就像直接问厨师菜做好了没有,而不是等服务员端上来。

专业说明

  • UI 测试:需要启动浏览器、加载页面、点击按钮(慢)
  • API 测试:直接发送 HTTP 请求(快)

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import time

# UI 测试(慢)
def test_ui_add_to_cart():
driver = webdriver.Chrome()
driver.get("https://example.com")
driver.find_element(By.ID, "login").click()
# ... 10+ 步操作
# 耗时:10-30 秒

# API 测试(快)
def test_api_add_to_cart():
response = requests.post("https://api.example.com/cart/add", json={
"product_id": 123,
"quantity": 1
}, headers={"Authorization": "Bearer token"})
assert response.status_code == 200
# 耗时:100-500 毫秒

# 速度对比:API 测试快 20-60 倍

优势 2:早期测试 🔍

通俗解释

就像在装修前先测试水电,而不是等装修完再测试。

专业说明

  • 前端开发前就能测试 API
  • 前后端并行开发
  • 早期发现问题,修复成本低

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 前端还没开发,但 API 已经可以测试
def test_user_registration_api():
"""测试用户注册 API"""
response = requests.post("https://api.example.com/register", json={
"username": "testuser",
"email": "test@example.com",
"password": "password123"
})

# 验证响应
assert response.status_code == 201
assert "user_id" in response.json()
assert "token" in response.json()

# 验证数据库
user = db.query(f"SELECT * FROM users WHERE email = 'test@example.com'")
assert user is not None
assert user.username == "testuser"

# 前端开发时,API 已经测试通过 ✅

优势 3:易于自动化 🤖

通俗解释

就像设置自动回复,而不是每次都手动回复。

专业说明

  • API 测试容易集成到 CI/CD
  • 每次代码提交自动测试
  • 快速发现回归问题

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# .github/workflows/api-test.yml
name: API Tests

on: [push, pull_request]

jobs:
api-test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9

- name: Install dependencies
run: pip install -r requirements.txt

- name: Run API tests
run: pytest tests/api/ -v

- name: Generate coverage report
run: pytest tests/api/ --cov=api --cov-report=html

- name: Upload coverage
uses: codecov/codecov-action@v2

🔄 API 测试的类型

1. 功能测试

定义:验证 API 的功能是否正确。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def test_get_product():
"""测试获取商品详情"""
response = requests.get("https://api.example.com/products/123")

assert response.status_code == 200
assert response.json()["id"] == 123
assert response.json()["name"] == "iPhone 15"
assert response.json()["price"] == 5999

def test_create_order():
"""测试创建订单"""
response = requests.post("https://api.example.com/orders", json={
"product_id": 123,
"quantity": 1,
"address": "北京市朝阳区xxx"
}, headers={"Authorization": "Bearer token"})

assert response.status_code == 201
assert "order_id" in response.json()
assert response.json()["status"] == "pending"

🛠️ API 测试工具推荐

Python 生态

工具 类型 特点 推荐度
Requests 简单、易用 ⭐⭐⭐⭐⭐
Pytest 框架 强大、灵活 ⭐⭐⭐⭐⭐
Tavern 框架 YAML 配置 ⭐⭐⭐⭐
Locust 性能测试 易用、可扩展 ⭐⭐⭐⭐⭐

JavaScript 生态

工具 类型 特点 推荐度
Axios 简单、Promise ⭐⭐⭐⭐⭐
Jest 框架 快速、易用 ⭐⭐⭐⭐⭐
Supertest Express 测试 ⭐⭐⭐⭐
K6 性能测试 现代、强大 ⭐⭐⭐⭐⭐

GUI 工具

工具 类型 特点 推荐度
Postman GUI 易用、功能强大 ⭐⭐⭐⭐⭐
Insomnia GUI 简洁、美观 ⭐⭐⭐⭐
SoapUI GUI SOAP/REST ⭐⭐⭐⭐

Pytest + Requests 示例(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import pytest
import requests

BASE_URL = "https://api.example.com"

class TestProductAPI:

@pytest.fixture(scope="class")
def auth_token(self):
"""获取认证 token"""
response = requests.post(f"{BASE_URL}/login", json={
"email": "test@example.com",
"password": "password123"
})
return response.json()["token"]

def test_get_products(self, auth_token):
"""测试获取商品列表"""
response = requests.get(
f"{BASE_URL}/products",
headers={"Authorization": f"Bearer {auth_token}"}
)

assert response.status_code == 200
assert "products" in response.json()
assert len(response.json()["products"]) > 0

def test_get_product_by_id(self, auth_token):
"""测试获取商品详情"""
response = requests.get(
f"{BASE_URL}/products/123",
headers={"Authorization": f"Bearer {auth_token}"}
)

assert response.status_code == 200
product = response.json()
assert product["id"] == 123
assert "name" in product
assert "price" in product

def test_create_product(self, auth_token):
"""测试创建商品"""
response = requests.post(
f"{BASE_URL}/products",
json={
"name": "Test Product",
"price": 99.99,
"stock": 100
},
headers={"Authorization": f"Bearer {auth_token}"}
)

assert response.status_code == 201
product = response.json()
assert product["name"] == "Test Product"
assert product["price"] == 99.99

def test_update_product(self, auth_token):
"""测试更新商品"""
response = requests.put(
f"{BASE_URL}/products/123",
json={"price": 199.99},
headers={"Authorization": f"Bearer {auth_token}"}
)

assert response.status_code == 200
product = response.json()
assert product["price"] == 199.99

def test_delete_product(self, auth_token):
"""测试删除商品"""
response = requests.delete(
f"{BASE_URL}/products/123",
headers={"Authorization": f"Bearer {auth_token}"}
)

assert response.status_code == 204

Postman 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Postman 测试脚本
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});

pm.test("Response time is less than 500ms", function () {
pm.expect(pm.response.responseTime).to.be.below(500);
});

pm.test("Response has products", function () {
var jsonData = pm.response.json();
pm.expect(jsonData).to.have.property('products');
pm.expect(jsonData.products).to.be.an('array');
pm.expect(jsonData.products.length).to.be.above(0);
});

pm.test("Product has required fields", function () {
var jsonData = pm.response.json();
var product = jsonData.products[0];
pm.expect(product).to.have.property('id');
pm.expect(product).to.have.property('name');
pm.expect(product).to.have.property('price');
});

📊 最佳实践

1. 使用测试金字塔 🔺

1
2
3
4
5
6
7
      /\
/E2E\ 10% - 端到端测试
/------\
/API测试\ 30% - API 测试 ← 重点
/----------\
/ 单元测试 \ 60% - 单元测试
/--------------\

原则

  • 60% 单元测试
  • 30% API 测试
  • 10% E2E 测试

2. 遵循 REST 规范 📝

HTTP 方法

方法 用途 示例
GET 获取资源 GET /products
POST 创建资源 POST /products
PUT 更新资源(全量) PUT /products/123
PATCH 更新资源(部分) PATCH /products/123
DELETE 删除资源 DELETE /products/123

状态码

状态码 含义 使用场景
200 成功 GET、PUT、PATCH 成功
201 已创建 POST 成功
204 无内容 DELETE 成功
400 请求错误 参数错误
401 未授权 未登录
403 禁止访问 无权限
404 未找到 资源不存在
500 服务器错误 服务器异常

3. 编写清晰的测试用例 ✍️

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def test_create_order_with_valid_data():
"""
测试用例:使用有效数据创建订单

前置条件:
- 用户已登录
- 商品库存充足

测试步骤:
1. 发送创建订单请求
2. 验证响应状态码为 201
3. 验证订单 ID 存在
4. 验证订单状态为 pending
5. 验证库存扣减

预期结果:
- 订单创建成功
- 库存正确扣减
"""
# 步骤 1
response = requests.post(f"{BASE_URL}/orders", json={
"product_id": 123,
"quantity": 1
}, headers={"Authorization": f"Bearer {token}"})

# 步骤 2
assert response.status_code == 201

# 步骤 3
order = response.json()
assert "order_id" in order

# 步骤 4
assert order["status"] == "pending"

# 步骤 5
product = requests.get(f"{BASE_URL}/products/123").json()
assert product["stock"] == initial_stock - 1

4. 使用数据驱动测试 📊

1
2
3
4
5
6
7
8
9
10
@pytest.mark.parametrize("product_id,expected_status", [
(123, 200), # 存在的商品
(999, 404), # 不存在的商品
(-1, 400), # 无效的 ID
("abc", 400), # 非数字 ID
])
def test_get_product_by_id(product_id, expected_status):
"""测试获取商品详情(数据驱动)"""
response = requests.get(f"{BASE_URL}/products/{product_id}")
assert response.status_code == expected_status

5. 集成到 CI/CD 🔄

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# .github/workflows/api-test.yml
name: API Tests

on: [push, pull_request]

jobs:
api-test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:14
env:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9

- name: Install dependencies
run: pip install -r requirements.txt

- name: Run migrations
run: python manage.py migrate

- name: Run API tests
run: pytest tests/api/ -v --cov=api

- name: Upload coverage
uses: codecov/codecov-action@v2

📊 总结对比

API 测试 vs 其他测试

维度 单元测试 API 测试 E2E 测试
测试范围 单个函数 完整接口 完整流程
测试速度 快(毫秒) 中(秒) 慢(分钟)
测试成本
问题定位 容易 中等 困难
测试数量 多(60%) 中(30%) 少(10%)
依赖 数据库 全部
维护成本

🎓 学习资源

书籍推荐

  • 《RESTful Web APIs》- Leonard Richardson
  • 《API 设计最佳实践》- Arnaud Lauret
  • 《测试驱动开发》- Kent Beck

在线课程

官方文档


🔗 相关主题

  • [[单元测试]] - API 测试的基础
  • [[集成测试]] - API 测试的一种
  • [[性能测试]] - API 性能测试
  • [[自动化测试]] - API 测试的实现方式

💡 快速参考卡片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
API 测试速查表
================

定义:测试应用程序接口的功能、性能、安全性

核心优势:
✅ 快速反馈(比 UI 测试快 10 倍)
✅ 早期测试(前端开发前就能测试)
✅ 易于自动化(适合 CI/CD)

测试类型:
- 功能测试(验证功能正确)
- 性能测试(验证响应时间)
- 安全测试(验证安全性)
- 契约测试(验证符合规范)
- 集成测试(验证系统集成)

推荐工具:
- Python: Requests + Pytest
- JavaScript: Axios + Jest
- GUI: Postman

HTTP 方法:
- GET: 获取资源
- POST: 创建资源
- PUT: 更新资源(全量)
- PATCH: 更新资源(部分)
- DELETE: 删除资源

常用状态码:
- 200: 成功
- 201: 已创建
- 204: 无内容
- 400: 请求错误
- 401: 未授权
- 404: 未找到
- 500: 服务器错误

最佳实践:
1. 遵循测试金字塔(30% API 测试)
2. 遵循 REST 规范
3. 编写清晰的测试用例
4. 使用数据驱动测试
5. 集成到 CI/CD

测试金字塔:
60% 单元测试
30% API 测试 ← 你在这里
10% E2E 测试

2. 性能测试

定义:验证 API 的响应时间和吞吐量。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from locust import HttpUser, task, between

class APIUser(HttpUser):
wait_time = between(1, 3)

@task
def get_products(self):
self.client.get("/products")

@task
def search_products(self):
self.client.get("/products/search?q=iPhone")

# 运行:locust -f test_api_performance.py --users 1000 --spawn-rate 10
# 目标:
# - 响应时间 P95 < 500ms
# - 吞吐量 > 1000 TPS
# - 错误率 < 0.1%

3. 安全测试

定义:验证 API 的安全性。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def test_unauthorized_access():
"""测试未授权访问"""
response = requests.get("https://api.example.com/orders")
assert response.status_code == 401

def test_sql_injection():
"""测试 SQL 注入"""
response = requests.get(
"https://api.example.com/products/search",
params={"q": "'; DROP TABLE products; --"}
)
assert response.status_code == 200
assert "error" not in response.json()

def test_xss():
"""测试 XSS 攻击"""
response = requests.post("https://api.example.com/comments", json={
"content": "<script>alert('XSS')</script>"
}, headers={"Authorization": "Bearer token"})

# 验证内容被转义
comment = response.json()
assert "<script>" not in comment["content"]
assert "&lt;script&gt;" in comment["content"]

4. 契约测试

定义:验证 API 是否符合约定的契约(OpenAPI/Swagger)。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
from openapi_spec_validator import validate_spec

def test_api_contract():
"""测试 API 契约"""
# 获取 OpenAPI 规范
spec = requests.get("https://api.example.com/openapi.json").json()

# 验证规范有效性
validate_spec(spec)

# 验证实际响应符合规范
response = requests.get("https://api.example.com/products/123")

# 验证响应格式
assert "id" in response.json()
assert "name" in response.json()
assert "price" in response.json()
assert isinstance(response.json()["id"], int)
assert isinstance(response.json()["name"], str)
assert isinstance(response.json()["price"], (int, float))

5. 集成测试

定义:验证 API 与其他系统的集成。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def test_payment_integration():
"""测试支付集成"""
# 1. 创建订单
order_response = requests.post("https://api.example.com/orders", json={
"product_id": 123,
"quantity": 1
}, headers={"Authorization": "Bearer token"})

order_id = order_response.json()["order_id"]

# 2. 调用支付 API
payment_response = requests.post(
f"https://api.example.com/orders/{order_id}/pay",
json={"payment_method": "wechat"},
headers={"Authorization": "Bearer token"}
)

assert payment_response.status_code == 200

# 3. 验证订单状态
order = requests.get(
f"https://api.example.com/orders/{order_id}",
headers={"Authorization": "Bearer token"}
).json()

assert order["status"] == "paid"

# 4. 验证库存扣减
product = requests.get("https://api.example.com/products/123").json()
assert product["stock"] == initial_stock - 1

⚠️ API 测试的挑战

挑战 1:测试数据管理 📊

问题

  • 需要准备测试数据
  • 数据之间有依赖关系
  • 测试后需要清理数据

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pytest

@pytest.fixture(scope="function")
def test_user():
"""创建测试用户"""
response = requests.post("https://api.example.com/register", json={
"username": f"testuser_{uuid.uuid4()}",
"email": f"test_{uuid.uuid4()}@example.com",
"password": "password123"
})

user = response.json()
yield user

# 测试结束后删除用户
requests.delete(
f"https://api.example.com/users/{user['user_id']}",
headers={"Authorization": f"Bearer {user['token']}"}
)

def test_create_order(test_user):
"""测试创建订单"""
response = requests.post("https://api.example.com/orders", json={
"product_id": 123,
"quantity": 1
}, headers={"Authorization": f"Bearer {test_user['token']}"})

assert response.status_code == 201

挑战 2:环境依赖 🔧

问题

  • 需要数据库
  • 需要第三方服务(支付、短信)
  • 环境不稳定

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用 Mock 模拟第三方服务
from unittest.mock import patch, Mock

@patch('payment_service.wechat_pay')
def test_wechat_payment(mock_wechat_pay):
"""测试微信支付"""
# 模拟微信支付成功
mock_wechat_pay.return_value = {
"success": True,
"transaction_id": "wx123456789"
}

response = requests.post("https://api.example.com/orders/123/pay", json={
"payment_method": "wechat"
}, headers={"Authorization": "Bearer token"})

assert response.status_code == 200
assert response.json()["payment_status"] == "success"

挑战 3:API 版本管理 🔄

问题

  • API 版本升级
  • 需要同时测试多个版本
  • 向后兼容性

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 测试多个 API 版本
@pytest.mark.parametrize("version", ["v1", "v2", "v3"])
def test_get_products_all_versions(version):
"""测试所有版本的获取商品 API"""
response = requests.get(f"https://api.example.com/{version}/products")

assert response.status_code == 200
assert "products" in response.json()

# v1 和 v2 返回数组,v3 返回对象
if version in ["v1", "v2"]:
assert isinstance(response.json()["products"], list)
else:
assert isinstance(response.json()["products"], dict)