Unittests in C#

Unittests in C#
09/2026
Noel H. | Solution Engineer

TL;DR

Unittests prüfen einzelne Einheiten einer Anwendung isoliert und in Millisekunden – ohne Datenbank oder Infrastruktur. Der Beitrag zeigt, wie das AAA-Pattern (Arrange, Act, Assert) Tests lesbar und diagnostizierbar hält, und ordnet die Test Doubles ein: Dummy, Stub, Spy, Mock und Fake. Ein Fokus liegt auf typischen Anti-Patterns wie Über-Mocking, Brittle Tests, Assertion Roulette, Mystery Guest und Test-Logik im Testcode. Zudem wird eingeordnet, warum Code-Coverage ein hilfreicher Indikator, aber kein Qualitätsbeweis ist. Fazit: Gut geschriebene Unittests verbessern das Design, sichern Refactorings ab und decken Fehlimplementierungen frühzeitig auf.

Was ist ein Unittest?

Ein Unittest ist ein automatisierter Test, der eine einzelne, in sich abgeschlossene Einheit des Systems, wie z. B. eine Methode, isoliert von ihren Abhängigkeiten verifiziert. Er läuft in Millisekunden, benötigt keine Datenbank, keinen Netzwerkzugriff und keine laufende Infrastruktur.

Gut geschriebene Unittests liefern folgende Vorteile:

  • Geschützte Regression: Jede Änderung am Code wird sofort gegen die bestehenden Tests geprüft. Dadurch werden Regressionen schneller entdeckt.
  • Design-Feedback: Schwer testbarer Code ist meistens auch schlecht entworfen. Tests erzwingen klare Abhängigkeiten.
  • Dokumentation: Tests wie BorrowBookAsync_BookNotAvailable_ThrowsBookNotAvailableException beschreiben präzise, was in dem Szenario passieren soll.
  • Refactoring-Sicherheit: Mit grünen Tests lässt sich der Code sicher umstrukturieren, ohne ständige Angst vor unentdeckten Fehlern.

Das AAA-Pattern (Arrange, Act, Assert)

Jeder Unittest lässt sich in drei Phasen gliedern. Diese Trennung macht die Tests lesbarer und erleichtert die Diagnose bei einem Fehlschlag.

  1. Arrange: Testdaten aufbauen
  2. Act: die zu testende Methode aufrufen
  3. Assert: das Ergebnis validieren

Ein gutes Beispiel für AAA:

1[TestMethod] 
2public void Create_ValidInputs_SetsDueDate14DaysLater() 
3{ 
4    // Arrange 
5    var now = new DateTimeOffset(2026, 1, 15, 12, 0, 0, TimeSpan.Zero); 
6    var expectedDueDate = now.AddDays(14); 
7 
8    // Act 
9    var loan = Loan.Create(Guid.NewGuid(), Guid.NewGuid(), now); 
10 
11    // Assert 
12    loan.DueDate.ShouldBe(expectedDueDate); 
13}

Ein schlechtes Beispiel für AAA:

1[TestMethod] 
2public void TestLoan() 
3{ 
4    var loan = Loan.Create(Guid.NewGuid(), Guid.NewGuid(), 
5        DateTimeOffset.UtcNow);  // nicht deterministisch! 
6    Assert.IsNotNull(loan); 
7    Assert.AreEqual(loan.BorrowedAt.AddDays(14), loan.DueDate); 
8    Assert.IsNull(loan.ReturnedAt); 
9    // was wird hier eigentlich getestet? 
10}

Test Doubles

TypBeschreibungUse Case
DummyObjekt, das übergeben, aber nie genutzt wird.Parameter-Befüllung, wenn der Wert irrelevant ist.
StubGibt vordefinierte Antworten auf Anfragen zurück.Indirekte Inputs: Ein Repository gibt ein Objekt zurück.
SpyStub, der zusätzlich aufzeichnet, wie er aufgerufen wird.Verifizieren, dass eine Methode aufgerufen wurde.
MockErwartet bestimmte Aufrufe und schlägt fehl, wenn diese nicht eintreten.Verifizieren von Interaktionen.
FakeEchte (vereinfachte) Implementierung, die im Produktivsystem nicht verwendbar ist.In-Memory-Repository.

Anti-Patterns

Über-Mocking

Wenn jede Methode eines Stubs verifiziert wird, testet man nicht mehr das einzelne Verhalten, sondern die Implementierung des Systems.

Beispiel:

1// Zu viel: Implementation Detail 
2await _books.Received(1).GetByIdAsync(book.Id);  // interne Abfrage 
3await _books.Received(1).UpdateAsync(book);        // das ist relevant! 
4// Nur fachlich wichtige Interactions verifizieren.

Brittle Tests

Tests, die sich auf die interne Struktur (private Felder) beziehen, brechen beim Refactoring. Getestet werden sollte das Verhalten und nicht die Implementierung.

Assertion Roulette

Ein Test mit zwölf Asserts zeigt bei einem Fehlschlag nur, welcher Assert zuerst fehlschlägt. Daher sollte der Test in diesem Fall aufgeteilt werden.

Mystery Guest

Die Daten kommen aus einer externen Datei, einer globalen Variable oder einer Datenbank. Das macht es dem Leser des Tests schwerer, nachzuvollziehen, warum ein bestimmter Wert verwendet wird.

Test-Logik (if/for)

Kontrollfluss im Test kann dazu führen, dass der Test selbst Fehler enthält. Tests sollten einfach gehalten werden. Verschiedene Varianten lassen sich über DataRow abbilden.

Beispiel:

1// NICHT SO 
2
3[TestMethod] 
4
5public void TestTitles() 
6
7{ 
8
9    foreach (var title in new[] { null, "", "   " }) 
10
11    { 
12
13        // Test-Logik = potenzielle Bugs im Test selbst! 
14
15    } 
16
17} 

// SO 

[DataTestMethod] 

[DataRow(null)] 

[DataRow("")] 

[DataRow("   ")] 

public void Constructor_EmptyTitle_ThrowsArgumentException(string? title) { ... }

Code-Coverage – nützlich, aber kein Ziel

Code-Coverage gibt an, welcher Anteil des Programmcodes während der Tests ausgeführt wurde, sagt aber nichts über die Qualität der Tests aus.

Was Code-Coverage aussagt: welcher Code überhaupt nicht getestet wurde.

Was Code-Coverage nicht aussagt: ob die Tests sinnvoll sind, ob die richtigen Szenarien abgedeckt sind oder ob die Assertions korrekt sind. Ein Coverage-Bericht kann jedoch beim Erkennen von komplett ungetesteten Klassen oder Pfaden helfen.

Fazit

Unittests sind ein wichtiger Bestandteil der heutigen Softwareentwicklung. Durch sie lassen sich Fehlimplementierungen, die z. B. bestehendem Code schaden würden, schneller aufdecken.

Noel H. | Solution Engineer
Noel H. | Solution Engineer

Entwickler-Schulung

Diese Beiträge entstehen aus der täglichen Arbeit unserer Entwicklerinnen und Entwickler. Devware gibt dieses Wissen auf Anfrage auch als Schulung weiter, zu .NET, Blazor und anderen Sprachen.
Pfeil-Icon – Mehr erfahren

Mehr zum Thema

Pfeil nach rechts (Verlinkung)
Mirror-Shape-Pattern für Blazor EditForm
09/2026

Mirror-Shape-Pattern für Blazor EditForm

Blauer Pfeil nach rechts (Verlinkung)
Blazor mit TypeScript: Integration & Best Practices
08/2026

Blazor mit TypeScript: Integration & Best Practices

Blauer Pfeil nach rechts (Verlinkung)
Aspire: Schnellere Entwicklung, einfacherer Start
07/2026

Aspire: Schnellere Entwicklung, einfacherer Start

Blauer Pfeil nach rechts (Verlinkung)
Individual­software vs Standard­software: die Entscheidung
09/2026

Individual­software vs Standard­software: die Entscheidung

Blauer Pfeil nach rechts (Verlinkung)