Integrationstests C# mit Docker: fortgeschrittene Techniken

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Integrationstests C# mit Docker: Fortgeschrittenes Niveau



Integrationstests sind eine kritische Phase der C#-Entwicklung, die es ermöglicht, die Interaktion von Systemkomponenten mit realen Abhängigkeiten zu überprüfen: Datenbanken, Nachrichtenwarteschlangen, externe APIs. Die Verwendung von Docker zu diesem Zweck ist zum Industriestandard geworden, aber auf fortgeschrittenem Niveau stellen sich Fragen der Isolierung, Leistung und parallelen Ausführung. In diesem Artikel analysieren wir professionelle Techniken für Integrationstests C# mit Docker unter Verwendung der Bibliothek Testcontainers und fortgeschrittener Konfigurationen.



1. Einrichtung von Testcontainers für .NET: Grundlegende Infrastruktur



Die Bibliothek Testcontainers für .NET ermöglicht die programmatische Verwaltung von Docker-Containern in der Testumgebung. Im Gegensatz zum manuellen Starten von docker-compose startet und stoppt Testcontainers Container automatisch für jede Testklasse oder jedes Modul und garantiert so eine saubere Umgebung.



Installieren Sie das Paket über NuGet:

dotnet add package Testcontainers --version 3.9.0dotnet add package Testcontainers.PostgreSql --version 3.9.0dotnet add package Testcontainers.MsSql --version 3.9.0


Beispiel einer Basisklasse für Integrationstests mit PostgreSQL:

using Testcontainers.PostgreSql;using Xunit;

public class DatabaseFixture : IAsyncLifetime{ private readonly PostgreSqlContainer _container = new PostgreSqlBuilder() .WithImage("postgres:15-alpine") .WithDatabase("testdb") .WithUsername("testuser") .WithPassword("testpass") .Build();

public string ConnectionString => _container.GetConnectionString();

public async Task InitializeAsync() { await _container.StartAsync(); }

public async Task DisposeAsync() { await _container.StopAsync(); }}


Jetzt kann jede Testklasse dieses Fixture verwenden, um eine isolierte Instanz der Datenbank zu erhalten.



2. Testisolierung und Zustandsverwaltung von Containern



Das fortgeschrittene Niveau erfordert eine feine Kontrolle über die Isolierung. Verwenden Sie anstelle eines Containers für die gesamte Testsuite einen Container pro Testklasse oder sogar pro Test. Dies verhindert einen „verschmutzten“ Zustand der Datenbank zwischen den Tests.



Implementierung über das Collection-Attribut in xUnit:

[CollectionDefinition("Database collection")]public class DatabaseCollection : ICollectionFixture<DatabaseFixture>{}

[Collection("Database collection")]public class UserRepositoryTests{ private readonly DatabaseFixture _fixture;

public UserRepositoryTests(DatabaseFixture fixture) { _fixture = fixture; }

[Fact] public async Task CreateUser_ShouldPersistUser() { // Arrange await using var context = new AppDbContext(_fixture.ConnectionString); await context.Database.EnsureCreatedAsync(); var repo = new UserRepository(context);

// Act var user = new User { Name = "Test User" }; await repo.CreateAsync(user);

// Assert var savedUser = await context.Users.FindAsync(user.Id); Assert.NotNull(savedUser); }}


Für vollständige Isolierung auf Testebene verwenden Sie ITestOutputHelper und erstellen Sie den Container innerhalb der DisposeAsync-Methode:

public class IsolatedTest : IAsyncLifetime{    private PostgreSqlContainer _container;

public async Task InitializeAsync() { _container = new PostgreSqlBuilder().Build(); await _container.StartAsync(); }

public async Task DisposeAsync() { await _container.DisposeAsync(); }}


3. Parallele Testausführung und Ressourcenverwaltung



Einer der Hauptvorteile von Docker ist die Möglichkeit, Tests parallel auszuführen. Mit Testcontainers können Sie mehrere Container gleichzeitig starten, was die Testausführungszeit erheblich verkürzt. Es ist jedoch wichtig, die Ressourcen Ihres Systems zu berücksichtigen. Konfigurieren Sie die Anzahl der parallelen Container basierend auf CPU und RAM. Verwenden Sie MaxParallelism in der Testkonfiguration, um die Anzahl der gleichzeitig ausgeführten Tests zu begrenzen.



Beispiel für die Konfiguration der parallelen Ausführung in xUnit:

// In der Datei xunit.runner.json{  \"parallelizeTestCollections\": true,  \"maxParallelThreads\": 4}


Dieser Ansatz ermöglicht es Ihnen, die Vorteile der Parallelisierung zu nutzen, ohne die Systemstabilität zu beeinträchtigen.



4. Integration in CI/CD-Pipelines



Für eine vollständige Automatisierung müssen Integrationstests mit Docker in CI/CD-Pipelines integriert werden. Die meisten modernen CI-Systeme (GitHub Actions, GitLab CI, Azure DevOps) unterstützen Docker nativ. Stellen Sie sicher, dass Docker in Ihrer CI-Umgebung installiert und konfiguriert ist. Verwenden Sie docker-compose oder Testcontainers, um Abhängigkeiten zu verwalten.



Beispiel einer GitHub Actions-Workflow-Datei:

name: .NET Integration Tests

on: [push]

jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:15-alpine env: POSTGRES_USER: testuser POSTGRES_PASSWORD: testpass POSTGRES_DB: testdb ports: - 5432:5432 steps: - uses: actions/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v3 with: dotnet-version: '8.0' - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build --no-restore - name: Run integration tests run: dotnet test --no-build --verbosity normal


Dieser Workflow startet einen PostgreSQL-Container, führt die Tests aus und stoppt den Container nach Abschluss. Für komplexere Szenarien mit mehreren Abhängigkeiten verwenden Sie docker-compose.



5. Fortgeschrittene Techniken: Netzwerke und benutzerdefinierte Container



In realen Projekten müssen Sie oft mit mehreren Containern interagieren, die über ein Netzwerk verbunden sind. Testcontainers unterstützt die Erstellung benutzerdefinierter Docker-Netzwerke, um Container zu isolieren oder zu verbinden. Sie können auch benutzerdefinierte Container-Images mit vorinstallierten Tools oder Konfigurationen erstellen.



Beispiel für die Erstellung eines benutzerdefinierten Netzwerks und das Hinzufügen von Containern:

var network = new NetworkBuilder()    .WithName(\"test-network\")    .Build();await network.CreateAsync();

var postgres = new PostgreSqlBuilder() .WithNetwork(network) .WithNetworkAliases(\"postgres-db\") .Build();

var redis = new RedisBuilder() .WithNetwork(network) .WithNetworkAliases(\"redis-cache\") .Build();

await Task.WhenAll(postgres.StartAsync(), redis.StartAsync());


Dieser Ansatz ermöglicht es Ihnen, komplexe Szenarien zu testen, bei denen Dienste über ein Netzwerk kommunizieren, ohne dass Sie die Infrastruktur manuell konfigurieren müssen.



Fazit



Integrationstests C# mit Docker auf fortgeschrittenem Niveau erfordern ein tiefes Verständnis der Isolierung, Parallelisierung und CI/CD-Integration. Die Verwendung von Testcontainers vereinfacht die Verwaltung von Containern erheblich und bietet flexible APIs für die Erstellung komplexer Testumgebungen. Denken Sie daran, dass die richtige Isolierung der Schlüssel zu zuverlässigen Tests ist, und die Parallelisierung die Ausführungszeit erheblich verkürzen kann. Integrieren Sie diese Techniken in Ihre CI/CD-Pipeline, um eine vollständige Automatisierung und hohe Codequalität zu gewährleisten.

Blogs

Buchempfehlungen