dotnet-testing-advanced-aspnet-integration-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseASP.NET Core 整合測試指南
ASP.NET Core 集成测试指南
適用情境
适用场景
本技能指導如何在 ASP.NET Core 中建立有效的整合測試,使用 和 測試完整的 HTTP 請求/回應流程。
WebApplicationFactory<T>TestServer本技能指导如何在 ASP.NET Core 中构建有效的集成测试,使用 和 测试完整的 HTTP 请求/响应流程。
WebApplicationFactory<T>TestServer適用場景
适用场景
- Web API 端點測試:驗證 RESTful API 的 CRUD 操作
- HTTP 請求/回應驗證:測試完整的請求處理管線
- 中介軟體測試:驗證 Authentication、Authorization、Logging 等
- 依賴注入驗證:確保 DI 容器設定正確
- 路由設定驗證:確保 URL 路由正確對應到控制器動作
- 模型繫結測試:驗證請求內容正確繫結到模型
- Web API 端点测试:验证 RESTful API 的 CRUD 操作
- HTTP 请求/响应验证:测试完整的请求处理管线
- 中间件测试:验证 Authentication、Authorization、Logging 等中间件
- 依赖注入验证:确保 DI 容器配置正确
- 路由配置验证:确保 URL 路由正确映射到控制器动作
- 模型绑定测试:验证请求内容正确绑定到模型
必要套件
必要包
xml
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
<PackageReference Include="AwesomeAssertions.Web" Version="1.9.6" />
<PackageReference Include="System.Net.Http.Json" Version="9.0.8" />⚠️ 重要提醒:使用時,必須安裝AwesomeAssertions,而非AwesomeAssertions.Web。FluentAssertions.Web
xml
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
<PackageReference Include="AwesomeAssertions.Web" Version="1.9.6" />
<PackageReference Include="System.Net.Http.Json" Version="9.0.8" />⚠️ 重要提醒:使用时,必须安装AwesomeAssertions,而非AwesomeAssertions.Web。FluentAssertions.Web
核心概念
核心概念
整合測試的兩種定義
集成测试的两种定义
定義一:多物件協作測試
將兩個以上的類別做整合,並且測試它們之間的運作是不是正確的,測試案例一定是跨類別物件的
定義二:外部資源整合測試
會使用到外部資源,例如資料庫、外部服務、檔案、需要對測試環境進行特別處理等
定义一:多对象协作测试
将两个以上的类进行整合,并测试它们之间的协作是否正确,测试案例一定跨类对象
定义二:外部资源整合测试
会用到外部资源,例如数据库、外部服务、文件,需要对测试环境进行特殊处理等
為什麼需要整合測試?
为什么需要集成测试?
- 確保多個模組在整合運作後,能夠正確工作
- 單元測試無法涵蓋的整合點:Routing、Middleware、Request/Response Pipeline
- WebApplication 做了太多的整合與設定,單元測試無法確認到全部
- 確認是否完善異常處理,減少更多問題的發生
- 确保多个模块整合后能正常工作
- 覆盖单元测试无法涉及的集成点:Routing、Middleware、Request/Response Pipeline
- WebApplication 包含过多整合与配置,单元测试无法全面验证
- 完善异常处理,减少问题发生
測試金字塔定位
测试金字塔定位
| 測試類型 | 測試範圍 | 執行速度 | 維護成本 | 建議比例 |
|---|---|---|---|---|
| 單元測試 | 單一類別/方法 | 很快 | 低 | 70% |
| 整合測試 | 多個元件 | 中等 | 中等 | 20% |
| 端對端測試 | 完整流程 | 慢 | 高 | 10% |
| 测试类型 | 测试范围 | 执行速度 | 维护成本 | 建议比例 |
|---|---|---|---|---|
| 单元测试 | 单一类/方法 | 很快 | 低 | 70% |
| 集成测试 | 多个组件 | 中等 | 中等 | 20% |
| 端对端测试 | 完整流程 | 慢 | 高 | 10% |
原則一:使用 WebApplicationFactory 建立測試環境
原则一:使用 WebApplicationFactory 搭建测试环境
基本使用方式
基本使用方式
csharp
public class BasicIntegrationTest : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public BasicIntegrationTest(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task Get_首頁_應回傳成功()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/");
// Assert
response.EnsureSuccessStatusCode();
}
}csharp
public class BasicIntegrationTest : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public BasicIntegrationTest(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task Get_首页_应返回成功()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/");
// Assert
response.EnsureSuccessStatusCode();
}
}自訂 WebApplicationFactory
自定义 WebApplicationFactory
csharp
public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram>
where TProgram : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// 移除原本的資料庫設定
services.RemoveAll(typeof(DbContextOptions<AppDbContext>));
// 加入記憶體資料庫
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase("TestDatabase");
});
// 替換外部服務為測試版本
services.Replace(ServiceDescriptor.Scoped<IEmailService, TestEmailService>());
});
// 設定測試環境
builder.UseEnvironment("Testing");
}
}csharp
public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram>
where TProgram : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// 移除原有的数据库配置
services.RemoveAll(typeof(DbContextOptions<AppDbContext>));
// 添加内存数据库
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase("TestDatabase");
});
// 将外部服务替换为测试版本
services.Replace(ServiceDescriptor.Scoped<IEmailService, TestEmailService>());
});
// 设置测试环境
builder.UseEnvironment("Testing");
}
}原則二:使用 AwesomeAssertions.Web 驗證 HTTP 回應
原则二:使用 AwesomeAssertions.Web 验证 HTTP 响应
HTTP 狀態碼斷言
HTTP 状态码断言
csharp
response.Should().Be200Ok(); // HTTP 200
response.Should().Be201Created(); // HTTP 201
response.Should().Be204NoContent(); // HTTP 204
response.Should().Be400BadRequest(); // HTTP 400
response.Should().Be404NotFound(); // HTTP 404
response.Should().Be500InternalServerError(); // HTTP 500csharp
response.Should().Be200Ok(); // HTTP 200
response.Should().Be201Created(); // HTTP 201
response.Should().Be204NoContent(); // HTTP 204
response.Should().Be400BadRequest(); // HTTP 400
response.Should().Be404NotFound(); // HTTP 404
response.Should().Be500InternalServerError(); // HTTP 500Satisfy<T> 強型別驗證
Satisfy<T> 强类型验证
csharp
[Fact]
public async Task GetShipper_當貨運商存在_應回傳成功結果()
{
// Arrange
await CleanupDatabaseAsync();
var shipperId = await SeedShipperAsync("順豐速運", "02-2345-6789");
// Act
var response = await Client.GetAsync($"/api/shippers/{shipperId}");
// Assert
response.Should().Be200Ok()
.And
.Satisfy<SuccessResultOutputModel<ShipperOutputModel>>(result =>
{
result.Status.Should().Be("Success");
result.Data.Should().NotBeNull();
result.Data!.ShipperId.Should().Be(shipperId);
result.Data.CompanyName.Should().Be("順豐速運");
result.Data.Phone.Should().Be("02-2345-6789");
});
}csharp
[Fact]
public async Task GetShipper_当货运商存在_应返回成功结果()
{
// Arrange
await CleanupDatabaseAsync();
var shipperId = await SeedShipperAsync("顺丰速运", "02-2345-6789");
// Act
var response = await Client.GetAsync($"/api/shippers/{shipperId}");
// Assert
response.Should().Be200Ok()
.And
.Satisfy<SuccessResultOutputModel<ShipperOutputModel>>(result =>
{
result.Status.Should().Be("Success");
result.Data.Should().NotBeNull();
result.Data!.ShipperId.Should().Be(shipperId);
result.Data.CompanyName.Should().Be("顺丰速运");
result.Data.Phone.Should().Be("02-2345-6789");
});
}與傳統方式的比較
与传统方式的比较
csharp
// ❌ 傳統方式 - 冗長且容易出錯
response.IsSuccessStatusCode.Should().BeTrue();
var content = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<SuccessResultOutputModel<ShipperOutputModel>>(content,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
result.Should().NotBeNull();
result!.Status.Should().Be("Success");
// ✅ 使用 Satisfy<T> - 簡潔且直觀
response.Should().Be200Ok()
.And
.Satisfy<SuccessResultOutputModel<ShipperOutputModel>>(result =>
{
result.Status.Should().Be("Success");
result.Data!.CompanyName.Should().Be("測試公司");
});csharp
// ❌ 传统方式 - 冗长且易出错
response.IsSuccessStatusCode.Should().BeTrue();
var content = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<SuccessResultOutputModel<ShipperOutputModel>>(content,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
result.Should().NotBeNull();
result!.Status.Should().Be("Success");
// ✅ 使用 Satisfy<T> - 简洁直观
response.Should().Be200Ok()
.And
.Satisfy<SuccessResultOutputModel<ShipperOutputModel>>(result =>
{
result.Status.Should().Be("Success");
result.Data!.CompanyName.Should().Be("测试公司");
});原則三:使用 System.Net.Http.Json 簡化 JSON 操作
原则三:使用 System.Net.Http.Json 简化 JSON 操作
PostAsJsonAsync 簡化 POST 請求
PostAsJsonAsync 简化 POST 请求
csharp
// ❌ 傳統方式
var createParameter = new ShipperCreateParameter { CompanyName = "測試公司", Phone = "02-1234-5678" };
var jsonContent = JsonSerializer.Serialize(createParameter);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await client.PostAsync("/api/shippers", content);
// ✅ 現代化方式
var createParameter = new ShipperCreateParameter { CompanyName = "測試公司", Phone = "02-1234-5678" };
var response = await client.PostAsJsonAsync("/api/shippers", createParameter);csharp
// ❌ 传统方式
var createParameter = new ShipperCreateParameter { CompanyName = "测试公司", Phone = "02-1234-5678" };
var jsonContent = JsonSerializer.Serialize(createParameter);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await client.PostAsync("/api/shippers", content);
// ✅ 现代化方式
var createParameter = new ShipperCreateParameter { CompanyName = "测试公司", Phone = "02-1234-5678" };
var response = await client.PostAsJsonAsync("/api/shippers", createParameter);ReadFromJsonAsync 簡化回應讀取
ReadFromJsonAsync 简化响应读取
csharp
// ❌ 傳統方式
var responseContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<SuccessResultOutputModel<ShipperOutputModel>>(responseContent,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
// ✅ 現代化方式
var result = await response.Content.ReadFromJsonAsync<SuccessResultOutputModel<ShipperOutputModel>>();csharp
// ❌ 传统方式
var responseContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<SuccessResultOutputModel<ShipperOutputModel>>(responseContent,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
// ✅ 现代化方式
var result = await response.Content.ReadFromJsonAsync<SuccessResultOutputModel<ShipperOutputModel>>();三個層級的整合測試策略
三个层级的集成测试策略
Level 1:簡單的 WebApi 專案
Level 1:简单的 WebApi 项目
特色:
- 沒有資料庫、Service 與 Repository 依賴
- 最簡單、基本的 WebApi 網站專案
- 直接使用 進行測試
WebApplicationFactory<Program>
測試重點:
- 各個 API 的輸入輸出驗證
- HTTP 動詞和路由正確性
- 模型綁定和序列化
- 狀態碼和回應格式驗證
csharp
public class BasicApiControllerTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public BasicApiControllerTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task GetStatus_應回傳OK()
{
// Act
var response = await _client.GetAsync("/api/status");
// Assert
response.Should().Be200Ok();
}
}特色:
- 无数据库、Service 与 Repository 依赖
- 最简单、基础的 WebApi 网站项目
- 直接使用 进行测试
WebApplicationFactory<Program>
测试重点:
- 各 API 的输入输出验证
- HTTP 动词和路由正确性
- 模型绑定和序列化
- 状态码和响应格式验证
csharp
public class BasicApiControllerTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public BasicApiControllerTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task GetStatus_应返回OK()
{
// Act
var response = await _client.GetAsync("/api/status");
// Assert
response.Should().Be200Ok();
}
}Level 2:相依 Service 的 WebApi 專案
Level 2:依赖 Service 的 WebApi 项目
特色:
- 沒有資料庫,但有 Service 依賴
- 使用 NSubstitute 建立 Service stub
- 在測試中配置依賴注入
csharp
public class ServiceStubWebApplicationFactory : WebApplicationFactory<Program>
{
private readonly IExampleService _serviceStub;
public ServiceStubWebApplicationFactory(IExampleService serviceStub)
{
_serviceStub = serviceStub;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.RemoveAll<IExampleService>();
services.AddScoped(_ => _serviceStub);
});
}
}
public class ServiceDependentControllerTests
{
[Fact]
public async Task GetData_應回傳服務資料()
{
// Arrange
var serviceStub = Substitute.For<IExampleService>();
serviceStub.GetDataAsync().Returns("測試資料");
var factory = new ServiceStubWebApplicationFactory(serviceStub);
var client = factory.CreateClient();
// Act
var response = await client.GetAsync("/api/data");
// Assert
response.Should().Be200Ok();
}
}特色:
- 无数据库,但存在 Service 依赖
- 使用 NSubstitute 构建 Service stub
- 在测试中配置依赖注入
csharp
public class ServiceStubWebApplicationFactory : WebApplicationFactory<Program>
{
private readonly IExampleService _serviceStub;
public ServiceStubWebApplicationFactory(IExampleService serviceStub)
{
_serviceStub = serviceStub;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.RemoveAll<IExampleService>();
services.AddScoped(_ => _serviceStub);
});
}
}
public class ServiceDependentControllerTests
{
[Fact]
public async Task GetData_应返回服务数据()
{
// Arrange
var serviceStub = Substitute.For<IExampleService>();
serviceStub.GetDataAsync().Returns("测试数据");
var factory = new ServiceStubWebApplicationFactory(serviceStub);
var client = factory.CreateClient();
// Act
var response = await client.GetAsync("/api/data");
// Assert
response.Should().Be200Ok();
}
}Level 3:完整的 WebApi 專案
Level 3:完整的 WebApi 项目
特色:
- 完整的 Solution 架構
- 包含真實的資料庫操作
- 使用 InMemory 或真實測試資料庫
csharp
public class FullDatabaseWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// 移除原本的資料庫設定
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
// 加入記憶體資料庫
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase("TestDatabase");
});
// 建立資料庫並加入測試資料
var serviceProvider = services.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
context.Database.EnsureCreated();
});
}
}特色:
- 完整的解决方案架构
- 包含真实的数据库操作
- 使用 InMemory 或真实测试数据库
csharp
public class FullDatabaseWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// 移除原有的数据库配置
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
// 添加内存数据库
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase("TestDatabase");
});
// 创建数据库并添加测试数据
var serviceProvider = services.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
context.Database.EnsureCreated();
});
}
}測試基底類別模式
测试基类模式
建立可重用的測試基底類別
构建可复用的测试基类
csharp
public abstract class IntegrationTestBase : IDisposable
{
protected readonly CustomWebApplicationFactory Factory;
protected readonly HttpClient Client;
protected IntegrationTestBase()
{
Factory = new CustomWebApplicationFactory();
Client = Factory.CreateClient();
}
protected async Task<int> SeedShipperAsync(string companyName, string phone = "02-12345678")
{
using var scope = Factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var shipper = new Shipper
{
CompanyName = companyName,
Phone = phone,
CreatedAt = DateTime.UtcNow
};
context.Shippers.Add(shipper);
await context.SaveChangesAsync();
return shipper.ShipperId;
}
protected async Task CleanupDatabaseAsync()
{
using var scope = Factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
context.Shippers.RemoveRange(context.Shippers);
await context.SaveChangesAsync();
}
public void Dispose()
{
Client?.Dispose();
Factory?.Dispose();
}
}csharp
public abstract class IntegrationTestBase : IDisposable
{
protected readonly CustomWebApplicationFactory Factory;
protected readonly HttpClient Client;
protected IntegrationTestBase()
{
Factory = new CustomWebApplicationFactory();
Client = Factory.CreateClient();
}
protected async Task<int> SeedShipperAsync(string companyName, string phone = "02-12345678")
{
using var scope = Factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var shipper = new Shipper
{
CompanyName = companyName,
Phone = phone,
CreatedAt = DateTime.UtcNow
};
context.Shippers.Add(shipper);
await context.SaveChangesAsync();
return shipper.ShipperId;
}
protected async Task CleanupDatabaseAsync()
{
using var scope = Factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
context.Shippers.RemoveRange(context.Shippers);
await context.SaveChangesAsync();
}
public void Dispose()
{
Client?.Dispose();
Factory?.Dispose();
}
}CRUD 操作測試範例
CRUD 操作测试示例
完整的 CRUD 操作測試程式碼(GET、POST、驗證錯誤、集合查詢)請參考 📄 CRUD 操作測試完整範例
完整的 CRUD 操作测试代码(GET、POST、验证错误、集合查询)请参考 📄 CRUD 操作测试完整示例
專案結構建議
项目结构建议
text
tests/
├── Sample.WebApplication.UnitTests/ # 單元測試
├── Sample.WebApplication.Integration.Tests/ # 整合測試
│ ├── Controllers/ # 控制器整合測試
│ │ └── ShippersControllerTests.cs
│ ├── Infrastructure/ # 測試基礎設施
│ │ └── CustomWebApplicationFactory.cs
│ ├── IntegrationTestBase.cs # 測試基底類別
│ └── GlobalUsings.cs
└── Sample.WebApplication.E2ETests/ # 端對端測試text
tests/
├── Sample.WebApplication.UnitTests/ # 单元测试
├── Sample.WebApplication.Integration.Tests/ # 集成测试
│ ├── Controllers/ # 控制器集成测试
│ │ └── ShippersControllerTests.cs
│ ├── Infrastructure/ # 测试基础设施
│ │ └── CustomWebApplicationFactory.cs
│ ├── IntegrationTestBase.cs # 测试基类
│ └── GlobalUsings.cs
└── Sample.WebApplication.E2ETests/ # 端对端测试套件相容性故障排除
包兼容性故障排除
常見錯誤
常见错误
text
error CS1061: 'ObjectAssertions' 未包含 'Be200Ok' 的定義text
error CS1061: 'ObjectAssertions' 未包含 'Be200Ok' 的定义解決方案
解决方案
| 基礎斷言庫 | 正確的套件 |
|---|---|
| FluentAssertions < 8.0.0 | FluentAssertions.Web |
| FluentAssertions >= 8.0.0 | FluentAssertions.Web.v8 |
| AwesomeAssertions >= 8.0.0 | AwesomeAssertions.Web |
xml
<!-- 正確:使用 AwesomeAssertions 應該安裝 AwesomeAssertions.Web -->
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
<PackageReference Include="AwesomeAssertions.Web" Version="1.9.6" />| 基础断言库 | 正确的包 |
|---|---|
| FluentAssertions < 8.0.0 | FluentAssertions.Web |
| FluentAssertions >= 8.0.0 | FluentAssertions.Web.v8 |
| AwesomeAssertions >= 8.0.0 | AwesomeAssertions.Web |
xml
<!-- 正确:使用 AwesomeAssertions 应安装 AwesomeAssertions.Web -->
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
<PackageReference Include="AwesomeAssertions.Web" Version="1.9.6" />最佳實踐
最佳实践
應該做的 ✅
应该做的 ✅
- 獨立測試專案:整合測試專案應與單元測試分離
- 測試資料隔離:每個測試案例有獨立的資料準備和清理
- 使用基底類別:共用的設定和輔助方法放在基底類別
- 明確的命名:使用三段式命名法(方法_情境_預期)
- 適當的測試範圍:專注於整合點,不要過度測試
- 独立测试项目:集成测试项目应与单元测试分离
- 测试数据隔离:每个测试案例有独立的数据准备和清理
- 使用基类:将共用的配置和辅助方法放在基类中
- 明确的命名:使用三段式命名法(方法_场景_预期)
- 适当的测试范围:专注于集成点,不要过度测试
應該避免的 ❌
应该避免的 ❌
- 混合測試類型:不要將單元測試和整合測試放在同一專案
- 測試相依性:每個測試應該獨立,不依賴其他測試的執行順序
- 過度模擬:整合測試應該盡量使用真實的元件
- 忽略清理:測試完成後要清理測試資料
- 硬編碼資料:使用工廠方法或 Builder 模式建立測試資料
- 混合测试类型:不要将单元测试和集成测试放在同一项目中
- 测试依赖性:每个测试应独立,不依赖其他测试的执行顺序
- 过度模拟:集成测试应尽量使用真实组件
- 忽略清理:测试完成后要清理测试数据
- 硬编码数据:使用工厂方法或 Builder 模式创建测试数据
相關技能
相关技能
- - 單元測試基礎
unit-test-fundamentals - - 使用 NSubstitute 進行模擬
nsubstitute-mocking - - AwesomeAssertions 流暢斷言
awesome-assertions-guide - - 使用 Testcontainers 進行容器化資料庫測試
testcontainers-database
- - 单元测试基础
unit-test-fundamentals - - 使用 NSubstitute 进行模拟
nsubstitute-mocking - - AwesomeAssertions 流畅断言
awesome-assertions-guide - - 使用 Testcontainers 进行容器化数据库测试
testcontainers-database
參考資源
参考资源
原始文章
原始文章
本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章:
- Day 19 - 整合測試入門:基礎架構與應用場景
本技能内容提炼自「老派软件工程师的测试修炼 - 30 天挑战」系列文章:
- Day 19 - 集成测试入门:基础架构与应用场景