引言:为什么需要在 C# 中进行测试?
\n在现代 C# (.NET) 开发中,代码质量不是奢侈品,而是必需品。测试不仅可以在代码进入生产环境之前捕获错误,还可以作为活文档,并加速重构过程。有三个主要的测试层级构成了“测试金字塔”:Unit(单元测试)、Integration(集成测试)和 E2E(端到端测试)。在本文中,我们将通过真实的 C# 代码示例来逐一解析它们。
\n\n1. 单元测试:质量的基础
\n单元测试在脱离外部依赖(数据库、文件系统、API)的情况下,检查代码的独立片段(方法、类)。在 .NET 生态系统中,有三个流行的框架:xUnit(推荐用于新项目)、NUnit 和 MSTest。
\n\n基本原则 (FIRST)
\n- \n
- Fast — 快速(毫秒级)。 \n
- Isolated — 隔离(使用 mock)。 \n
- Repeatable — 可重复。 \n
- Self-validating — 自我验证(Assert)。 \n
- Timely — 及时。 \n
使用 xUnit 和 Moq 的单元测试示例
\n假设我们有一个用于计算折扣的服务:
\npublic interface IDiscountService\n{\n decimal CalculateDiscount(decimal price, bool isLoyaltyMember);\n}\n\npublic class DiscountService : IDiscountService\n{\n public decimal CalculateDiscount(decimal price, bool isLoyaltyMember)\n {\n if (isLoyaltyMember) return price * 0.9m; // 10% 折扣\n return price;\n }\n}\n使用 Moq(用于模拟依赖)和 xUnit 的测试:
\nusing Xunit;\nusing Moq;\n\npublic class DiscountServiceTests\n{\n [Fact]\n public void CalculateDiscount_WhenLoyaltyMember_ReturnsDiscountedPrice()\n {\n // Arrange\n var service = new DiscountService();\n var price = 100m;\n\n // Act\n var result = service.CalculateDiscount(price, true);\n\n // Assert\n Assert.Equal(90m, result);\n }\n\n [Theory]\n [InlineData(100, false, 100)]\n [InlineData(200, true, 180)]\n public void CalculateDiscount_MultipleCases_ReturnsCorrectValues(decimal price, bool isLoyalty, decimal expected)\n {\n var service = new DiscountService();\n var result = service.CalculateDiscount(price, isLoyalty);\n Assert.Equal(expected, result);\n }\n}\n建议: 使用 [Fact] 属性进行单个测试,使用 [Theory] + [InlineData] 进行参数化测试。
2. 集成测试:检查组件之间的协作
\n集成测试检查多个模块如何协同工作——例如,控制器、服务和真实数据库(或其替代品,如内存中的 SQLite)。在 ASP.NET Core 中,使用 WebApplicationFactory 来实现这一点。
配置 WebApplicationFactory
\n创建一个测试类,用于启动测试 Web 主机:
\nusing Microsoft.AspNetCore.Mvc.Testing;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\nusing MyApp.Data;\n\npublic class CustomWebApplicationFactory : WebApplicationFactory<Program>\n{\n protected override void ConfigureWebHost(IWebHostBuilder builder)\n {\n builder.ConfigureServices(services =>\n {\n // 移除真实的 DbContext\n var descriptor = services.SingleOrDefault(\n d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));\n if (descriptor != null) services.Remove(descriptor);\n\n // 添加内存数据库\n services.AddDbContext<AppDbContext>(options =>\n