C# 测试:从单元测试到端到端测试——如何在 .NET 中构建高质量的检查架构
软件质量直接取决于测试体系。在 C# 和 .NET 生态系统中,存在一个成熟的测试金字塔:单元测试(unit)、集成测试(integration)和端到端测试(end-to-end)。本文将结合实际示例,逐一解析每个层级,展示应使用哪些库,并提供现成的代码模板。
1. 单元测试:代码稳定性的基础
单元测试用于独立于外部依赖(数据库、API、文件系统)检查单个方法、类或组件的功能。主要目标是在重构时快速发现回归问题。
C# 工具
- NUnit — 经典框架,使用
[Test]、[TestCase]属性。 - xUnit — 由 NUnit 创建者开发的现代框架,在新项目中更受青睐。
- Moq — 用于创建 mock 对象的库。
- FluentAssertions — 用于编写可读性强的断言。
使用 xUnit 和 Moq 的单元测试示例
假设我们有一个折扣计算服务,它需要访问用户仓库:
public interface IUserRepository{ bool IsPremiumUser(int userId);}
public class DiscountService{ private readonly IUserRepository _userRepo; public DiscountService(IUserRepository userRepo) => _userRepo = userRepo;
public decimal CalculateDiscount(int userId, decimal amount) { if (_userRepo.IsPremiumUser(userId)) return amount * 0.9m; // 10% 折扣 return amount; }}
// 测试public class DiscountServiceTests{ [Fact] public void CalculateDiscount_ForPremiumUser_Returns90Percent() { // Arrange var mockRepo = new Mock<IUserRepository>(); mockRepo.Setup(r => r.IsPremiumUser(It.IsAny<int>())).Returns(true); var service = new DiscountService(mockRepo.Object);
// Act var result = service.CalculateDiscount(1, 100m);
// Assert Assert.Equal(90m, result); }}建议: 在 xUnit 中使用 [Theory] 和 [InlineData] 进行参数化测试——这可以减少代码重复。
2. 集成测试:检查组件间的协作
集成测试用于检查您的模块如何与真实的外部系统(如数据库、消息队列、Web 服务)进行交互。与单元测试不同,这里不使用 mock——我们启动真实的运行环境。
库与方法
- TestContainers — 直接从测试中启动 Docker 容器(PostgreSQL、SQL Server、Redis)。
- WebApplicationFactory(来自 Microsoft.AspNetCore.Mvc.Testing)— 创建 ASP.NET Core 测试主机。
- Respawn — 在测试之间清理数据库,无需重新创建容器。
使用 TestContainers 和 WebApplicationFactory 的集成测试示例
我们来验证 API 是否正确地将用户保存到 PostgreSQL:
public class UserApiIntegrationTests : IClassFixture<TestContainersFixture>{ private readonly HttpClient _client; private readonly TestContainersFixture _fixture;
public UserApiIntegrationTests(TestContainersFixture fixture) { _fixture = fixture; var factory = new WebApplicationFactory<Program>() .WithWebHostBuilder(builder => { builder.ConfigureTestServices(services => { // 将真实连接字符串替换为容器连接字符串 services.RemoveAll<DbContextOptions<AppDbContext>>(); se