——零侵入框架设计、自动化工具链与真实场景模拟
在微服务架构下,C#应用的复杂性呈指数级增长。集成测试(Integration Testing) 和 端到端测试(E2E Testing) 是保障系统稳定性的两大核心防线:
本文通过代码实战,从依赖注入模拟到浏览器自动化,构建一个企业级测试框架,并附上深度注释与工程化优化策略!
// 项目文件:.csproj
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0" />
<PackageReference Include="Moq" Version="22.0.0" />
<PackageReference Include="xUnit" Version="2.4.1" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
</ItemGroup>
</Project>
// 服务层接口
public interface IOrderService
{
Task<Order> CreateOrderAsync(Order order);
Task<Order?> GetOrderAsync(Guid id);
}
// 模拟实现
public class MockOrderService : IOrderService
{
private readonly Mock<Order> _mockOrder = new();
public Task<Order> CreateOrderAsync(Order order)
{
_mockOrder.Setup(m => m.Id).Returns(Guid.NewGuid());
return Task.FromResult(_mockOrder.Object);
}
public Task<Order?> GetOrderAsync(Guid id)
{
return Task.FromResult(_mockOrder.Object);
}
}
注释解析:
Moq
:通过Setup
定义模拟行为,Returns
指定返回值。MockOrderService
:模拟真实服务,避免依赖真实数据库或API。// 测试类:OrderServiceTests.cs
public class OrderServiceTests
{
private readonly IOrderService _orderService;
public OrderServiceTests()
{
_orderService = new MockOrderService();
}
[Fact]
public async Task CreateOrderAsync_ShouldReturnNewOrder()
{
// Arrange
var order = new Order { CustomerId = "C123" };
// Act
var result = await _orderService.CreateOrderAsync(order);
// Assert
result.Should().NotBeNull();
result.Id.Should().NotBeEmpty();
}
[Fact]
public async Task GetOrderAsync_WithExistingId_ShouldReturnOrder()
{
// Arrange
var existingId = Guid.NewGuid();
var mockOrder = new Mock<Order>();
mockOrder.Setup(m => m.Id).Returns(existingId);
// Act
var result = await _orderService.GetOrderAsync(existingId);
// Assert
result.Should().Be(mockOrder.Object);
}
}
关键点:
[Fact]
:标记测试用例。FluentAssertions
:链式断言更易读。// 安装NuGet包
dotnet add package Microsoft.Playwright
// 配置测试环境
public class TestFixture : IAsyncLifetime
{
public IPlaywright Playwright { get; private set; } = null!;
public IBrowser Browser { get; private set; } = null!;
public IPage Page { get; private set; } = null!;
public async Task InitializeAsync()
{
Playwright = await Playwright.CreateAsync();
Browser = await Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = false // 显示浏览器窗口
});
Page = await Browser.NewPageAsync();
}
public async Task DisposeAsync()
{
await Browser.CloseAsync();
await Playwright.StopAsync();
}
}
// 测试类:OrderCheckoutE2ETests.cs
public class OrderCheckoutE2ETests : IClassFixture<TestFixture>
{
private readonly IPage _page;
public OrderCheckoutE2ETests(TestFixture fixture)
{
_page = fixture.Page;
}
[Fact]
public async Task CheckoutProcess_ShouldCompleteSuccessfully()
{
// Arrange
await _page.GotoAsync("https://localhost:5001/checkout");
await _page.FillAsync("#customerName", "John Doe");
await _page.FillAsync("#email", "[email protected]");
await _page.ClickAsync("#placeOrderButton");
// Assert
await _page.WaitForSelectorAsync(".success-message");
await _page.Locator(".order-id").InnerTextAsync().Should().NotBeEmpty();
}
[Fact]
public async Task InvalidEmail_ShouldShowError()
{
// Arrange
await _page.GotoAsync("https://localhost:5001/checkout");
await _page.FillAsync("#email", "invalid-email");
// Act
await _page.ClickAsync("#placeOrderButton");
// Assert
await _page.WaitForSelectorAsync(".error-message");
await _page.Locator(".error-message").InnerTextAsync().Should().Contain("Invalid email format");
}
}
注释解析:
Playwright
:支持 Chromium、Firefox、Webkit 多浏览器。Headless = false
:测试时显示浏览器窗口便于调试。// 使用Theory + ClassData
[Theory]
[ClassData(typeof(InvalidEmailData))]
public async Task CheckoutWithInvalidEmail_ShouldFail(string email, string expectedError)
{
await _page.GotoAsync("https://localhost:5001/checkout");
await _page.FillAsync("#email", email);
await _page.ClickAsync("#placeOrderButton");
await _page.Locator(".error-message").InnerTextAsync().Should().Contain(expectedError);
}
// 数据提供类
public class InvalidEmailData : TheoryData<string, string>
{
public InvalidEmailData()
{
Add("john@example", "Invalid email format");
Add("[email protected]", "Invalid email format");
Add("", "Email is required");
}
}
// 在项目级配置并行执行
[assembly: CollectionBehavior(DisableTestParallelization = false)]
// 在测试类上启用并行
[Collection("Parallel Tests")]
public class OrderServiceTests { ... }
// 在测试方法上控制并行级别
[Fact(Skip = "Not Parallelizable")]
public async Task NonParallelizableTest() { ... }
# azure-pipelines.yml
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- script: |
dotnet restore
dotnet build --configuration Release
displayName: 'Build Solution'
- script: |
dotnet test Tests/IntegrationTests/Integration.Tests.csproj --logger:trx
dotnet test Tests/E2ETests/E2E.Tests.csproj --logger:trx
displayName: 'Run Tests'
- task: PublishTestResults@2
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/*.trx'
displayName: 'Publish Test Results'
// 配置文件:appsettings.Test.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=TestDB;User=sa;Password=TestPass123;"
},
"ThirdPartyApis": {
"PaymentGatewayUrl": "https://sandbox.payment.com/api"
}
}
# 使用Coverlet生成覆盖率报告
dotnet test --collect:"XPlat Code Coverage"
reportgenerator "-reports:coverage.cobertura.xml" "-targetdir:coveragereport" "-reporttypes:Html"
// 在测试代码中添加断点
await _page.ClickAsync("#placeOrderButton");
Debugger.Break(); // 触发调试器
// 测试类:OrderToPaymentE2ETests.cs
public class OrderToPaymentE2ETests : IClassFixture<TestFixture>
{
private readonly IPage _page;
public OrderToPaymentE2ETests(TestFixture fixture)
{
_page = fixture.Page;
}
[Fact]
public async Task OrderLifecycle_ShouldComplete()
{
// 1. 登录
await _page.GotoAsync("/login");
await _page.FillAsync("#username", "admin");
await _page.FillAsync("#password", "SecurePass123!");
await _page.ClickAsync("#loginButton");
// 2. 创建订单
await _page.GotoAsync("/orders/new");
await _page.FillAsync("#customerName", "Jane Doe");
await _page.ClickAsync("#addItemButton");
await _page.SelectOptionAsync("#productSelect", "Product A");
// 3. 支付流程
await _page.GotoAsync("/payment");
await _page.FillAsync("#cardNumber", "4242424242424242");
await _page.ClickAsync("#payButton");
// 4. 验证成功
await _page.WaitForSelectorAsync(".payment-success");
var orderId = await _page.Locator("#orderId").InnerTextAsync();
orderId.Should().NotBeEmpty();
}
}
维度 | C#实践方案 | 工具/技术 |
---|---|---|
集成测试 | Moq + xUnit + 依赖注入 | FluentAssertions、HttpClientFactory |
端到端测试 | Playwright + 多浏览器支持 | Selenium Grid、Azure DevOps |
性能 | 并行测试 + 覆盖率分析 | Coverlet、ReportGenerator |
环境隔离 | 配置文件 + 依赖注入工厂 | IConfiguration、AppDomain |
附录:快速上手命令
# 1. 初始化测试项目
dotnet new mstest -n MyProject.Tests
# 2. 安装端到端测试依赖
dotnet add package Microsoft.Playwright
# 3. 运行所有测试
dotnet test --filter "TestCategory=Integration,E2E"
# 4. 生成覆盖率报告
dotnet test --collect:"XPlat Code Coverage"
代码即质量,测试即信心——掌握这套C#测试体系,让微服务架构在复杂场景下依然坚如磐石!