C# 测试:单元测试、集成测试、端到端测试——高级水平

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么要在高级水平上编写测试?

C# 测试的初级水平通常局限于简单的 Assert.Equal 和孤立的单元测试。然而,在真实的企业级项目中,这远远不够。缺陷常常出现在组件之间的接口处——与数据库、外部 API 交互时,或是在具有副作用的复杂业务逻辑中。

C# (.NET) 中的高级测试意味着能够构建测试金字塔,其中单元测试快速且独立,集成测试验证真实的交互,而端到端测试模拟用户行为。本文将介绍现代工具:xUnit、Moq、FluentAssertions、Testcontainers 和 WebApplicationFactory。您将学习如何编写可靠的测试,这些测试在重构时不会失效,并让您对代码充满信心。



1. 高级单元测试:模拟对象和桩对象

单元测试必须与外部依赖隔离。在高级水平上,我们使用诸如 Moq 或 NSubstitute 之类的库来创建模拟对象。



正确配置模拟对象

不要手动创建桩对象,而是使用 Mock.Of<T>() 进行快速配置。以下是使用 Moq 和 FluentAssertions 的示例:

public interface IEmailService{    Task SendAsync(string to, string subject);}

public class NotificationService{ private readonly IEmailService _emailService;

public NotificationService(IEmailService emailService) { _emailService = emailService; }

public async Task NotifyUserAsync(string email) { await _emailService.SendAsync(email, "Welcome!"); }}

// 测试[Fact]public async Task NotifyUserAsync_ShouldSendEmail(){ // 准备 var mockEmailService = new Mock<IEmailService>(); var service = new NotificationService(mockEmailService.Object);

// 执行 await service.NotifyUserAsync("test@example.com");

// 断言 mockEmailService.Verify( x => x.SendAsync("test@example.com", "Welcome!"), Times.Once);}


避免测试实现细节

高级方法是测试行为,而不是内部调用。仅对关键依赖项使用 Verify。对于其他依赖项,请验证结果。



2. 集成测试:数据库和外部服务

集成测试验证多个模块是否协同工作。对于 .NET Core/5+,理想的工具是 WebApplicationFactory(用于 ASP.NET Core)和 Testcontainers(用于依赖项的容器化)。



使用真实数据库配置 WebApplicationFactory

不要使用 InMemory 提供程序(其行为与真实数据库不同),而是使用 Testcontainers 在 Docker 中启动 PostgreSQL 或 SQL Server:

public class IntegrationTestFixture : IAsyncLifetime{    private readonly TestcontainersContainer _dbContainer;

public string ConnectionString { get; private set; }

public IntegrationTestFixture() { _dbContainer = new TestcontainersBuilder<TestcontainersContainer>() .WithImage("postgres:15") .WithPortBinding(5432, true) .WithEnvironment("POSTGRES_USER", "test") .WithEnvironment("POSTGRES_PASSWORD", "test") .WithEnvironment("POSTGRES_DB", "testdb") .Build(); }

public async Task InitializeAsync() { await _dbContainer.StartAsync(); ConnectionString = $"Host=localhost;Port={_dbContainer.GetMappedPublicPort(5432)};" + $"Database=testdb;Username=test;Password=test"; }

public async Task DisposeAsync() { await _dbConta

博客

书籍推荐