Testen in C#: Von Unit bis E2E – Wie man eine qualitativ hochwertige Testarchitektur in .NET aufbaut
Die Softwarequalität hängt direkt vom Testsystem ab. Im Ökosystem von C# und .NET gibt es eine etablierte Testpyramide: Modultests (Unit), Integrationstests (Integration) und End-to-End-Tests (E2E). In diesem Artikel werden wir jede Ebene anhand praktischer Beispiele durchgehen, zeigen, welche Bibliotheken verwendet werden, und fertige Codevorlagen bereitstellen.
1. Unit-Testing: Die Grundlage für Code-Stabilität
Unit-Tests überprüfen die Funktionsweise einzelner Methoden, Klassen oder Komponenten isoliert von externen Abhängigkeiten (DB, API, Dateisystem). Das Hauptziel ist es, Regressionen bei Refactoring schnell zu finden.
Werkzeuge für C#
- NUnit – Klassisches Framework mit Attributen wie
[Test],[TestCase]. - xUnit – Modernes Framework von den Erstellern von NUnit, bevorzugt in neuen Projekten.
- Moq – Bibliothek zum Erstellen von Mock-Objekten.
- FluentAssertions – Für lesbare Asserts.
Beispiel eines Unit-Tests mit xUnit und Moq
Angenommen, wir haben einen Rabattberechnungsdienst, der auf ein Benutzer-Repository zugreift:
public interface IUserRepository{ bool IsPremiumUser(int userId);}
public class DiscountService{ private readonly IUserRepository _userRepo; public DiscountService(IUserRepository userRepo) => _userRepo = userRepo;
public decimal CalculateDiscount(int userId, decimal amount) { if (_userRepo.IsPremiumUser(userId)) return amount * 0.9m; // 10% Rabatt return amount; }}
// Testpublic class DiscountServiceTests{ [Fact] public void CalculateDiscount_ForPremiumUser_Returns90Percent() { // Arrange var mockRepo = new Mock<IUserRepository>(); mockRepo.Setup(r => r.IsPremiumUser(It.IsAny<int>())).Returns(true); var service = new DiscountService(mockRepo.Object);
// Act var result = service.CalculateDiscount(1, 100m);
// Assert Assert.Equal(90m, result); }}Tipp: Verwenden Sie [Theory] und [InlineData] in xUnit für parametrisierte Tests – das reduziert Code-Duplizierung.
2. Integrationstests: Überprüfung der Komponentenverknüpfung
Integrationstests prüfen, wie Ihre Module mit echten externen Systemen interagieren: Datenbanken, Warteschlangen, Webservices. Im Gegensatz zu Unit-Tests werden hier keine Mocks verwendet – wir starten echte Umgebungen.
Bibliotheken und Ansätze
- TestContainers – Startet Docker-Container (PostgreSQL, SQL Server, Redis) direkt aus den Tests.
- WebApplicationFactory (aus Microsoft.AspNetCore.Mvc.Testing) – Erstellt einen Test-Host für ASP.NET Core.
- Respawn – Bereinigt die Datenbank zwischen Tests, ohne den Container neu zu erstellen.
Beispiel eines Integrationstests mit TestContainers und WebApplicationFactory
Wir überprüfen, ob unsere API einen Benutzer korrekt in PostgreSQL speichert:
public class UserApiIntegrationTests : IClassFixture<TestContainersFixture>{ private readonly HttpClient _client; private readonly TestContainersFixture _fixture;
public UserApiIntegrationTests(TestContainersFixture fixture) { _fixture = fixture; var factory = new WebApplicationFactory<Program>() .WithWebHostBuilder(builder => { builder.ConfigureTestServices(services => { // Ersetzen der echten Verbindungszeichenfolge durch den Container services.RemoveAll<DbContextOptions<AppDbContext>>(); se
```