Unit Testing
Unit Testing (Modultest) ist das Testen einzelner, isolierter Teile (Units) eines Programms.
Jeder Test prüft eine kleine Funktionseinheit (Methode/Funktion) unabhängig vom Rest des Programms.
Unit Test Konzept:
Programm Tests
┌─────────────┐ ┌──────────────┐
│ Funktion A │───────────────>│ Test für A │
├─────────────┤ ├──────────────┤
│ Funktion B │───────────────>│ Test für B │
├─────────────┤ ├──────────────┤
│ Funktion C │───────────────>│ Test für C │
└─────────────┘ └──────────────┘
Jede Funktion Eigener Test!
wird isoliert getestet
Test-Ablauf (AAA-Pattern):
┌──────────────────┐
│ 1. ARRANGE │ ← Vorbereitung
│ Setup/Init │ (Testdaten, Objekte)
└────────┬─────────┘
│
┌────────▼─────────┐
│ 2. ACT │ ← Ausführung
│ Funktion │ (Code unter Test)
│ aufrufen │
└────────┬─────────┘
│
┌────────▼─────────┐
│ 3. ASSERT │ ← Überprüfung
│ Ergebnis │ (Stimmt Ergebnis?)
│ prüfen │
└──────────────────┘
Beispiel-Ablauf:
Code unter Test:
┌────────────────────────┐
│ public int add(a, b) { │
│ return a + b; │
│ } │
└────────────────────────┘
│
│ wird getestet von
▼
Unit Test:
┌────────────────────────────────┐
│ @Test │
│ testAdd() { │
│ // Arrange │
│ Calculator calc = new ... │
│ │
│ // Act │
│ int result = calc.add(2, 3);│
│ │
│ // Assert │
│ assertEquals(5, result); │
│ } │
└────────────────────────────────┘
Test-Ergebnis Visualisierung:
✓ Erfolgreicher Test:
┌──────────────────────────┐
│ Input: add(2, 3) │
│ Erwartet: 5 │
│ Tatsächlich: 5 │
│ → ✓ TEST PASSED │
└──────────────────────────┘
✗ Fehlgeschlagener Test:
┌──────────────────────────┐
│ Input: add(2, 3) │
│ Erwartet: 5 │
│ Tatsächlich: 6 │
│ → ✗ TEST FAILED │
└──────────────────────────┘
Arten der Durchführung von Unittests
Parallel während der Entwicklung (TDD)
Test-Driven Development:
1. Test schreiben (schlägt fehl)
2. Code schreiben (Test wird grün)
3. Code verbessern (Refactoring)
4. Wiederholen
┌─────────┐
│ RED │ ← Test schreiben (schlägt fehl)
└────┬────┘
│
┌────▼────┐
│ GREEN │ ← Code schreiben (Test läuft)
└────┬────┘
│
┌────▼────┐
│REFACTOR │ ← Code verbessern
└────┬────┘
│
└────> Wiederholen
Vorteile: Fehler werden sofort erkannt
Nach Abschluss des Code-Schreibens
Tests werden nach der Implementierung geschrieben.
1. Code schreiben
2. Code fertigstellen
3. Tests schreiben
4. Tests ausführen
Nachteil: Fehler werden später entdeckt
Beispiele in verschiedenen Sprachen
Java (JUnit)
import org.junit.Test;
import static org.junit.Assert.*;
public class CalculatorTest {
@Test
public void testAdd() {
// Arrange
Calculator calc = new Calculator();
// Act
int result = calc.add(2, 3);
// Assert
assertEquals(5, result);
}
@Test
public void testDivideByZero() {
Calculator calc = new Calculator();
// Assert Exception
assertThrows(ArithmeticException.class, () -> {
calc.divide(10, 0);
});
}
}
C# (NUnit/xUnit)
using NUnit.Framework;
[TestFixture]
public class CalculatorTests
{
[Test]
public void Add_TwoNumbers_ReturnsSum()
{
// Arrange
var calc = new Calculator();
// Act
var result = calc.Add(2, 3);
// Assert
Assert.AreEqual(5, result);
}
[Test]
public void Divide_ByZero_ThrowsException()
{
var calc = new Calculator();
// Assert
Assert.Throws<DivideByZeroException>(() =>
calc.Divide(10, 0)
);
}
}
Python (unittest/pytest)
import unittest
class TestCalculator(unittest.TestCase):
def test_add(self):
# Arrange
calc = Calculator()
# Act
result = calc.add(2, 3)
# Assert
self.assertEqual(result, 5)
def test_divide_by_zero(self):
calc = Calculator()
# Assert Exception
with self.assertRaises(ZeroDivisionError):
calc.divide(10, 0)
Assertions (Überprüfungen)
Häufigste Assertions:
┌────────────────────┬─────────────────────────┐
│ Assertion │ Prüft │
├────────────────────┼─────────────────────────┤
│ assertEquals(a,b) │ a == b │
│ assertNotEquals │ a != b │
│ assertTrue(x) │ x ist true │
│ assertFalse(x) │ x ist false │
│ assertNull(x) │ x ist null │
│ assertNotNull(x) │ x ist nicht null │
│ assertThrows(...) │ Exception wird geworfen │
└────────────────────┴─────────────────────────┘
Prüfungsrelevant
Was ist Unit Testing?
- Testen einzelner Funktionen/Methoden
- Isoliert vom Rest des Programms
- Automatisiert ausführbar
Warum Unit Tests?
- Fehler früh erkennen
- Refactoring sicherer
- Dokumentiert, wie Code funktioniert
- Verhindert Regressionen (alte Fehler kommen zurück)
AAA-Pattern:
- Arrange: Vorbereitung (Setup)
- Act: Ausführung (Code aufrufen)
- Assert: Überprüfung (Ergebnis prüfen)
Eigenschaften guter Unit Tests:
- Fast: Schnell ausführbar
- Independent: Unabhängig voneinander
- Repeatable: Immer gleiches Ergebnis
- Self-Validating: Klares Pass/Fail
- Timely: Zeitnah geschrieben
Best Practices
- Ein Test = Eine Sache
// ✓ RICHTIG
testAdd()
testSubtract()
// ✗ FALSCH
testAllOperations()
- Aussagekräftige Namen
// ✓ RICHTIG
testAdd_TwoPositiveNumbers_ReturnsSum()
// ✗ FALSCH
test1()
- Keine Logik in Tests
// ✗ FALSCH
for (int i = 0; i < 10; i++) {
assertEquals(i, calc.add(i, 0));
}
// ✓ RICHTIG
assertEquals(5, calc.add(5, 0));
- Tests unabhängig machen
- Keine Shared State
- Jeder Test kann alleine laufen
- Test Coverage
- Siehe Code Coverage
- Mindestens 70-80%
Häufige Fehler
- Tests zu groß
- Testen zu viel auf einmal
- Externe Abhängigkeiten
- Datenbank, Netzwerk in Tests
- → Stattdessen: Mocks verwenden
- Tests ignorieren
@Ignore // ✗ NICHT gut!
@Test
public void testSomething() { ... }
- Keine Assertions
@Test
public void testAdd() {
calc.add(2, 3); // ✗ Nichts wird geprüft!
}
Siehe auch
- Code Coverage - Wie viel Code ist getestet?
- Try-Catch - Exception-Handling testen
- Exception - Fehler in Tests