目录
1. 总结接口设计文档
2. 添加管理员账户
3. 通过 curl 进行接口自动化测试
4. 总结接口测试文档
5. 把接口自动化用例整合成 python 程序
1. 总结接口设计文档
接口测试之前,首先需要明确接口的定义规范。
- 有哪些接口
- 每个接口的作用
- 每个接口的参数
- 每个接口的返回值
@SPEC.md 参考文档,并且结合代码中已经实现的接口,总结一份完整的接口交互文档。包含每个接口的路径、参数、返回的响应结果、作用。总结出的文档写到 API.md 中。
总结出的文档形如:
# 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",便于未来进行重复的测试.
随着程序开发的进展,可能早期的单元测试会收到影响。因此随时多运行单元测试,及时修复,是开发时的一个重要习惯。
总结的文档形如:
# 基于 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 构建接口自动化测试,这个过程需要安装一些依赖:
pip install -r requirements.txt --break-system-packages注意:这里使用--break-system-packages选项,是因为 python 默认不建议把第三方库进行全局安装(为了防止出现版本冲突)。但是由于我们此处并不涉及多个版本库共存的场景,因此直接加上上述选项,强制进行全局安装即可。
接下来 opencode 就会根据前面总结的接口测试的步骤文档,进行测试代码的开发。
开发之后,第一版程序大概率会跑不过(很多响应的细节,python 代码中不一定一步到位),因此直接在 opencode 中运行测试
我已经安装改好了 python 的依赖,直接运行 pytest