后端与接口
在 .NET 中用 Verify 做快照测试:审批…
适用于 .NET 测试中需要捕获渲染输出、API 表面、HTTP 响应、序列化结果或复杂对象,并用人工审批的基线文件防止意外变更的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 .NET 中用 Verify 做快照特定
解决思路
- 安装 Verify 包:运行 `dotnet add package Verify.Xunit`;如果使用其他测试框架,运行 `dotnet add package Verify.NUnit` 或 `dotnet add package Verify.MSTest`。
- 在测试项目中创建 `ModuleInitializer.cs`,引入 `using System.Runtime.CompilerServices;`。
- 在 `ModuleInitializer.cs` 中添加 `[ModuleInitializer] public static void Init()`。
- 在 `Init()` 中调用 `VerifyBase.UseProjectRelativeDirectory("Snapshots")`,让 verified 文件使用相对源码文件的路径。
- 可选在 `Init()` 中调用 `DiffTools.UseOrder(DiffTool.Rider, DiffTool.VisualStudioCode)` 配置 diff 工具;不配置则自动检测。
- 首次运行测试:让测试生成带实际输出的 `.received.` 文件。
- 人工审查 `.received.` 文件后批准,创建 `.verified.` 文件。
- 后续运行:让测试将实际输出与 `.verified.` 文件比较。
- 检测到变化时:让测试失败,并用 diff 工具展示差异供审查。
- 简单对象验证:在 `[Fact]` 测试中构造对象并 `return Verify(user);`,生成 `VerifyUserDto.verified.txt`。
- 字符串/HTML 验证:调用 `await Verify(html, extension: "html");`,使用 `extension` 参数生成 `.verified.html` 文件。
- 邮件模板测试:注入 `IMjmlTemplateRenderer`。
- 准备变量字典:`Dictionary<string, string>` 包含 `OrganizationName`、`InviteeName`、`InviterName`、`InvitationLink`、`ExpirationDate` 等。
- 调用 `renderer.RenderTemplateAsync("UserInvitations/UserSignupInvitation", variables)` 渲染 HTML。
- 对渲染的 HTML 调用 `await Verify(html, extension: "html");`。
- API surface 手动审批:获取 `typeof(MyLibrary.PublicClass).Assembly`。
- 对 `assembly.GetExportedTypes().OrderBy(t => t.FullName)` 排序。
- Select 匿名对象:`Type = t.FullName`,`Members = t.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static).Where(m => m.DeclaringType == t).OrderBy(m => m.Name).Select(m => m.ToString())`。
- 调用 `return Verify(publicApi);`,生成 `.verified.txt` 完整 API 表面,任何变化需显式审批。
- 使用 PublicApiGenerator 审批 API:运行 `dotnet add package PublicApiGenerator` 和 `dotnet add package Verify.Xunit`。
- 调用 `var api = typeof(MyPublicClass).Assembly.GeneratePublicApi(); return Verify(api);`。
- HTTP 响应测试:用 `_factory.CreateClient()` 创建客户端。
- 执行 `var response = await client.GetAsync("/api/users/123");`。
- 调用 `await Verify(new { StatusCode = response.StatusCode, Headers = response.Headers.Where(h => h.Key.StartsWith("X-")).ToDictionary(h => h.Key, h => h.Value.First()), Body = await response.Content.ReadAsStringAsync() });`,一起验证状态码、头与正文。
- 局部清理动态值:调用 `return Verify(order).ScrubMember("Id").ScrubMember("CreatedAt");`,将动态值替换为 `Guid_1`、`DateTime_1` 等占位符。
- 全局清理动态值:在 `ModuleInitializer` 中调用 `VerifierSettings.ScrubMembersWithType<DateTime>();`。
- 继续调用 `VerifierSettings.ScrubMembersWithType<DateTimeOffset>();`。
- 继续调用 `VerifierSettings.ScrubMembersWithType<Guid>();`。
- 清理特定模式:调用 `VerifierSettings.AddScrubber(s => Regex.Replace(s, @"token=[a-zA-Z0-9]+", "token=SCRUBBED"));`。
- 组织文件:将 verified 文件放在 `tests/MyApp.Tests/Snapshots/` 下,并按测试分组,例如 `Snapshots/EmailTests/WelcomeEmail.verified.html`、`Snapshots/EmailTests/PasswordReset.verified.html`、`Snapshots/ApiTests/GetUser.verified.txt`。
- 在测试项目中保留 `EmailTests.cs`、`ApiTests.cs`、`ModuleInitializer.cs`。
- 配置 `.gitignore`:添加 `*.received.*`,只提交 `.verified.*` 文件。
- 配置 `.gitattributes`:添加 `*.verified.txt linguist-generated=true`。
- 配置 `.gitattributes`:添加 `*.verified.html linguist-generated=true`。
- 配置 `.gitattributes`:添加 `*.verified.json linguist-generated=true`。
- CI/CD 中避免启动 diff 工具:在 `ModuleInitializer.Init` 中判断 `Environment.GetEnvironmentVariable("CI") == "true"`。
- 若 CI 为 true,调用 `VerifyDiffPlex.UseDiffPlex(OutputType.Minimal);`。
- 若 CI 为 true,调用 `DiffRunner.Disabled = true;`。
- GitHub Actions 运行 `dotnet test`,并设置 env `CI: true`。
- GitHub Actions 失败时使用 `actions/upload-artifact@v4` 上传 artifact,名称为 `snapshots`,路径为 `**/*.received.*` 和 `**/*.verified.*`。
- DO:使用描述性测试名,使其成为文件名,例如 `UserRegistration_WithValidData_ReturnsConfirmation`。
- DO:一致地清理动态值,例如 `VerifierSettings.ScrubMembersWithType<Guid>();`。
- DO:对非文本内容使用 `extension` 参数,例如 `await Verify(html, extension: "html");`。
- DO:将 verified 文件保留在源码控制中,例如 `git add *.verified.*`。
- DON'T:不要未清理就验证随机/动态数据,例如 `var order = new Order { Id = Guid.NewGuid() }; await Verify(order);`。
- DON'T:不要提交 `.received` 文件,例如 `git add *.received.*`。
- DON'T:不要用快照测试替代简单断言,例如不要 `await Verify(result.Count);`,应使用 `Assert.Equal(5, result.Count)`。
- MJML 邮件集成步骤 1:使用带 `{{variable}}` 占位符的 MJML 模板。
- MJML 邮件集成步骤 2:用测试数据渲染为 HTML。
- MJML 邮件集成步骤 3:对渲染输出做快照测试。
- MJML 邮件集成步骤 4:在批准前用 diff 工具审查变更。
- 通过上述 MJML 快照测试捕获变量替换错误、CSS/layout 回归、邮件客户端兼容问题和意外内容变更。
适用边界 · 注意事项
- 简单值检查不要用快照测试,应使用常规断言。
- 业务逻辑不要用快照测试,应使用显式断言。
- 性能测试不要用快照测试,应使用 benchmarks。