OJ平台远端判题子系统开发(七):集成测试与Remote模式验证

前面完成了HTTP API、沙箱引擎、Worker并发、判题标准化和安全加固的全部开发。本次的核心工作是系统级的集成测试和Remote模式的端到端验证,并开发了配套的测试工具。


一、测试全景

项目的测试覆盖多个层次,形成从单元到端到端的完整验证体系:

E2E Tests: Compose四服务端到端
        │
        ├── Integration Tests: 真实Docker判题(8场景)
        ├── Integration Tests: gRPC Remote模式
        │
        ├── Unit Tests: 判题优先级链(10场景)
        ├── Unit Tests: HTTP API(2场景)
        ├── Unit Tests: Judger Service(4场景)
        ├── Unit Tests: 提交验证(7场景)
        ├── Unit Tests: MemoryQueue(7场景)
        ├── Unit Tests: InMemoryRepository(12场景)
        ├── Unit Tests: Domain(1场景)
        ├── Unit Tests: Sandbox — 熔断器(3场景)
        ├── Unit Tests: Sandbox — 工厂(4场景)
        ├── Unit Tests: Sandbox — Seccomp(2场景)
        ├── Unit Tests: Sandbox — CLI解析(1场景)
        ├── Unit Tests: Worker(4场景)
        ├── Unit Tests: gRPC Client(2场景)
        └── Unit Tests: Workspace Pool(1场景)

全部测试结果(80个测试函数,14个测试文件,10个包,含Docker集成测试):

$ go test ./internal/... -count=1 -timeout 120s
ok  remote_judge/internal/api              2.546s
ok  remote_judge/internal/domain           1.527s
ok  remote_judge/internal/judger           ~18s
ok  remote_judge/internal/queue            1.760s
ok  remote_judge/internal/repository       1.768s
ok  remote_judge/internal/sandbox          ~18s
ok  remote_judge/internal/service          1.814s
ok  remote_judge/internal/transport/grpcclient  0.444s
ok  remote_judge/internal/worker           4.036s

Mock模式(-short)71个测试函数,10个包全部通过。


二、判题优先级链测试

2.1 问题背景

判题系统中多种异常状态可能同时出现:程序超时被Kill的同时可能OOM,输出超限的同时可能答案错误。如果判定顺序不正确,参赛者会收到误导性的错误信息。

因此实现了严格的判定优先级链:

TLE(优先) → MLE → OLE → RE → WA → AC

2.2 优先级冲突测试设计

为验证优先级链的正确性,设计了自定义沙箱(prioritySandbox),精确控制 ExecResult 的每个字段,模拟各种冲突场景:

测试 场景 期望判定 验证要点
TestPriorityTLEOverMLE TLE + OOM 同时出现 TLE TLE优先级高于OOM
TestPriorityTLEOverRE TLE + exitCode=1 TLE TLE优先级高于RE
TestPriorityMLEOverOLE MLE + 输出超限 MLE MLE优先级高于OLE
TestPriorityOLERejectedAboveLimit 输出4096B 限制1KB OLE 独立OLE判定
TestPriorityOLEOverRE OLE + exitCode=1 OLE OLE优先级高于RE
TestPriorityMLEOverWA MLE + 输出正确 MLE MLE优先级高于WA

10个优先级测试全部PASS。

all_tests


三、基础设施层测试

3.1 MemoryQueue单元测试

队列是异步判题的核心组件,本次补充了完整的单元测试覆盖:

测试 验证场景
TestMemoryQueuePublishConsume 基本发布-消费流程
TestMemoryQueuePublishAfterClose 关闭后发布返回 ErrQueueClosed
TestMemoryQueueConsumeAfterCloseReturnsClosedChannel 关闭后通道可读为零值
TestMemoryQueuePublishCancelledContext 已取消上下文阻塞发布
TestMemoryQueueDoubleCloseIsIdempotent 两次关闭不panic
TestMemoryQueueZeroSizeDefaults size=0 回退到128
TestMemoryQueueFIFO 先进先出顺序保证

7个测试覆盖了队列生命周期中的所有关键边界条件。

3.2 InMemoryRepository单元测试

存储层是服务和判题之间的数据桥梁,本次补充了12个测试覆盖提交仓储和题目仓储:

提交仓储(8个测试):

测试 验证场景
TestInMemorySubmissionCRUD 创建、读取、更新完整流程
TestInMemorySubmissionGetNotFound 查询不存在提交返回 ErrNotFound
TestInMemorySubmissionUpdateNotFound 更新不存在提交返回 ErrNotFound
TestInMemorySubmissionListFilter 按UserID过滤
TestInMemorySubmissionListPagination 分页边界(正常页、超出范围页)
TestInMemorySubmissionSaveAndGetCases 案例结果保存与读取
TestInMemorySubmissionCountRecentByUser 时间窗口内提交计数
TestInMemorySubmissionNextID 单调递增ID生成

题目仓储(4个测试):

测试 验证场景
TestInMemoryProblemGetByID 预置题目查询
TestInMemoryProblemGetByIDNotFound 不存在题目返回 ErrNotFound
TestInMemoryProblemListCases 测试用例列表
TestInMemoryProblemListCasesNotFound 不存在题目案例返回 ErrNotFound

3.3 提交验证全覆盖

validateCreateRequest 有5个校验条件,每个条件都有独立的测试:

测试 校验条件
TestValidateCreateRequestNegativeUserID UserID <= 0
TestValidateCreateRequestNegativeProblemID ProblemID <= 0
TestValidateCreateRequestBlankCode 空白代码(""、" "、"\t\n ")
TestValidateCreateRequestCodeTooLong 超过128KB
TestValidateCreateRequestUnsupportedLanguage 不支持的语言
TestValidateCreateRequestSupportedLanguages 所有3种语言均通过
TestValidateCreateRequestExactMaxLength 恰好128KB边界通过

四、Worker错误路径测试

4.1 正常流程(已有)

TestJudgeWorkerHandle 验证了 Pending → Accepted 的完整流程。

4.2 错误路径(新增)

测试 验证场景
TestWorkerRejectBusy 令牌池满载时新任务触发 rejectBusy → System Error
TestWorkerHandleSystemErrorFromJudger Judger返回error → 提交标记System Error
TestWorkerHandleProblemNotFound 题目不存在 → 提交标记System Error

三个错误路径覆盖了Worker的主要异常分支。


五、Docker集成测试

5.1 测试内容

在真实Docker环境中,使用 DockerCLISandbox 执行编译和运行,而不是Mock沙箱的预设返回值。每个测试用例提交真实代码并验证判题状态。

$ go test -v -count=1 -timeout 120s ./internal/judger/ -run Docker

8个场景全部PASS,每个测试输出包含场景描述、最终状态、运行时指标和每个测试点的详细结果。

5.2 Docker集成测试与Mock测试的区别

特性 Mock测试 Docker集成测试
依赖 无外部依赖 Docker Desktop + 判题镜像
速度 毫秒级 秒级(容器启动+编译运行)
验证范围 逻辑正确性 逻辑 + Docker交互 + 资源限制
适用阶段 开发中频繁运行 提交前/CI验证

六、Remote模式端到端验证

6.1 Remote模式架构

Client → HTTP API (server) → Queue → Worker → gRPC Client → gRPC Server (judger) → Docker Sandbox

Remote模式与Embedded模式的核心区别在于Worker和Judger之间的通信方式:Embedded模式下是进程内函数调用,Remote模式下是跨进程(甚至跨主机)的gRPC调用。

6.2 双进程部署验证

终端1 — 启动独立Judger:

set REMOTE_JUDGE_SANDBOX=docker
set REMOTE_JUDGE_GRPC_ADDR=127.0.0.1:9091
go run .\cmd\judger

输出 gRPC judger listening on :9091 表示启动成功。

judger_start

终端2 — 启动Server(remote模式):

set REMOTE_JUDGE_JUDGER_MODE=remote
set REMOTE_JUDGE_REPOSITORY=memory
set REMOTE_JUDGE_QUEUE=memory
set REMOTE_JUDGE_HTTP_ADDR=:8081
set REMOTE_JUDGE_GRPC_ADDR=127.0.0.1:9091
go run .\cmd\server

输出 judger mode=remote 表示以Remote模式启动。

remote_server_start

6.3 Smoke工具端到端测试

开发了smoke工具(cmd/smoke),用于快速验证判题全链路。smoke不依赖外部程序,直接在代码中提交预定义的测试用例并轮询结果。

测试命令:

go run .\cmd\smoke -addr http://127.0.0.1:8081 -lang cpp17 -mode ac
go run .\cmd\smoke -addr http://127.0.0.1:8081 -lang cpp17 -mode wa
go run .\cmd\smoke -addr http://127.0.0.1:8081 -lang cpp17 -mode ce
go run .\cmd\smoke -addr http://127.0.0.1:8081 -lang python3.11 -mode ac

测试结果:

Smoke测试 提交语言 预期结果 实际结果
C++ Accepted cpp17 Accepted Accepted
C++ Wrong Answer cpp17 Wrong Answer Wrong Answer
C++ Compile Error cpp17 Compile Error Compile Error
Python Accepted python3.11 Accepted Accepted

smoke_cpp_ac

smoke_cpp_wa

smoke_cpp_ce

smoke_python_ac

6.4 gRPC压测工具

开发了gRPC压测工具(cmd/grpcstress),直接对Judger的gRPC接口施加负载,验证远程Judger在并发场景下的稳定性:

参数 说明 默认值
-n 请求总数 30
-c 并发数 5
-addr gRPC地址 127.0.0.1:9090
-lang 测试语言 cpp17/go1.22/python3.11/mixed

mixed模式按1:1:1分配三种语言的请求,模拟真实比赛中不同队伍使用不同语言提交的场景。


七、HTTP压测工具

cmd/stress 是HTTP级压测工具,从客户端视角对完整API链路施加负载:

go run .\cmd\stress -n 100 -c 10 -addr http://127.0.0.1:8081
参数 说明 默认值
-n 请求总数 100
-c 并发数 10
-addr HTTP API地址 http://127.0.0.1:8080

压测验证了两个关键指标:

  1. Worker令牌池在高并发下能正确限制并行判题数
  2. 超额请求被正确地以System Error拒绝,而非导致系统崩溃

八、沙箱工厂与Domain测试

8.1 工厂构建测试

Build() 函数根据配置文件决定创建哪种沙箱,新增4个测试覆盖所有分支:

测试 验证内容
TestBuildMockSandbox mock模式 → MockSandbox,不包装熔断器
TestBuildDockerSandbox docker模式 → DockerCLISandbox + CircuitBreakerSandbox
TestBuildDockerSandboxCopyMode transfer=copy正确传播到DockerCLISandbox
TestBuildDockerSandboxPrewarmDisabled PrewarmImages=false跳过热身

8.2 Domain层测试

测试 验证内容
TestIsTerminalStatus 8个终态返回true,4个中间态返回false

九、测试工具与测试覆盖汇总

9.1 全部测试文件与函数

包 测试文件 测试函数数 模式
api http_test.go 2 Mock
domain domain_test.go 1 Mock
judger service_test.go 4 Mock
judger priority_test.go 10 Mock
judger workspace_pool_test.go 1 Mock
judger docker_integration_test.go 8 Docker
queue memory_test.go 7 Mock
repository submission_test.go 8 Mock
repository problem_test.go 4 Mock
sandbox circuit_breaker_test.go 3 任意
sandbox seccomp_test.go 2 任意
sandbox dockercli_test.go 1 任意
sandbox factory_test.go 4 任意
service submission_test.go 3 Mock
service validation_test.go 7 Mock
transport/grpcclient client_test.go 2 Mock
worker judge_worker_test.go 1 Mock
worker worker_error_test.go 3 Mock

总计:80个测试函数(Mock模式71个 + Docker集成9个),14个测试文件,10个包。

9.2 测试分层

E2E (smoke / grpcstress / stress)         ← 端到端验证
  ↓
Integration (judger Docker tests)          ← 组件集成验证
  ↓
Unit (mock tests: api/service/worker/queue/repository/...)  ← 单元逻辑验证

十、本周总结

完成内容

  1. 全量测试:80个测试函数(Mock 71 + Docker 9),10个包全部通过
  2. 判题优先级链:10个冲突场景测试,验证TLE>MLE>OLE>RE>WA>AC
  3. 基础设施层:MemoryQueue 7个测试 + InMemoryRepository 12个测试
  4. 提交验证:5个校验条件全覆盖(7个测试)
  5. Worker错误路径:3个异常分支测试
  6. 沙箱工厂:4个构建分支测试
  7. Domain层:IsTerminalStatus 12个状态全覆盖
  8. Docker集成测试:8个场景覆盖全部判题状态
  9. Remote模式双进程部署验证
  10. Smoke端到端工具(4条测试链路验证)
  11. gRPC压测工具和HTTP压测工具开发
  12. 测试金字塔体系的完整建立

调研查阅的资料

posted @ 2026-06-09 13:01  宋佳奇  阅读(24)  评论(0)    收藏  举报