Einheit 08: TDD und MOCKs

Ziele

  • Grundlegendes VerstĂ€ndnis fĂŒr TDD, Code Katas und Mocking-Frameworks

  • Praktische Anwendung aller drei Konzepte

Motivation

Die Nutzung von Test-Driven Development (TDD), Code Katas und Mocking-Frameworks kann die QualitĂ€t und Effizienz der Softwareentwicklung steigern. TDD zwingt einen dazu, zuerst Tests zu schreiben, bevor Code geschrieben wird, was dazu fĂŒhrt, dass es von Anfang an klare Anforderungen an den Code gibt.

Code Katas bieten die Möglichkeit, diese FĂ€higkeiten zu verbessern, indem wiederholt kleine Übungsprobleme gelöst werde.

Mocking-Frameworks ermöglichen es, isolierte Tests fĂŒr einzelne Komponenten zu schreiben, was die Fehlererkennung und -behebung erleichtert. Durch die Kombination dieser Praktiken wird die Entwicklung agiler und robuster.

TDD

All Code is guilty, until proven innocent.

Test Driven Development ist ein Technik, in dem Tests vor dem Code geschrieben werden um diese zu validieren.

Validierung: Tut der Code das richtige.

Voraussetzung: Kurze bzw. schnelle Zyklen bei der Entwicklung und dem Testen. HierfĂŒr macht es Sinn Unit Test Frameworks zu nutzen.

Genereller Ablauf

  1. Test schreiben - Der Test soll die gewĂŒnschte FunktionalitĂ€t abdecken.

  2. Test ausfĂŒhren. Sicherstellen, dass der neue Test fehlschlĂ€gt (=rot🔮)

  3. Code schreiben, so dass der Test ohne Fehler durchlaufen wird. Nicht mehr, nicht weniger.

  4. Test ausfĂŒhren. Ist der Test noch rot? ZurĂŒck zu 3. und Code so anpassen, so dass der Test ohne Fehler durchlaufen wird. Ist der Test durchgelaufen (=grĂŒn 🟱)? Dann zu 5. weiter.

  5. Code Refactoring.

  6. Weiter mit Schritt 1.

  1. Red – Test erstellen, der fehlschlĂ€gt

  2. Green – Test lĂ€uft ohne Fehler durch

  3. Refactor – Da jetzt alle Tests (einschl. des neuen Tests) grĂŒn sind, kann der Code refactored werden.

Übung

TDD fĂŒr Primzahlen

Eine Zahl gilt dann als Primzahl, wenn sie folgende Bedingungen erfĂŒllt:

  • Sie ist natĂŒrlich, also grĂ¶ĂŸer als 1 und ganz (ohne Nachkommastellen) Sie lĂ€sst sich ganzzahlig (d.h. ohne Rest) nur durch 1 und sich selbst teilen.

Aufgabe

Das Tutorial soweit durcharbeiten, bis die gezeigte Ausgabe zu sehen ist.

Voraussetzungen

  • Erforderlich:

    • Visual Studio Code

    • C# (Extension fĂŒr VS Code)

  • Empfohlen

    • .NET Extension Pack (Extension fĂŒr VS Code)

    • .C# Dev Kit (Extension fĂŒr VS Code)

    • .NET Install Tool (Extension fĂŒr VS Code)

.NET Solution erstellen:

dotnet new sln -o seks-tdd  

In den Ordner wechseln und eine Klassenbibliothek erstellen:

dotnet new classlib -o PrimeService

Class1 in PrimeService umbenennen

namespace Prime.Services;
public class PrimeService
{
}

Im seks-tdd Ordner die Klassenbibliothek hinzufĂŒgen

dotnet sln add ./PrimeService/PrimeService.csproj

Das Testprojekt erstellen

dotnet new xunit -o PrimeService.Tests

Das Testprojekt zur Solution hinzufĂŒgen:

dotnet sln add ./PrimeService.Tests/PrimeService.Tests.csproj

Zum Abschluss noch die PrimeService Klassenbibliothek dem Testprojekt als Referenz hinzufĂŒgen:

dotnet add ./PrimeService.Tests/PrimeService.Tests.csproj reference ./PrimeService/PrimeService.csproj  

Den ersten Test schreiben

namespace Prime.Services.Tests;
public class PrimeServiceTest
{
    [Fact]
    public void TestOneIsPrime()
    {
        var primeService = new PrimeService();
        bool result = primeService.IsPrime(1);
        Assert.False(result, "1 should not be prime");
    }
}

Auf Konsole die Test starten:

dotnet test

Alle Probleme beheben und Code Implementieren.

Die IsPrime Klasse sollte dann ungefÀhr so aussehen: #

namespace Prime.Services;
public class PrimeService
{
  public bool IsPrime(int candidate)
  {
    return false;
  }
}

Nun nochmal den Test starten (s.o.):

Die TestausfĂŒhrung wird gestartet, bitte warten...
Insgesamt 1 Testdateien stimmten mit dem angegebenen Muster ĂŒberein.

Bestanden!   : Fehler:     0, erfolgreich:     1, ĂŒbersprungen:     0, gesamt:     1, Dauer: < 1 ms - PrimeService.Tests.dll (net7.0)

Code Katas

Code Katas sind vorgegebene Programmieraufgaben. Die Idee wird erstmal 1999 in dem Buch "The Pragmatic Programmer" von David Thomas und Andrew Hunt erwÀhnt.

Dabei geht es nicht um die Lösung der Aufgabenstellung, sondern den Bearbeitungsweg. Im Vordergrund steht somit das Erlernen neuer Fertigkeiten:

  • Umgang mit der IDE

  • Nutzung der Kommandozeilentools

  • Erlenen von Shortcuts

  • Anwenden von Prinzipien im Software-Design

  • Anwenden von Refactoring-Methoden

  • ...

In der Regel werden Code Katas mittels TDD durchgefĂŒhrt.

Je öfters Code Katas wiederholt werden, desto schneller (und leichter) kann man die Problemstellung lösen.

Da man sich bei den Wiederholungen weniger auf die Problem- bzw. Aufgabenstellung konzentrieren muss, werden die AblÀufe um und wÀhrend des Programmierens verbessert.

Code Kata Beispiel: FizzBuzz

Aufgabenstellung:

  1. Schreibe eine Methode fizzbuzz, die eine Nummer als Eingabe akzeptiert und als String zurĂŒck liefert.

  2. FĂŒr alle Vielfachen von Drei liefere Fizz anstelle der Nummer.

  3. FĂŒr alle Vielfachen von FĂŒnf liefere Buzz anstelle der Nummer.

  4. FĂŒr alle Zahlen, die eine Vielfaches von drei als auch fĂŒnf sind liefere FizzBuzz zurĂŒck.

Regeln:

  • Es wird mit der minimalen Lösung gestartet, die fehlschlĂ€gt!

  • In jeder Interration nur so viel Code schreiben, dass der nĂ€chste Test grĂŒn wird.

  • Sobald der Test grĂŒn ist, wird der Code refactored.

  • ÜberprĂŒfungen (Assertions) im Test werden exakt entsprechend der Anforderungen formuliert.

Links:

Übung

Code Kata Fizzbuzz entweder mit .NET oder mit JavaScript durcharbeiten

Variante A: .NET und C#

Voraussetzungen wie ein erster Aufgabe.

Variante B: JavaScript

Erforderlich:

Empfohlen:

Mocking Frameworks

Mocking-Frameworks erlauben es sog. Mock-Objekte zu erstellen, die das Verhalten aufgerufener Objekte simulieren. Das kann sinnvoll sein, wenn z.B. ein Web Service , eine Datenbank oder allgemein Objekte aufgerufen werden, die im eigentlichen Test zwar benötigt werden, aber deren Verhalten in diesem Test nicht geprĂŒft werden soll.

Das Mock-Objekt liefert dabei keine Echtdaten zurĂŒck, sondern vorher zum Testfall passend festgelegte Werte. Somit lĂ€sst sich leicht ein deterministisches Verhalten herbeifĂŒhren, da sich die aufgerufenen Objekte immer gleich verhalten.

Wie sich die Mock-Objekte verhalten sollen, wird am Anfang des jeweiligen Tests festgelegt.

Vor dem eigentlichen Test werden alle Objekte, die nicht benötigt werden ausgetauscht und durch die Mock-Objekte ersetzt (dahinter steht das Prinzip der Polymorphie in der OO-Entwicklung).

Übung

NumberGenerator mit moq

  1. Einen Generator erstellen, der bei Eingabe einer ganzen Zahl die nÀchste Primzahl liefert.

  2. Der Generator muss den zuvor entwickelten PrimeService nutzen.

  3. Trotz des noch nicht ausgereiften Zustands des PrimeServices, muss die Funktionsweise des Nummerngenerators sichergestellt werden.

In den Order mit dem ersten Testprojekt (1. Übung) wechseln:

dotnet add package Moq

Im Testprojekt eine neue Datei NumberGeneratorTest.cs anlegen:

namespace Prime.Services.Tests;
using Moq;

public class NumberGeneratorTest
{
}

Den Test erstellen:

[Fact]
public void GenerateNextPrimeTest()
{
  var numberGenerator = new NumberGenerator();
  int result = numberGenerator.GenerateNextPrime(9);
  Assert.Equal(11, result);
}

Im PrimeService Projekt eine neue Datei NumberGenerator.cs anlegen

public class NumberGenerator
{
  IPrimeService _primeService = new PrimeService();
  public NumberGenerator(IPrimeService primeService)
  {
    _primeService = primeService;
  }

  public int GenerateNextPrime(int candidate)
  {
    while (!_primeService.IsPrime(++candidate))
    {
      //loop until we find a prime number
    };
    return candidate;
  }
}

Nun wird im Test Projekt ein Fehler erscheinen:

There is no argument given that corresponds to the required parameter 'primeService' of 'NumberGenerator.NumberGenerator(IPrimeService)

Den Test entsprechend abÀndern

[Fact]
public void GenerateNextPrimeTest()
{
  var primeServiceMock = new Mock<IPrimeService>();
  primeServiceMock.Setup(x => x.IsPrime(10)).Returns(false);
  primeServiceMock.Setup(x => x.IsPrime(11)).Returns(true);
  
  var numberGenerator = new numberGenerator(primeServiceMock.Object);
  int result = numberGenerator.GenerateNextPrime(9);
  Assert.Equal(11, result);
}

Nun nochmal den Test starten (s.o.):

Die TestausfĂŒhrung wird gestartet, bitte warten...
Insgesamt 1 Testdateien stimmten mit dem angegebenen Muster ĂŒberein.

Bestanden!   : Fehler:     0, erfolgreich:     2, ĂŒbersprungen:     0, gesamt:     2, Dauer: 51 ms - PrimeService.Tests.dll (net7.0)

Links

Voraussetzungen

  • analog zu Übung 1 und Übung 2

  • moq als Paket

Last updated