Einleitung: Warum Testen kein Luxus, sondern eine Notwendigkeit ist
In der modernen C# (.NET)-Entwicklung bestimmt die Codequalität nicht nur die Geschwindigkeit der Produktveröffentlichung, sondern auch dessen Ruf. Fehler in der Produktion, plötzliche Abstürze nach einem Abhängigkeitsupdate oder fehlerhafte Geschäftslogik – das sind Szenarien, die durch ein gut durchdachtes Testen vermieden werden können.
Viele Entwickler, insbesondere Anfänger, betrachten Tests als "zusätzliche Arbeit". In der Praxis spart das Testen jedoch Stunden beim Debuggen und Refactoring. In diesem Artikel betrachten wir drei wichtige Testebenen im C#-Ökosystem: Unit-Tests (Modultests), Integrationstests und E2E-Tests (End-to-End). Sie werden sehen, wie jede Ebene ihre eigenen Aufgaben löst, welche Werkzeuge verwendet werden und wie man saubere, wartbare Tests schreibt.
1. Unit-Testing: Das Fundament der Qualität
Unit-Tests überprüfen isolierte Codeblöcke (normalerweise eine Methode oder Klasse) getrennt von externen Abhängigkeiten. In C# ist dies die schnellste und stabilste Testebene. Das Hauptziel ist sicherzustellen, dass jede Funktion bei verschiedenen Eingabedaten korrekt funktioniert.
Werkzeuge für Unit-Tests in C#
Die beliebtesten Frameworks für .NET sind xUnit (von Microsoft empfohlen) und NUnit. Zum Ersetzen von Abhängigkeiten wird die Bibliothek Moq oder das in .NET integrierte FakeItEasy verwendet.
Beispiel eines Unit-Tests (xUnit + Moq)
Angenommen, wir haben einen Dienst zur Rabattberechnung, der von einem Benutzerrepository abhängt:
public class DiscountService{ private readonly IUserRepository _userRepo;
public DiscountService(IUserRepository userRepo) { _userRepo = userRepo; }
public decimal CalculateDiscount(int userId, decimal orderTotal) { var user = _userRepo.GetById(userId); if (user.IsVip) return orderTotal * 0.2m; // 20% Rabatt return orderTotal * 0.05m; // 5% Rabatt }}Test für diese Methode:
public class DiscountServiceTests{ [Fact] public void CalculateDiscount_WhenUserIsVip_Returns20Percent() { // Arrange var mockRepo = new Mock<IUserRepository>(); mockRepo.Setup(repo => repo.GetById(1)) .Returns(new User { Id = 1, IsVip = true }); var service = new DiscountService(mockRepo.Object);
// Act var result = service.CalculateDiscount(1, 100m);
// Assert Assert.Equal(20m, result); }}Wichtig: In Unit-Tests greifen wir nicht auf eine echte Datenbank zu. Wir verwenden einen Mock, um die Logik von DiscountService zu isolieren.
2. Integrationstests: Überprüfung des Zusammenspiels von Komponenten
Integrationstests überprüfen die Interaktion mehrerer echter Komponenten: Datenbank, API, Dateisystem. In .NET werden hierfür häufig WebApplicationFactory (für ASP.NET Core) und Testcontainer (Testcontainers) verwendet.
Besonderheiten von Integrationstests
- Langsamer als Unit-Tests – erfordern das Hochfahren der Infrastruktur (DB, HTTP-Server).
- Aufwändiger in der Wartung – der Zustand der Testumgebung muss verwaltet werden.
- Wert – decken Probleme in der Konfiguration, beim Daten-Mapping und bei der Arbeit mit echten Datenbanken auf.
Beispiel eines Integrationstests (ASP.NET Core + xUnit)
Angenommen, wir haben einen Controller für die Arbeit mit Bestellungen. Der Test prüft, ob die Anfrage an die echte API korrektes JSON zurückgibt:
public class OrdersApiTests : IClassFixture<WebApplicationFactory<Program>>{ private readonly HttpClient _client;
public OrdersApiTests(WebApplicationFactory<Program> factory) { _client = f