【8.进行接口测试】:通过 curl 进行接口自动化测试 / 通过 python 程序进行接口自动化测试

目录

[1. 总结接口设计文档](#1. 总结接口设计文档)

[2. 添加管理员账户](#2. 添加管理员账户)

[3. 通过 curl 进行接口自动化测试](#3. 通过 curl 进行接口自动化测试)

[4. 总结接口测试文档](#4. 总结接口测试文档)

[5. 把接口自动化用例整合成 python 程序](#5. 把接口自动化用例整合成 python 程序)


1. 总结接口设计文档

接口测试之前,首先需要明确接口的定义规范。

  • 有哪些接口
  • 每个接口的作用
  • 每个接口的参数
  • 每个接口的返回值

@SPEC.md 参考文档,并且结合代码中已经实现的接口,总结一份完整的接口交互文档。包含每个接口的路径、参数、返回的响应结果、作用。总结出的文档写到 API.md 中。

总结出的文档形如:

html 复制代码
# API 接口文档

## 基础信息

- **Base URL**: `/api`
- **Content-Type**: `application/json`
- **认证方式**: Session/Cookie(通过 `session_id` Cookie)

---

## 公开接口(普通用户)

### 1. 题目列表

**GET** `/api/problems`

获取所有题目列表。

**响应示例** (200):
```json
{
  "problems": [
    {
      "id": 1,
      "title": "两数之和",
      "difficulty": "Easy",
      "created_at": "2026-04-24 10:00:00"
    }
  ]
}
```

---

### 2. 题目详情

**GET** `/api/problems/:id`

获取指定题目的详细信息及测试用例。

**路径参数**:
| 参数 | 类型 | 说明 |
|------|------|------|
| id | int | 题目 ID |

**响应示例** (200):
```json
{
  "id": 1,
  "title": "两数之和",
  "difficulty": "Easy",
  "content": "给定一个整数数组 nums...",
  "template": "#include <iostream>\nusing namespace std;",
  "created_at": "2026-04-24 10:00:00",
  "test_cases": [
    {
      "id": 1,
      "input": "2 7 11 15",
      "expected": "9 15",
      "position": 0
    }
  ]
}
```

**错误响应**:
- `400`: `{"error": "invalid problem id"}`
- `404`: `{"error": "problem not found"}`

---

### 3. 提交代码

**POST** `/api/submit`

提交代码并执行。

**请求体**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| code | string | 是 | C++ 源代码 |
| problem_id | int | 是 | 题目 ID |

**响应示例** (200):
```json
{
  "status": "AC",
  "message": "All test cases passed",
  "passed": 3,
  "total": 3,
  "details": [
    {
      "case": 1,
      "exit_code": 0,
      "elapsed_ms": 15,
      "stdout": "9 15",
      "status": "AC"
    },
    {
      "case": 2,
      "exit_code": 0,
      "elapsed_ms": 12,
      "stdout": "9 15",
      "status": "AC",
      "expected": "9 15",
      "actual": "9 15"
    }
  ]
}
```

**status 枚举值**:
- `AC`: All Correct,所有测试用例通过
- `WA`: Wrong Answer,输出结果错误
- `TLE`: Time Limit Exceeded,超时
- `RE`: Runtime Error,运行时错误
- `CE`: Compilation Error,编译错误

**错误响应**:
- `400`: `{"error": "code is required"}` / `{"error": "problem_id is required"}`
- `404`: `{"error": "problem not found"}`

---

### 4. 用户注册

**POST** `/api/register`

注册新用户账号。

**请求体**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| username | string | 是 | 用户名,3-64 字符 |
| password | string | 是 | 密码,至少 6 字符 |

**响应示例** (201):
```json
{
  "message": "user registered successfully",
  "id": 1
}
```

**错误响应**:
- `400`: `{"error": "username is required"}` / `{"error": "username must be between 3 and 64 characters"}` / `{"error": "password must be at least 6 characters"}`
- `409`: `{"error": "username already exists"}`
- `500`: `{"error": "failed to create user"}`

---

### 5. 用户登录

**POST** `/api/login`

用户登录,登录成功后在 Cookie 中设置 `session_id`。

**请求体**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| username | string | 是 | 用户名 |
| password | string | 是 | 密码 |

**响应示例** (200):
```json
{
  "message": "login successful",
  "username": "testuser",
  "role": "user"
}
```

**错误响应**:
- `400`: `{"error": "username is required"}` / `{"error": "password is required"}`
- `401`: `{"error": "invalid username or password"}`

---

### 6. 用户登出

**POST** `/api/logout`

用户登出,销毁当前会话。

**请求头**:
- `Cookie`: `session_id=xxx`

**响应示例** (200):
```json
{
  "message": "logout successful"
}
```

---

## 管理接口(管理员)

> **注意**: 管理接口需要管理员权限(role 为 `admin`)

### 7. 新增题目

**POST** `/api/admin/problems`

创建新题目。

**请求头**:
- `Cookie`: `session_id=xxx`

**请求体**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| title | string | 是 | 题目标题 |
| difficulty | string | 是 | 难度等级:`Easy`、`Medium`、`Hard` |
| content | string | 是 | 题目描述(Markdown 格式) |
| template | string | 否 | 代码模板 |
| test_cases | array | 否 | 测试用例数组 |

**test_cases 数组元素**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| input | string | 是 | 输入数据 |
| expected | string | 是 | 期望输出 |
| position | int | 否 | 排序序号 |

**响应示例** (201):
```json
{
  "id": 1,
  "message": "problem created successfully"
}
```

**错误响应**:
- `400`: `{"error": "title is required"}` / `{"error": "difficulty is required"}` / `{"error": "content is required"}` / `{"error": "invalid difficulty, must be Easy, Medium, or Hard"}`
- `500`: `{"error": "failed to save problem"}`

---

### 8. 删除题目

**DELETE** `/api/admin/problems/:id`

删除指定题目。

**路径参数**:
| 参数 | 类型 | 说明 |
|------|------|------|
| id | int | 题目 ID |

**请求头**:
- `Cookie`: `session_id=xxx`

**响应示例** (200):
```json
{
  "message": "problem deleted successfully"
}
```

**错误响应**:
- `400`: `{"error": "invalid problem id"}`
- `500`: `{"error": "failed to delete problem"}`

---

## 数据模型

### 题目 (Problem)

| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 题目 ID |
| title | string | 题目标题 |
| difficulty | string | 难度:`Easy`、`Medium`、`Hard` |
| content | string | 题目描述 |
| template | string | 代码模板 |
| created_at | string | 创建时间 |

### 测试用例 (TestCase)

| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 用例 ID |
| problem_id | int | 所属题目 ID |
| input | string | 输入数据 |
| expected | string | 期望输出 |
| position | int | 排序序号 |

### 用户 (User)

| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 用户 ID |
| username | string | 用户名 |
| role | string | 角色:`user`、`admin` |
| created_at | string | 创建时间 |

---

## 错误码汇总

| HTTP 状态码 | 说明 |
|-------------|------|
| 200 | 请求成功 |
| 201 | 创建成功 |
| 400 | 请求参数错误 |
| 401 | 认证失败 |
| 404 | 资源不存在 |
| 409 | 资源冲突(如用户名已存在) |
| 500 | 服务器内部错误 |

根据这份接口文档,就可以进行接口自动化测试了。

正常来说,API接口文档应该是在正式开发之前,就确定好的。

咱们本次开发中,在创建SPEC.md阶段,当时是有看到关于接口的定义,但是没发现此处定义的接口并不完整详细。直到开发了一些功能之后才意识到这个问题。因此只能先完成开发,再补充接口文档了。

各位在开发时,要记得把接口文档提前定义好。

2. 添加管理员账户

此处我们约定,管理员用户名为: admin,密码为 :

由于我们实现了密码加密功能,因此直接在数据库中插入明文密码,是无法正确进行登录的。

而密码加密后的内容,我们又无法手动计算得出。

因此我们需要写一个辅助工具,来构造管理员账户。

实现一个小工具,往数据库中插入管理员账户(用户名是 admin,密码是:),注意密码需要调用加密的逻辑,向数据库中存储密文密码,如果 admin 用户已经存在,删除之前的用户,重新插入这个 admin 用户.

这样的小工具AI开发很快。我们运行这个程序,即可在数据库中插入好管理员信息。

3. 通过 curl 进行接口自动化测试

@API.md 根据当前文档中对于接口的描述,通过 curl 命令的方式来构造 http 请求,验证每个接口是能够正常工作的,服务端已经启动,直接进行测试.

此时,opencode 就会按照文档中的描述,通过curl命令进行接口测试。

注意:由于opencode 在创建后台进程时疑似存在bug,因此需要我们提前在另一个终端中,把oj_server 启动好,并告诉AI直接进行测试。

4. 总结接口测试文档

为了让接口自动化测试的步骤能 "固化" 下来,避免下次测试时由于 AI 的随机性导致测试不通过,我们可以让 AI 总结接口测试的过程。

将上面的步骤,总结成一个 "基于 curl 的接口自动化测试文档.md",便于未来进行重复的测试.

随着程序开发的进展,可能早期的单元测试会收到影响。因此随时多运行单元测试,及时修复,是开发时的一个重要习惯。

总结的文档形如:

html 复制代码
# 基于 curl 的接口自动化测试文档

## 基础信息

- **Base URL**: `http://localhost:8080/api`
- **Content-Type**: `application/json`
- **认证方式**: Session/Cookie(通过 `session_id` Cookie)

---

## 准备工作

### 启动服务器

```bash
./build/oj_server
```

### 创建 Cookie 文件存放目录

```bash
mkdir -p /tmp
```

---

## 公开接口测试

### 1. 获取题目列表

```bash
curl -s http://localhost:8080/api/problems
```

**预期响应**: 200 OK,返回题目列表

---

### 2. 获取题目详情

```bash
# 正常情况
curl -s http://localhost:8080/api/problems/425

# 题目不存在 (404)
curl -s http://localhost:8080/api/problems/999999
```

**预期响应**:
- 200 OK: 返回题目详情及测试用例
- 404 Not Found: `{"error": "problem not found"}`

---

### 3. 用户注册

```bash
# 正常注册
curl -s -X POST http://localhost:8080/api/register \
  -H "Content-Type: application/json" \
  -d '{"username":"testuser","password":"123456"}'

# 用户名过短 (400)
curl -s -X POST http://localhost:8080/api/register \
  -H "Content-Type: application/json" \
  -d '{"username":"ab","password":"123456"}'

# 用户名已存在 (409)
curl -s -X POST http://localhost:8080/api/register \
  -H "Content-Type: application/json" \
  -d '{"username":"testuser","password":"123456"}'
```

**预期响应**:
- 201 Created: `{"message": "user registered successfully", "id": 1}`
- 400 Bad Request: `{"error": "username must be between 3 and 64 characters"}`
- 409 Conflict: `{"error": "username already exists"}`

---

### 4. 用户登录

```bash
# 普通用户登录
curl -s -X POST http://localhost:8080/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"testuser","password":"123456"}' \
  -c /tmp/user_cookies.txt

# 管理员登录
curl -s -X POST http://localhost:8080/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin123"}' \
  -c /tmp/admin_cookies.txt
```

**预期响应**: 200 OK,返回用户信息及 role
- 普通用户: `{"message": "login successful", "username": "testuser", "role": "user"}`
- 管理员: `{"message": "login successful", "username": "admin", "role": "admin"}`

---

### 5. 用户登出

```bash
curl -s -X POST http://localhost:8080/api/logout -b /tmp/user_cookies.txt
```

**预期响应**: 200 OK: `{"message": "logout successful"}`

---

### 6. 提交代码

```bash
# 正常提交
curl -s -X POST http://localhost:8080/api/submit \
  -H "Content-Type: application/json" \
  -d '{
    "code": "#include <iostream>\nusing namespace std;\nint main() { int a,b; cin>>a>>b; cout<<a+b<<endl; return 0; }",
    "problem_id": 425
  }'

# 缺少 code 字段 (400)
curl -s -X POST http://localhost:8080/api/submit \
  -H "Content-Type: application/json" \
  -d '{"problem_id": 425}'

# 题目不存在 (404)
curl -s -X POST http://localhost:8080/api/submit \
  -H "Content-Type: application/json" \
  -d '{"code": "#include <iostream>", "problem_id": 999999}'
```

**预期响应**:
- 200 OK: 返回执行结果,包含 status (AC/WA/TLE/RE/CE)、passed/total 数量及详细结果
- 400 Bad Request: `{"error": "code is required"}`
- 404 Not Found: `{"error": "problem not found"}`

---

## 管理接口测试(需要管理员权限)

### 7. 创建题目

```bash
# 完整参数
curl -s -X POST http://localhost:8080/api/admin/problems \
  -H "Content-Type: application/json" \
  -b /tmp/admin_cookies.txt \
  -d '{
    "title": "两数之和",
    "difficulty": "Easy",
    "content": "给定一个整数数组...",
    "template": "#include <iostream>",
    "test_cases": [
      {"input": "2 7 11 15", "expected": "9", "position": 0},
      {"input": "1 2 3", "expected": "3", "position": 1}
    ]
  }'

# 最小参数
curl -s -X POST http://localhost:8080/api/admin/problems \
  -H "Content-Type: application/json" \
  -b /tmp/admin_cookies.txt \
  -d '{"title": "Test", "difficulty": "Easy", "content": "Test content"}'

# 缺少必填字段 (400)
curl -s -X POST http://localhost:8080/api/admin/problems \
  -H "Content-Type: application/json" \
  -b /tmp/admin_cookies.txt \
  -d '{"title": "Test"}'

# 难度值无效 (400)
curl -s -X POST http://localhost:8080/api/admin/problems \
  -H "Content-Type: application/json" \
  -b /tmp/admin_cookies.txt \
  -d '{"title": "Test", "difficulty": "Invalid", "content": "Test"}'
```

**预期响应**:
- 201 Created: `{"id": 1, "message": "problem created successfully"}`
- 400 Bad Request: `{"error": "title is required"}` / `{"error": "invalid difficulty, must be Easy, Medium, or Hard"}`

---

### 8. 删除题目

```bash
# 正常删除
curl -s -X DELETE http://localhost:8080/api/admin/problems/431 \
  -b /tmp/admin_cookies.txt

# 无效 ID (400)
curl -s -X DELETE http://localhost:8080/api/admin/problems/invalid \
  -b /tmp/admin_cookies.txt

# 题目不存在 (500)
curl -s -X DELETE http://localhost:8080/api/admin/problems/999999 \
  -b /tmp/admin_cookies.txt
```

**预期响应**:
- 200 OK: `{"message": "problem deleted successfully"}`
- 400 Bad Request: `{"error": "invalid problem id"}`
- 500 Internal Server Error: `{"error": "failed to delete problem"}`

---

## 权限测试

### 测试非管理员用户访问管理接口

```bash
# 先用普通用户登录
curl -s -X POST http://localhost:8080/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"testuser","password":"123456"}' \
  -c /tmp/user_cookies.txt

# 尝试创建题目(应失败)
curl -s -X POST http://localhost:8080/api/admin/problems \
  -H "Content-Type: application/json" \
  -b /tmp/user_cookies.txt \
  -d '{"title": "Hack", "difficulty": "Easy", "content": "Test"}'
```

---

## 一键测试脚本

将以下内容保存为 `test_api.sh`,可直接运行:

```bash
#!/bin/bash

BASE_URL="http://localhost:8080/api"

echo "=== 1. 测试获取题目列表 ==="
curl -s "$BASE_URL/problems" | jq .

echo -e "\n=== 2. 测试获取题目详情 ==="
curl -s "$BASE_URL/problems/425" | jq .

echo -e "\n=== 3. 测试用户注册 ==="
curl -s -X POST "$BASE_URL/register" \
  -H "Content-Type: application/json" \
  -d '{"username":"curluser","password":"123456"}' | jq .

echo -e "\n=== 4. 测试用户登录 ==="
curl -s -X POST "$BASE_URL/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin123"}' \
  -c /tmp/admin_cookies.txt | jq .

echo -e "\n=== 5. 测试提交代码 ==="
curl -s -X POST "$BASE_URL/submit" \
  -H "Content-Type: application/json" \
  -d '{"code":"#include <iostream>\nusing namespace std;\nint main(){int a,b;cin>>a>>b;cout<<a+b<<endl;return 0;}","problem_id":425}' | jq .

echo -e "\n=== 6. 测试创建题目 ==="
curl -s -X POST "$BASE_URL/admin/problems" \
  -H "Content-Type: application/json" \
  -b /tmp/admin_cookies.txt \
  -d '{"title":"Curl Test","difficulty":"Easy","content":"Test"}' | jq .

echo -e "\n=== 7. 测试删除题目 ==="
curl -s -X DELETE "$BASE_URL/admin/problems/432" \
  -b /tmp/admin_cookies.txt | jq .

echo -e "\n=== 8. 测试登出 ==="
curl -s -X POST "$BASE_URL/logout" -b /tmp/admin_cookies.txt | jq .
```

---

## 错误码汇总

| HTTP 状态码 | 说明 |
|-------------|------|
| 200 | 请求成功 |
| 201 | 创建成功 |
| 400 | 请求参数错误 |
| 401 | 认证失败 |
| 404 | 资源不存在 |
| 409 | 资源冲突(如用户名已存在) |
| 500 | 服务器内部错误 |

---

## status 枚举值

| 值 | 说明 |
|----|------|
| AC | All Correct,所有测试用例通过 |
| WA | Wrong Answer,输出结果错误 |
| TLE | Time Limit Exceeded,超时 |
| RE | Runtime Error,运行时错误 |
| CE | Compilation Error,编译错误 |

5. 把接口自动化用例整合成 python 程序

通过LLM基于文档的方式进行接口测试,虽然比较灵活,但是也存在两个问题:

  • 每次运行都需要消耗一定的token
  • 由于LLM的执行存在一定的"随机性",每次自行测试的结果不一定稳定。

因此,我们也可以把上述的测试过程,转换成传统的,基于 python 方式的接口自动化测试。

根据 @基于curl的接口自动化测试文档.md 中的内容,把接口自动化测试,通过 python 程序来进行实现,测试代码放到 @tests/python/ 中.

此时,opencode 会基于 requests 和 pytest 构建接口自动化测试,这个过程需要安装一些依赖:

bash 复制代码
pip install -r requirements.txt --break-system-packages

注意:这里使用**--break-system-packages** 选项,是因为 python 默认不建议把第三方库进行全局安装(为了防止出现版本冲突)。但是由于我们此处并不涉及多个版本库共存的场景,因此直接加上上述选项,强制进行全局安装即可。

接下来 opencode 就会根据前面总结的接口测试的步骤文档,进行测试代码的开发。

开发之后,第一版程序大概率会跑不过(很多响应的细节,python 代码中不一定一步到位),因此直接在 opencode 中运行测试

我已经安装改好了 python 的依赖,直接运行 pytest

相关推荐
一条大祥脚1 小时前
26杭电暑期第三场 DP|费用流|分解质因子|数论|贪心|图论|期望|无穷级数
网络·图论·网络流·无穷级数·费用流
早睡早起身体好1232 小时前
为什么 Network 中会出现两个同名请求?——CORS 预检请求学习笔记
网络·笔记·学习·网络安全
888CC++2 小时前
C++ 快速学习指南:从入门到进阶的实战路线
开发语言·c++
daad7772 小时前
记录matlab状态机demo
java·网络·matlab
小小晓.2 小时前
C++记:函数
开发语言·c++·算法
paopaokaka_luck3 小时前
基于springboot3+vue3的智能文库平台(AI智能搜索、AI智能汇总、实时在线状态展示、多格式文档预览与富文本编辑、Echarts图形化分析)
前端·网络·spring boot·网络协议·echarts
Yang96113 小时前
告别“慢工出细活”:如何平衡光纤熔接的速度与精度?
网络
驱动小百科3 小时前
Windows运行库合集下载 VC++、DirectX、.NET运行库安装教程
c++·windows·.net·windows运行库合集下载·windows运行库安装
charlie1145141914 小时前
Cinux —— 给物理内存建账本:bitmap 物理内存管理器
开发语言·c++·操作系统·开源项目