C++ 在线判题平台(OJ)测试报告

C++ 在线判题平台(OJ)测试报告

项目:CPP Online Judge

测试类型:功能测试 + 接口测试 + UI 自动化测试 + 并发正确性测试 + 性能测试

测试环境:阿里云 ECS(2 核 / 1.8 GB,Alibaba Cloud Linux 3)

部署地址:http://139.196.124.168:8088

报告日期:2026-09-14


目录


一、项目简介

本项目实现了一个完整 OJ 平台所应具备的核心功能,并按 B 端 / C 端特性做了功能切分:

  • 普通用户(学生) :注册登录、浏览题目列表(分页 / 难度筛选 / 关键词搜索)、查看题目详情、
    在 Monaco 在线编辑器中编写 C++ 代码、提交判题、查看个人提交记录与提交详情。
  • 管理员(教师):题目的新增 / 编辑 / 删除、上传测试用例文件、查看全部提交记录。
  • 游客:仅可浏览题目列表与题目详情,无法提交代码。

二、开发技术

层次 选型
后端 Web 框架 cpp-httplib(header-only,C++17)
前端 原生 HTML + CSS + JS + Monaco Editor + marked.js + KaTeX
数据库 MySQL 8.0(本项目实测部署使用 MySQL 5.7.40)
消息队列 / 缓存 Redis List(oj:judge:queue
判题沙箱 Docker 容器(gcc:13 镜像)
鉴权 JWT(HS256,jwt-cpp)
密码存储 bcrypt(cost=12,通过 crypt() 实现)
构建 CMake 3.26 + GCC 10.2

三、测试环境与部署

3.1 环境信息

项目 配置
服务器 阿里云 ECS,2 核 CPU / 1869 MB 内存 / 40 GB 磁盘
操作系统 Alibaba Cloud Linux 3(Anolis,内核对应 EL8)
编译器 g++ 10.2.1、CMake 3.26.5
数据库 MySQL 5.7.40(为被测应用单独拉起 3307 实例,与服务器上原有的 3306 生产库隔离)
缓存/队列 Redis 6.2.22(6379)
判题沙箱 Docker 26.1.3 + gcc:13 镜像(1.39 GB)
服务端口 后端 8088(与服务器上原有的 8080 服务互不影响)
进程托管 systemd(cpp-oj-backend / cpp-oj-judge / cpp-oj-mysql

3.2 测试数据准备

通过管理端 API 向数据库写入测试数据(脚本 data/seed_test_data.py,25 项校验全部通过):

类型 内容
内置题目 A + B Problem、Hello World、斐波那契数列
新增测试题 数组求和、字符串反转、最大子段和、多行输出与末尾空行容差、大整数相加、判题-TLE 验证题、判题-MLE 验证题、无测试用例题目-SE 验证
测试账号 管理员 admin/admin123;普通用户 stu_teststu_demo
题库合计 12 道题,其中 11 道配好输入/输出测试用例,1 道故意不配用例用于验证 SE

四、测试用例设计(Xmind 思维导图)

测试用例按 认证 / 题目 / 判题 / 提交记录 / 管理端 / 安全 六大模块 + 边界值 + 异常值设计,

119 条(接口 85 + UI 34)

测试执行流程如下:

4.1 用例设计要点

设计维度 具体做法 示例
正常路径 覆盖每个接口的成功分支 正确代码提交判 AC
边界值 取上边界、下边界、边界±1 用户名 2/3/20/21 字节;密码 5/6 字节;代码 65536/65537 字节
异常输入 空值、非法格式、类型错误 空 JSON、非法难度、非数字 ID
状态机 异步判题的全部 7 种状态 AC / WA / TLE / MLE / RE / CE / SE
输出比对规则 验证题面约定的容差 行尾空格、末尾空行、多行输出、大整数
权限矩阵 游客 / 普通用户 / 管理员三视角 同一管理接口分别验 401 / 403 / 200
越权访问 跨用户读取资源 普通用户读他人提交 → 403
安全 注入与鉴权绕过 SQL 注入、注释符注入、路径注入、JWT 前缀
并发 顺序基线 vs 并发对照 见第九章(发现 P0 缺陷)

五、自动化测试代码

5.1 技术栈

  • 接口自动化 :Python 3.10 标准库(urllib),无第三方依赖,便于在服务器直接运行
  • UI 自动化:Python + Selenium 4.49 + Chrome headless
  • 接口集合:Postman Collection v2.1(62 个请求),用 newman 执行
  • 性能测试:Apache JMeter 5.6.3
  • 测试数据:Python 脚本调用管理端 API 写入

5.2 接口自动化测试(核心代码片段)

判题是异步的,因此封装了「提交 → 轮询 → 断言终态」的通用方法:

python 复制代码
def submit(sub_token, problem_id, code, language="cpp"):
    return call("POST", "/api/submissions", token=sub_token,
                body={"problem_id": problem_id, "code": code,
                      "language": language})

def wait_result(token, sid, timeout=120):
    """轮询直到状态不再是 pending,返回 (状态, 详情)"""
    st, t0 = "", time.time()
    while time.time() - t0 < timeout:
        r = call("GET", "/api/submissions/%s" % sid, token=token)
        d = data_of(r) or {}
        st = d.get("status", "")
        if st and st != "pending":
            return st, d
        time.sleep(1)
    return st, (data_of(r) or {})

判题状态用例矩阵(每条都真实提交代码并等待结果):

python 复制代码
judge_cases = [
    ("TC-SUB-001", "提交正确代码判 AC",   AB_ID,
     '#include <iostream>\nint main(){long long a,b;std::cin>>a>>b;'
     'std::cout<<a+b<<std::endl;return 0;}', "ac"),
    ("TC-SUB-002", "提交错误代码判 WA",   AB_ID, "... cout<<a-b; ...", "wa"),
    ("TC-SUB-003", "死循环代码判 TLE",    TLE_ID, "int main(){while(1){}}", "tle"),
    ("TC-SUB-004", "空指针解引用判 RE",   AB_ID, "int main(){int* p=nullptr;*p=42;}", "re"),
    ("TC-SUB-005", "内存超限代码判 MLE",  MLE_ID, "std::vector<long long> v; while(1) v.push_back(1);", "mle"),
    ("TC-SUB-006", "编译错误代码判 CE",   AB_ID, "int main(){ this is not valid c++; }", "ce"),
]

5.3 UI 自动化测试(关键踩坑)

该前端有一个很关键的行为 :只要 localStorage 里存在 oj_token

访问 /pages/login.html 会被立即重定向到题目列表页。

因此自动化的登录动作必须先清空登录态,否则永远等不到登录表单:

python 复制代码
def do_login(d, user, pwd):
    """注意:该前端在存在 oj_token 时会立刻把 login.html 重定向到 problems.html,
    因此必须先清空登录态再进入登录页,否则永远等不到表单。"""
    d.get(BASE + "/pages/login.html")
    d.execute_script("localStorage.clear();")
    d.get(BASE + "/pages/login.html")
    WebDriverWait(d, 15).until(EC.presence_of_element_located((By.ID, "username")))
    d.find_element(By.ID, "username").send_keys(user)
    d.find_element(By.ID, "password").send_keys(pwd)
    d.find_element(By.ID, "submitBtn").click()

5.4 并发正确性测试(对照法)

为了区分「性能不达标」与「并发下结果错误」,设计了顺序基线 vs 并发的对照实验:

python 复制代码
def sequential_login(n, user, pwd):   # 顺序基线
    ...按顺序发 n 次登录,统计成功/失败

def concurrent_login(threads, per_thread, user, pwd):  # 并发对照
    ts = [threading.Thread(target=worker) for _ in range(threads)]
    ...

六、功能测试结果

6.1 接口自动化测试

执行结果:85 条用例,通过 77 条,失败 8 条,通过率 90.6%

模块 通过 / 总数 通过率
认证(注册/登录/鉴权) 19 / 20 95.0%
题目(列表/详情/筛选/搜索) 12 / 14 85.7%
提交与判题(7 种状态 + 参数校验) 18 / 20 90.0%
提交记录与可见性 10 / 11 90.9%
管理端与权限 11 / 12 91.7%
权限控制 3 / 3 100%
安全与边界 4 / 5 80.0%
合计 77 / 85 90.6%

6.2 判题核心链路验证(全部通过)

这是本项目最核心的功能,7 种判题状态全部真实验证通过:

# 用例 提交内容 期望 实际 耗时 内存
1 AC 答案正确 正确的 a+b ac ac 326 ms 0 KB
2 WA 答案错误 输出 a-b wa wa 295 ms 0 KB
3 TLE 超时 while(1){} tle tle 1000 ms 0 KB
4 RE 运行错误 空指针解引用 re re 0 ms 0 KB
5 MLE 内存超限 无限 push_back mle mle 0 ms 65536 KB
6 CE 编译错误 非法 C++ 语法 ce ce 0 ms 0 KB
7 SE 系统错误 向无测试用例题目提交 se se --- ---

6.3 UI 自动化测试

执行结果:34 条用例,通过 33 条,失败 1 条,通过率 97.1% ,同步产出 19 张页面截图

用例编号 用例名称 结果
TC-UI-001~004 首页渲染、游客导航、主视觉按钮、统计卡片
TC-UI-005~009 注册页加载、用户名/密码/一致性三项前端校验、注册成功
TC-UI-010~013 登录页加载、错误密码提示、普通用户登录、管理员登录跳转后台
TC-UI-014~016 管理员菜单含「管理后台」、普通用户菜单不含、普通用户直访后台被拦截
TC-UI-017 登录用户可访问提交记录页
TC-UI-018~020 题目列表渲染、关键词搜索、难度筛选
TC-UI-021~022 题目详情渲染、Monaco 编辑器加载成功
TC-UI-023~026 判题中中间态、AC 显示「通过」、WA 显示「答案错误」、CE 显示「编译错误」
TC-UI-027~028 提交记录列表、提交详情弹窗
TC-UI-029~031 管理后台列表、用例按钮状态标识、用例上传弹窗
TC-UI-032 点击「+ 新增题目」弹窗应出现 缺陷 D-07
TC-UI-033~034 编辑题目弹窗回填、退出登录清除登录态

前端权限差异测试的一个亮点:管理员下拉菜单里多出「管理后台」入口,普通用户没有

且普通用户直接访问 /pages/admin.html 会被前端拦截跳回首页 ------ 界面层权限隔离生效。

6.4 测试截图

以下截图均由 Selenium 在 headless Chrome(1440×900)中真实操作页面后自动截取。

登录页与前端校验

题目列表(支持搜索 / 难度筛选 / 分页)

题目详情 ------ 左侧 Markdown + LaTeX 题面,右侧 Monaco 在线编辑器

提交代码后的判题结果(异步轮询 → 状态标签 + 耗时 + 内存)

编译错误(含 g++ 原始报错信息)

提交记录与提交详情弹窗

管理后台 ------ 题目列表与测试用例上传

缺陷复现 ------ 点击「新增题目」弹窗不出现(D-07)

七、Postman 接口测试

7.1 集合结构

交付一套可直接导入 Postman 9.x 的集合(Collection v2.1)+ 环境文件:

文件夹 请求数 覆盖内容
01 认证模块 11 注册正常/重复用户名/用户名过短/密码过短、登录成功/密码错误/字段缺失、获取当前用户、未带 token
02 题目模块 10 列表分页、难度筛选、非法难度、关键词搜索、题目详情、不存在 ID、非数字 ID
03 提交与判题 20 AC/WA/TLE/RE/CE 各一对「提交 + 轮询」、空代码、超长代码、题目不存在、未登录
04 提交记录 10 本人列表、状态筛选、非法状态、本人详情、越权查看他人提交
05 管理端 11 管理员列表、普通用户越权、创建/更新/删除题目、上传测试用例、删除不存在题目
合计 62

集合中内建了 412 条断言,并对每个请求都校验响应时间小于 3000 ms。


八、性能测试报告(JMeter)

8.1 测试计划设计

用 Apache JMeter 5.6.3 编写测试计划(jmeter/在线OJ平台性能测试.jmx),共 5 个线程组 / 18 个采样器 / 113 条断言:

线程组 默认负载 说明
setUp 准备组 1 线程 × 1 次 管理员登录取 token,供后续线程组使用
线程组 1:在线用户登录 20 线程 / ramp-up 10s / 循环 10 POST /api/auth/login
线程组 2:题目列表与详情浏览 20 线程 / ramp-up 10s / 循环 10 含 100~500ms 随机思考时间
线程组 3:提交代码并轮询判题 5 线程 / ramp-up 5s / 循环 3 提交后用 While Controller 每 1 秒轮询判题结果

所有并发参数均可通过 -J 覆盖(-Jthreads-Jloops-Jhost-Jport 等)。

考虑到被测服务器只有 2 核 1.8 GB 且同时运行着另一个生产服务,计划中明确建议并发不超过 50

8.2 执行结果

复制代码
summary =    746 in 00:00:42 =   17.8/s Avg:  1043 Min:    63 Max:  7657 Err: 199 (26.68%)

746 个样本,总耗时 42 秒,平均响应 1043 ms,最大 7657 ms,错误率 26.68%。

按采样器细分后,问题的分布非常集中:

采样器 样本数 错误率 平均 P90 P95 最大 响应码
POST /api/auth/login(用户登录) 200 99.5% 3112 ms 5361 5759 7657 200×1, 401×199
GET /api/problems(题目列表) 200 0.0% 331 ms 462 553 2754 200×200
GET /api/problems/1(题目详情) 200 0.0% 250 ms 373 419 604 200×200
POST /api/submissions(提交代码) 15 0.0% 451 ms 674 756 756 200×15
GET /api/submissions/{id}(轮询判题) 约 130 0.0% 约 250 ms --- --- 619 200×全部

全体请求分位:p50 = 305 msp90 = 3596 msp95 = 4254 msp99 = 6088 msmax = 7657 ms

8.3 关键结论

  1. 题目浏览类接口表现良好 :列表平均 331 ms / P95 553 ms,详情平均 250 ms / P95 419 ms,
    20 并发下零错误
  2. 提交与轮询链路零错误 :提交平均 451 ms,轮询平均约 250 ms,全部 200,
    说明异步判题队列在并发提交下工作正常。
  3. 登录接口 99.5% 失败,且不是超时而是 401 :199 次登录返回 401 Unauthorized。
    不是性能问题,而是正确性问题 ------ 详见第九章。
  4. 除去登录接口,其余接口的总体错误率为 0% ;也就是说 26.68% 的整体错误率完全由登录接口贡献
  5. 登录接口平均耗时 3112 ms 明显高于其它接口(300 ms 级别),是因为它是唯一走
    bcrypt(cost=12) 密码校验 的接口,单次校验即消耗数百毫秒 CPU;
    在 2 核机器上 20 并发时 CPU 被迅速打满,这也是它成为瓶颈的直接原因。

九、并发正确性测试(重大发现)

JMeter 报告里 198 次 401 是一个强烈信号。为了区分「性能不足」与「并发下结果错误」,

专门设计了顺序基线 vs 并发对照实验。

9.1 测试结果

场景 并发 成功 / 总数 失败率 响应码分布
顺序登录 30 次(admin) 1 30 / 30 0% 200×30
顺序登录 30 次(自建账号) 1 30 / 30 0% 200×30
并发登录(admin) 5 线程 × 6 0 / 30 100% 401×30
并发登录(admin) 10 线程 × 5 0 / 50 100% 401×50
并发登录(admin) 20 线程 × 5 0 / 100 100% 401×100
并发登录(自建账号) 10 线程 × 5 0 / 50 100% 401×50
并发注册 10 线程 × 4 8 / 40 80% 200×8, 400×32

顺序执行 100% 成功,并发(≥5 线程)100% 失败。 这排除了「凭据错误」「账号问题」,

明确指向并发数据竞争

9.2 根因定位

查看后端日志,捕获得到了直接证据:

复制代码
[2026-09-14 21:01:42.615] [ERROR] [user_service.cpp:25 registerUser]
    bcrypt hash failed: bcrypt hash failed: crypt() returned null
[2026-09-14 21:01:42.782] [ERROR] [user_service.cpp:25 registerUser]
    bcrypt hash failed: bcrypt hash failed: crypt() returned null
... (共出现 32 次)

定位到问题代码:

cpp 复制代码
// backend/src/utils/password.cpp
std::string bcryptHash(const std::string& password) {
    std::string salt = generateBcryptSalt(12);
    char* result = crypt(password.c_str(), salt.c_str());   // ← 非线程安全
    if (!result) throw std::runtime_error("bcrypt hash failed: crypt() returned null");
    return std::string(result);
}

bool bcryptVerify(const std::string& password, const std::string& hash) {
    char* result = crypt(password.c_str(), hash.c_str());   // ← 非线程安全
    if (!result) return false;
    return std::strcmp(result, hash.c_str()) == 0;
}

crypt() 是线程不安全的。 服务器上 man 3 crypt 的原文(glibc 2.32 / libxcrypt 4.1.1):

复制代码
   Interface            Attribute     Value
   ────────────────────────────────────────────────────────
   crypt                Thread safety MT-Unsafe race:crypt
   crypt_r, crypt_rn,   Thread safety MT-Safe
   crypt_ra

crypt() 内部使用静态缓冲区保存结果,并发调用时会互相踩踏,

表现为返回 NULL返回被覆盖的哈希串

而 cpp-httplib 默认使用线程池并发处理请求,多个登录/注册请求会同时进入 crypt()

  • 注册crypt() 返回 NULL → 抛异常 → user_service.cpp:25 记错误日志 → 返回 400 internal error
  • 登录crypt() 结果被污染 → strcmp(result, hash) 不相等 → 判定密码错误 → 返回 401

9.3 影响评估

维度 评估
严重程度 P0 / 严重 ------ 核心功能在并发下完全不可用
影响范围 所有涉及密码校验的接口:/api/auth/login/api/auth/register
触发条件 仅需 5 个并发请求(无需高负载),任何真实多人使用场景必然触发
业务影响 班级/教学场景下多个学生同时登录时几乎全部失败;且有概率把错误哈希写入数据库,导致账号永久无法登录
隐蔽性 单用户手工测试完全无法发现(顺序 100% 成功),必须并发才暴露

9.4 修复建议

cpp 复制代码
// 方案一(推荐):改用可重入版本 crypt_r
#include <crypt.h>
std::string bcryptHash(const std::string& password) {
    std::string salt = generateBcryptSalt(12);
    struct crypt_data data;
    std::memset(&data, 0, sizeof(data));
    char* result = crypt_r(password.c_str(), salt.c_str(), &data);   // MT-Safe
    if (!result) throw std::runtime_error("bcrypt hash failed");
    return std::string(result);
}

bool bcryptVerify(const std::string& password, const std::string& hash) {
    struct crypt_data data;
    std::memset(&data, 0, sizeof(data));
    char* result = crypt_r(password.c_str(), hash.c_str(), &data);   // MT-Safe,且线程栈上独立
    if (!result) return false;
    return std::string(result) == hash;
}

注意:crypt_rcrypt_data 必须每个线程/每次调用独立 (用栈变量即可),

不能共享同一个 crypt_data

方案二:用互斥锁把 crypt() 调用串行化(实现简单,但会限制登录并发度)。

方案三:替换为原生 bcrypt 实现(如 libbcrypt),彻底规避 crypt() 的线程安全问题。


十、缺陷清单

本次测试共发现 12 个已确认缺陷 ,其中 P0 严重 1 个、P1 高 3 个、P2 中 3 个、P3 轻微 5 个。

(另有 1 条曾疑似缺陷的观测项 D-06,经公网复测确认非缺陷,已在清单中标注排除。)

P0 ------ 严重

D-01 并发下 crypt() 非线程安全,导致并发登录/注册全部失败
内容
用例 TC-CONC-001、JMeter 线程组 1
现象 顺序登录 100% 成功;并发 ≥5 线程时登录 100% 返回 401,并发注册 80% 返回 400
证据 后端日志 32 次 bcrypt hash failed: crypt() returned nullman 3 crypt 标注 MT-Unsafe race:crypt
位置 backend/src/utils/password.cpp:33:41
根因 crypt() 使用静态缓冲区,多线程并发调用互相踩踏,返回 NULL 或被污染的哈希
影响 核心功能在并发下完全不可用;可能把错误哈希写入库导致账号永久无法登录
建议 改用 crypt_r()(每线程独立 crypt_data),或用互斥锁串行化

P1 ------ 高

D-02 更新题目会清空测试用例,导致该题后续提交全部判 SE
内容
用例 TC-ADMIN-010
复现 创建题目 → 上传测试用例 → PUT 修改标题 → 查询该题,testcase_in_key 变为空
位置 backend/src/handlers/admin_handler.cppparseProblemFromJson 不解析用例 key;problem_service.cpp 的 update 未保留原 key
影响 管理员编辑一次题目后,该题所有提交都会得到「系统错误」
建议 update 时保留未传字段的既有值(改为真正的部分更新)
D-03 非数字/混合题目 ID 处理错误,且存在路径注入前缀解析
内容
用例 TC-PROB-011TC-PROB-013TC-SEC-004
现象 GET /api/problems/abc500 且响应体为空 (破坏统一 JSON 信封) ② GET /api/problems/1abc200 且返回题目 1GET /api/problems/1%20OR%201=1200 且返回题目 1
位置 backend/src/handlers/problem_handler.cpp:52 直接 std::stoll,无 try/catch,服务端也未注册全局异常处理器
根因 std::stoll前缀宽容解析 特性("1abc" 解析为 1),且异常未被捕获
影响 参数校验形同虚设,客户端错误请求会拿到合法数据;异常路径返回 500 空体不利于排障
建议 用严格数字校验(如正则 ^\d+$)+ try/catch,非法参数统一返回 400

P2 ------ 中

D-04 非法 status 筛选返回 500 而非 400
  • 用例 TC-LIST-003GET /api/submissions?status=bogus500 + invalid status
  • 位置:backend/src/services/submission_service.cpp 产出 invalid status
    submission_handler.cpp 把所有未识别错误一律映射为 500
  • 影响:客户端参数错误被误报为服务端故障;建议 handler 增加错误前缀到状态码的映射
D-05 恰好 65536 字节代码返回 500(长度上限与数据库列容量不一致)
  • 用例 TC-SUB-015:提交 65536 字节代码 → 500 + failed to create submission
  • 分析:服务层长度校验为 code.size() > 65536 才拒绝(即 65536 视为合法),
    submissions.code 列类型是 TEXT(上限 65535 字节),写入时溢出报错
  • 影响:边界值上的错误不是清晰的参数错误,而是 500
  • 建议:把长度上限调整为 65535,或把列类型改为 MEDIUMTEXT
D-06 判题耗时统计包含容器启动开销(已排除,非缺陷

⚠️ 本条在真实环境下未能复现 ,特此更正说明:

早期经 SSH 隧道测试时观察到「题目时限 1000 ms,AC 用例耗时 1745~4641 ms」,

一度判定为「AC 但耗时超时限」。但 8088 端口放行后改为公网直连 复测,

AC 用例耗时稳定为 326 ms 、WA 为 295 ms ,均远低于题目时限,

用例 TC-SUB-019 通过

结论:早期观测值是压测链路(自建 SSH 隧道)自身引入的时延 造成的假象,

不是被测系统的缺陷,故从缺陷清单中移除。

保留一条设计观察 (不构成缺陷):backend/judge/sandbox.cpp 用整条 docker run

的墙钟时间作为 time_used_ms,其中包含容器启动开销,因此该数值大于程序真实运行时间

当题目时限设置得很小(如 100 ms 级)或宿主机负载很高时,存在统计偏大的风险。

建议后续改为采集容器内进程的 CPU 时间(/usr/bin/timegetrusage),

或引入 warm container 池消除启动开销。

D-07 管理后台「新增题目」按钮失效(弹窗永不出现)
  • 用例 TC-UI-032;截图 18_管理后台_新增题目失败.png

  • 位置:frontend/js/admin.js:244

    javascript 复制代码
    document.getElementById('formTestCases').value = '';   // 全前端仅此一处,元素不存在

    该语句位于 resetForm() 中,在设置弹窗标题与调用 openModal() 之前 抛出异常,
    导致弹窗永远打不开

  • 验证:全前端(html+js)搜索 formTestCases 仅命中这一行,admin.html 中不存在该 id

  • 影响:管理端无法通过界面新增题目,只能靠编辑已有题目或直接调 API

  • 建议:删除该行,或补齐对应表单元素

D-08 密码允许为纯空白字符
  • 用例 TC-AUTH-020:提交 password = " "(6 个空格)→ 注册成功
  • 位置:backend/src/utils/user_service.cpp:13 仅校验长度 password.length() < 6
    未校验是否全为空白;前端 register.html 同样未做 trim
  • 影响:可注册「空格密码」账号,且登录时用户难以察觉
  • 建议:增加 trim 后非空校验

P3 ------ 轻微

编号 缺陷 用例 说明
D-09 空代码的错误文案误导 TC-SUB-010 提交空 code 返回 problem_id and code required,应提示 code cannot be empty(与缺 problem_id 共用文案)
D-10 成功响应 datanull 而非 {} Postman 4 条断言失败 response.hconst nlohmann::json& data = {} 默认构造出的是 null 而非空对象,应写 nlohmann::json::object()
D-11 上传测试用例不选文件仍返回 200 TC-ADMIN-011 input_file/output_file 均可缺省,管理员易误以为配置成功;建议至少要求一个文件
D-12 更新题目缺少难度枚举校验 TC-ADMIN-009 创建题目会校验 difficulty ∈ {easy,medium,hard},更新路径却没有,非法值会进入数据库
D-13 language=c 被接受但始终按 C++17 编译 TC-SUB-017 提交语言选 c 可正常受理且界面显示「语言: c」,但判题机固定执行 g++ -std=c++17

缺陷分布

严重程度 数量 编号
P0 严重 1 D-01
P1 高 3 D-02、D-03(含 3 条用例)、D-07
P2 中 3 D-04、D-05、D-08
P3 轻微 5 D-09 ~ D-13
合计(已确认) 12
已排除 1 D-06(经公网复测确认非缺陷,见上)

说明:另有 2 条曾被记为失败的用例,经复核属测试脚本自身缺陷 而非产品缺陷,已修正:

① UI 用例读取提交详情弹窗时未等待异步内容加载;

② 在跑接口测试的同时并行运行了另一个脚本,导致 crypt() 竞态被触发。

上述用例在干净环境下均已通过(最终 UI 通过率 33/34,仅剩 D-07 一条真实失败)。


十一、测试结论与改进建议

11.1 结论

  1. 核心判题功能完全可用 :AC / WA / TLE / MLE / RE / CE / SE 七种状态全部通过真实验证,
    输出比对的行尾空白与末尾空行容差符合预期,Docker 沙箱的资源限制(内存、超时)也真实生效。
  2. Web 层基本功能健全 :119 条用例中 110 条通过(接口 77/85、UI 33/34),
    题目浏览类接口在 20 并发下零错误,
    权限控制(401/403)与前端三视角差异(游客/用户/管理员)均正确。
  3. 存在一个 P0 级并发缺陷crypt() 非线程安全导致并发登录/注册几乎全部失败。
    该缺陷在顺序测试下完全不可见,只有并发测试才能暴露 ------ 这也是本次测试最有价值的产出。
  4. 可用性方面的明显短板:管理后台「新增题目」入口因前端 JS 异常完全不可用。

11.2 改进建议(按优先级)

优先级 建议
P0 crypt_r() 替换 crypt();并在 CI 中加入并发登录用例作为回归门禁
P1 修复 admin.js:244#formTestCases 空引用;为前端的写入接口补充单元/UI 冒烟用例
P1 题目更新改为真正的部分更新,保留未传字段;并为「更新后用例仍可判题」加回归用例
P1 统一 ID 参数的严格数字校验,注册全局异常处理器,保证任何情况都返回 JSON 信封
P2 统一长度上限与数据库列容量的定义(65536 vs TEXT 65535)
P2 判题耗时改为采集容器内进程 CPU 时间,或引入容器池消除启动开销
P2 handler 层建立「错误码 ↔ HTTP 状态码」映射表,避免参数错误一律 500
P3 修正 nlohmann::json 默认构造为 null 的问题;补齐用例上传的文件校验
工程 补充提交频率限制、JWT 密钥强制从环境变量注入(当前默认值为硬编码)、启用 HTTPS

复现方式

bash 复制代码
# 1. 初始化测试数据
set OJ_BASE_URL=http://139.196.124.168:8088
python data/seed_test_data.py

# 2. 接口自动化测试
python automation/api_tests.py

# 3. UI 自动化测试(自动产出截图)
python automation/ui_tests.py

# 4. 并发正确性测试
python automation/concurrency_tests.py

# 5. Postman 集合(newman)
newman run postman/在线OJ平台.postman_collection.json \
  -e postman/在线OJ环境.postman_environment.json \
  --reporters cli,htmlextra

# 6. JMeter 性能测试
jmeter -n -t jmeter/在线OJ平台性能测试.jmx \
  -l results/jmeter_result.jtl -e -o results/jmeter_report

附录:测试数据统计

统计项 数值
测试用例总数 119 条(接口 85 + UI 34)
通过 110 条(接口 77 + UI 33)
失败 9 条(全部对应真实缺陷)
总通过率 92.4%
发现缺陷 12 个已确认(P0×1、P1×3、P2×3、P3×5)+ 1 个已排除
Postman 断言 412 条(失败 4 条)
JMeter 样本 746 个
页面截图 19 张
Xmind 导图 2 份
相关推荐
sdm0704271 小时前
仿muduo库实现高并发服务器-下
开发语言·网络·c++·多路转接
爱和冰阔落2 小时前
【Linux】手写日志与固定线程池:任务队列、工作线程和安全退出
linux·运维·c++·redis·安卓
All for pursuit.2 小时前
【矩阵-2】240.搜索二维矩阵 II
数据结构·c++·算法·leetcode
程序猿阿森2 小时前
C语言预处理完全指南:宏定义、条件编译与头文件规范,一篇搞懂工程化编程
c语言·c++·编译
艾莉丝努力练剑2 小时前
【AI大模型接入SDK】LLMManager类架构与智能指针选型
网络·c++·人工智能·学习·面试·架构
6Hzlia2 小时前
【Classic 150 刷题计划】 LeetCode 209. 长度最小的子数组 | C++ 滑动窗口(毛毛虫算法)经典模板
c++·算法·leetcode
6Hzlia2 小时前
【Classic 150 刷题计划】 LeetCode 28. 找出字符串中第一个匹配项的下标 | C++ 滑动窗口与子串比对
c++·算法·leetcode
wuminyu9 小时前
Kafka中sendfile与mmap实现机制解析
java·linux·c语言·jvm·c++
无小道14 小时前
C++——列表初始化
c++·列表初始化