测试与 E2E
xUnit、Moq、Testcontainers、Playwright。
测试策略:xUnit 与集成测试(对照 JUnit/Mockito)
这一课你将能做到
- 用 xUnit 写出参数化的单元测试,覆盖判题服务的各分支(AC/WA/TLE/RE)
- 用
WebApplicationFactory起真实宿主做集成测试,DI 覆盖为内存数据库 - 对照 JUnit 5/Mockito/@SpringBootTest 建立完整的测试金字塔与命令行流程
为什么要学
测试是「敢不敢重构」的唯一底气。Java 侧的组合 JUnit 5 + Mockito + @SpringBootTest(+ Testcontainers),.NET 侧对应 xUnit + NSubstitute + WebApplicationFactory,连「单元测业务、集成测管线」的分层策略都相同。
人话:单元测试把类拎出来验逻辑(依赖换假货),集成测试把应用真起来发 HTTP 请求验装配。判题服务规则多、边界多(超时 1ms 之差翻转 AC/TLE),最值得堆测试。
先建立图像
集成测试(少) WebApplicationFactory ≙ @SpringBootTest + Testcontainers
──────────── 真实路由/绑定/DI,数据库用替身
单元测试(多) xUnit + NSubstitute ≙ JUnit 5 + Mockito
跟着做
步骤 1:建测试项目
dotnet new xunit -o Oj.Api.Tests
cd Oj.Api.Tests
dotnet add reference ../Oj.Api/Oj.Api.csproj
dotnet add package NSubstitute
dotnet add package Microsoft.AspNetCore.Mvc.Testing
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet test
步骤 2:单元测试——判题规则(xUnit 参数化)
被测代码:根据用例结果与资源占用判定状态:
public class VerdictEvaluator
{
public static string Evaluate(bool outputMatched, int timeMs, int limitMs, int exitCode) =>
(outputMatched, timeMs, limitMs, exitCode) switch
{
(_, _, _, != 0) => "RE",
(false, _, _, 0) => "WA",
(true, > 0, _, 0) when timeMs > limitMs => "TLE",
(true, _, _, 0) => "AC",
_ => "SE",
};
}
测试类([Theory] + [InlineData] = JUnit 的 @ParameterizedTest + @CsvSource):
public class VerdictEvaluatorTests
{
[Theory]
[InlineData(true, 100, 1000, 0, "AC")] // 输出对、没超时
[InlineData(true, 2000, 1000, 0, "TLE")] // 超时
[InlineData(false, 50, 1000, 0, "WA")] // 答案错
[InlineData(true, 0, 1000, 1, "RE")] // 非零退出码优先判 RE
public void Evaluate_Should_Classify(bool matched, int time, int limit, int code, string expected)
{
var actual = VerdictEvaluator.Evaluate(matched, time, limit, code);
Assert.Equal(expected, actual);
}
}
断言对照:Assert.Equal(a, b) ≙ ; ≙ 。