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  宋佳奇  阅读(8)  评论(0)    收藏  举报