dotnet-testing-advanced-aspnet-integration-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ASP.NET Core 整合測試指南

ASP.NET Core 集成测试指南

適用情境

适用场景

本技能指導如何在 ASP.NET Core 中建立有效的整合測試,使用
WebApplicationFactory<T>
TestServer
測試完整的 HTTP 請求/回應流程。
本技能指导如何在 ASP.NET Core 中构建有效的集成测试,使用
WebApplicationFactory<T>
TestServer
测试完整的 HTTP 请求/响应流程。

適用場景

适用场景

  • 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 500
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 500

Satisfy<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.0FluentAssertions.Web
FluentAssertions >= 8.0.0FluentAssertions.Web.v8
AwesomeAssertions >= 8.0.0AwesomeAssertions.Web
xml
<!-- 正確:使用 AwesomeAssertions 應該安裝 AwesomeAssertions.Web -->
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
<PackageReference Include="AwesomeAssertions.Web" Version="1.9.6" />

基础断言库正确的包
FluentAssertions < 8.0.0FluentAssertions.Web
FluentAssertions >= 8.0.0FluentAssertions.Web.v8
AwesomeAssertions >= 8.0.0AwesomeAssertions.Web
xml
<!-- 正确:使用 AwesomeAssertions 应安装 AwesomeAssertions.Web -->
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
<PackageReference Include="AwesomeAssertions.Web" Version="1.9.6" />

最佳實踐

最佳实践

應該做的 ✅

应该做的 ✅

  1. 獨立測試專案:整合測試專案應與單元測試分離
  2. 測試資料隔離:每個測試案例有獨立的資料準備和清理
  3. 使用基底類別:共用的設定和輔助方法放在基底類別
  4. 明確的命名:使用三段式命名法(方法_情境_預期)
  5. 適當的測試範圍:專注於整合點,不要過度測試
  1. 独立测试项目:集成测试项目应与单元测试分离
  2. 测试数据隔离:每个测试案例有独立的数据准备和清理
  3. 使用基类:将共用的配置和辅助方法放在基类中
  4. 明确的命名:使用三段式命名法(方法_场景_预期)
  5. 适当的测试范围:专注于集成点,不要过度测试

應該避免的 ❌

应该避免的 ❌

  1. 混合測試類型:不要將單元測試和整合測試放在同一專案
  2. 測試相依性:每個測試應該獨立,不依賴其他測試的執行順序
  3. 過度模擬:整合測試應該盡量使用真實的元件
  4. 忽略清理:測試完成後要清理測試資料
  5. 硬編碼資料:使用工廠方法或 Builder 模式建立測試資料

  1. 混合测试类型:不要将单元测试和集成测试放在同一项目中
  2. 测试依赖性:每个测试应独立,不依赖其他测试的执行顺序
  3. 过度模拟:集成测试应尽量使用真实组件
  4. 忽略清理:测试完成后要清理测试数据
  5. 硬编码数据:使用工厂方法或 Builder 模式创建测试数据

相關技能

相关技能

  • unit-test-fundamentals
    - 單元測試基礎
  • nsubstitute-mocking
    - 使用 NSubstitute 進行模擬
  • awesome-assertions-guide
    - AwesomeAssertions 流暢斷言
  • testcontainers-database
    - 使用 Testcontainers 進行容器化資料庫測試

  • unit-test-fundamentals
    - 单元测试基础
  • nsubstitute-mocking
    - 使用 NSubstitute 进行模拟
  • awesome-assertions-guide
    - AwesomeAssertions 流畅断言
  • testcontainers-database
    - 使用 Testcontainers 进行容器化数据库测试

參考資源

参考资源

原始文章

原始文章

本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章:
本技能内容提炼自「老派软件工程师的测试修炼 - 30 天挑战」系列文章:

官方文件

官方文档