現代軟體應用很少獨立工作。典型的應用程式會與幾個外部系統進行通信,如: 資料庫、 消息系統、 緩存提供商 其他第三方服務。 你應該編寫測試確保一切正常運行。 單元測試有助於隔離地測試業務邏輯,不涉及任何外部服務。它們易於編寫並提供幾乎即時的反饋。 有了單元測試還不夠,集成測試用來驗證與外部系統的交互 ...
現代軟體應用很少獨立工作。典型的應用程式會與幾個外部系統進行通信,如:
- 資料庫、
- 消息系統、
- 緩存提供商
- 其他第三方服務。
你應該編寫測試確保一切正常運行。
單元測試有助於隔離地測試業務邏輯,不涉及任何外部服務。它們易於編寫並提供幾乎即時的反饋。
有了單元測試還不夠,集成測試用來驗證與外部系統的交互情況,讓你對你的應用程式完全有信心。
所以,在本周的時事通訊中,我將向你展示如何使用Docker進行集成測試。我們需要以下組件
- TestContainers
- Docker
- xUnit
TestContainers是什麼
Testcontainers 是一個用於使用臨時 Docker 容器編寫測試的庫。
集成測試是“困難”的,因為你需要維護測試基礎設施。在運行測試之前,你需要確保資料庫已啟動並正在運行。你還必須為測試提供所需的任何數據。如果你的測試在同一資料庫上並行運行,它們可能會相互干擾。
一個可能的解決方案是使用所需服務的記憶體中變體。但這與使用mocks並沒有太大的不同。記憶體中的服務可能沒有生產服務的所有功能。
Testcontainers 通過使用 Docker 來啟動真實服務來解決這個問題,以進行集成測試。
下麵是創建 SQL Server 容器的示例:
MsSqlContainer dbContainer = new MsSqlBuilder() .WithImage("mcr.microsoft.com/mssql/server:2022-latest") .WithPassword("Strong_password_123!") .Build();
之後,你可以使用 MsSqlContainer
實例來獲取運行在容器內部的資料庫的連接。 這是一個真實的SQL Server資料庫,而不是記憶體資料庫。
自定義 WebApplicationFactory:
ASP.NET Core 提供了一個記憶體測試伺服器,我們可以用它來啟動一個應用程式實例來運行測試。Microsoft.AspNetCore.Mvc.Testing 包提供了我們將用作實現基礎的 WebApplicationFactory
類。
WebApplicationFactory<TEntryPoint>
用於為集成測試創建一個 TestServer
。
IntegrationTestWebAppFactory
進行以下工作:
- 創建並配置MySqlContainer實例
- 調用
ConfigureTestServices
用容器中的資料庫來設置EF Core - 用IAsyncLifetime來控制容器實例的啟動/停止
public class IntegrationTestWebAppFactory : WebApplicationFactory<Program>, IAsyncLifetime { private readonly MsSqlContainer _dbContainer = new MsSqlBuilder() .WithImage("mcr.microsoft.com/mssql/server:2022-latest") .WithPassword("Strong_password_123!") .Build(); protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureTestServices(services => { var descriptorType = typeof(DbContextOptions<ApplicationDbContext>); var descriptor = services .SingleOrDefault(s => s.ServiceType == descriptorType); if (descriptor is not null) { services.Remove(descriptor); } services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(_dbContainer.GetConnectionString())); }); } public Task InitializeAsync() { return _dbContainer.StartAsync(); } public new Task DisposeAsync() { return _dbContainer.StopAsync(); } }
MsSqlContainer
有一個 GetConnectionString
方法,用於獲取當前容器的連接字元串。連接字元串可能會在測試之間發生變化,因為每個測試類都會創建一個單獨的容器實例。在同一測試類中的測試案例將使用相同的容器實例。因此,如果你需要在測試之間進行清理,請記住這一點。
另一件事是Data Migration。你必須在每次測試之前顯示運行,以創建所需的資料庫結構。
使用 IAsyncLifetime
非同步啟動容器實例。在運行任何測試之前,容器是在 StartAsync
中啟動的。而它是在 StopAsync
中停止的。
創建測試基類
測試基類將實現一個類固件介面 IClassFixture
。併在測試用例之間提供共用的對象實例。這是實例化大多數測試所需的任何服務的地方。
例如,我正在創建一個 IServiceScope
,用於在測試中解析scoped services。
- ISender 用來發送command和queries
- ApplicationDbContext 用來設置資料庫或者驗證結果
public abstract class BaseIntegrationTest : IClassFixture<IntegrationTestWebAppFactory>, IDisposable { private readonly IServiceScope _scope; protected readonly ISender Sender; protected readonly ApplicationDbContext DbContext; protected BaseIntegrationTest(IntegrationTestWebAppFactory factory) { _scope = factory.Services.CreateScope(); Sender = _scope.ServiceProvider.GetRequiredService<ISender>(); DbContext = _scope.ServiceProvider .GetRequiredService<ApplicationDbContext>(); } public void Dispose() { _scope?.Dispose(); DbContext?.Dispose(); } }
現在萬事俱備,可以寫測試用例了。
編寫測試用例
這裡有一個ProductTests類,裡面有一個集成測試的用例,我們使用Arragne, Act, Assert結構。
public class ProductTests : BaseIntegrationTest { public ProductTests(IntegrationTestWebAppFactory factory) : base(factory) { } [Fact] public async Task Create_ShouldCreateProduct() { // Arrange var command = new CreateProduct.Command { Name = "AMD Ryzen 7 7700X", Category = "CPU", Price = 223.99m }; // Act var productId = await Sender.Send(command); // Assert var product = DbContext .Products .FirstOrDefault(p => p.Id == productId); Assert.NotNull(product); } }
結束語
Testcontainers 是使用 Docker 編寫集成測試的出色解決方案。你可以啟動並配置任何 Docker 鏡像,並從你的應用程式中使用它。這比使用模擬或記憶體變體要好得多,它們缺乏許多功能。
如果你有一個支持 Docker 的 CI/CD 流水線,Testcontainers 將開箱即用。
有幾個集成測試可以大大增強你對系統的信心。