Die Clean Code Developer Initiative benennt neben Prinzipien auch diverse Praktiken, die ein Team anwenden sollte. Dazu gehören
- Integrationstests (oranger Grad)
- Unit Tests (gelber Grad)
- Coverage Analyse (gelber Grad)
- Continuous Integration (grüner Grad)
Diese vier Praktiken leisten gemeinsam einen sehr wichtigen Beitrag zum Wert der Korrektheit. Die Praktiken Integrations- und Unit Tests sorgen für automatisierte Tests. Die Coverage Analyse stellt sicher, dass die Testabdeckung ausreichend hoch ist. Schlussendlich sorgt der Continuous Integration Prozess dafür, dass Tests und Coverage Analyse vollautomatisiert über den gesamten Code des Projekts ausgeführt werden. Im folgenden wird anhand eines kleinen Beispielprojekts in C# gezeigt, wie Coverage Analyse und Continuous Integration ganz konkret eingerichtet werden können. Dabei kommen Stryker Mutator und GitLab als Tools zum Einsatz.
Mutation Testing zur Coverage Analyse
Über Stryker Mutator als Tool zur Coverage Analyse habe ich bereits in der dotnetpro einen Artikel veröffentlicht (siehe https://www.dotnetpro.de/tools/testing/mutanten-kommen-killt-2402511.html). Die Idee dahinter ist simpel:
- Ändere den Code an einer Stelle (bspw. „>“ zu „>=“ oder „+“ zu „-„)
- Lasse alle Tests laufen
- Wenn kein Test auf rot geht, ist eine Lücke in der Testabdeckung gefunden
Stryker Mutator steht für JavaScript, C# und Scala zur Verfügung. Für andere Sprachen lassen sich ebenfalls passende Tools finden.
Continuous Integration
Den vierten Baustein, Continuous Integration, habe ich exemplarisch mit GitLab umgesetzt. Ich habe dort ein Beispielprojekt in C# abgelegt und anschließend den CI Prozess konfiguriert. Im Prinzip ist der Vorgang simpel: Man legt im Wurzelverzeichnis des GitLab Projektes die Datei .gitlab-ci.yml an. Diese ist verantwortlich für die Konfiguration der CI Pipeline. Im Detail wird es dann allerdings an manchen Stellen etwas zeitaufwendig, alle Details zusammenzusammeln und so lange anzupassen, bis alles wie gewünscht läuft. Wichtig dabei ist, dass man die Befehle immer zuerst lokal auf dem eigenen Rechner ausführt. Solange der Befehl lokal nicht funktioniert, sollte man ihn nicht im CI eintragen. Dort wartet man länger auf das Resultat, so dass man lokal schneller vorankommt.
Der CI Prozess läuft in GitLab in Docker Containern. Man kann entweder die GitLab eigene Infrastruktur nutzen und erhält im kostenfreien Plan bereits 400 Minuten CI/CD Zeit zur Verfügung gestellt. Mit sehr wenig Aufwand lässt sich ein Runner zur Ausführung der CI Pipeline auch auf einer eigenen Maschine installieren. Natürlich kann dazu ein Cloud Server verwendet werden. Ich habe es mit einem IONOS Cloud Server in Minuten zum Laufen gebracht. Eine Anleitung für Ubuntu findet sich bspw. hier: https://lindevs.com/install-gitlab-runner-on-ubuntu/
Clean Code Trainings
Geschlossene Firmenkurse
Wir führen alle Seminare als geschlossene Firmenkurse für Sie durch.
Bei Interesse oder Fragen kontaktieren Sie uns gerne.
dotnet-stryker ausführen
Mein Beispielprojekt soll in der Testphase den Befehl dotnet-stryker ausführen. Da das Tool nicht zum Standardumfang des Docker Image dotnet/sdk gehört, muss es zunächst installiert werden. Im Anschluss muss noch der Pfad exportiert werden, damit das Programm gefunden wird. Das folgende Listing zeigt die vollständige CI Konfiguration des Beispiels. Nach Veröffentlichung dieses Beitrags werde ich sicherlich daran weiter basteln.
image: mcr.microsoft.com/dotnet/sdk:latest
stages:
- build
- test
build:
stage: build
script:
- dotnet --version
- dotnet build
test:
stage: test
script:
- dotnet tool install -g dotnet-stryker
- export PATH="$PATH:/root/.dotnet/tools"
- cd mutation.tests
- dotnet-stryker --break-at 95
artifacts:
paths: [mutation.tests/StrykerOutput]
untracked: false
expire_in: 2 days
Das gesamte Projekt steht bei GitLab in einem öffentlichen Repository bereit:
https://gitlab.com/slieser/mutation-testing-ci
Zeile 1 definiert, welches Docker Image für die Stages verwendet werden soll. In diesem Fall handelt es sich um das offizielle .NET Core 6.0 SDK Image (mit „latest“ wird aktuell 6.0.3 gezogen). Das SDK ist erforderlich, damit Compiler und Buildsystem zur Verfügung stehen.
Die folgenden Zeilen 2-4 definieren die beiden Stages „build“ und „test“. Ferner wird damit auch die Reihenfolge der Ausführung festgelegt. Es folgen dann die Definitionen der Stages.
In der Stage „build“ wird erst angezeigt, welche .NET Version zum Einsatz kommt (Zeile 9). Dies könnte später bei der Fehlersuche hilfreich sein. Anschließend wird der Befehl „dotnet build“ ausgeführt, um das Projekt zu übersetzen. Letztlich wird so lediglich sichergestellt, dass die Tests erst ausgeführt werden, wenn das Projekt gebaut werden kann. In realen CI Pipelines würden hier Artefakte erstellt und abgelegt, bspw. NuGet Pakete oder Docker Images.
Die „test“ Stage ab Zeile 12 installiert zunächst dotnet-stryker als Tool. Damit der Befehl später gefunden wird, wird der Pfad mit export angehängt (Zeile16). Danach wechselt der Befehl „cd“ in das Verzeichnis, in dem das Testprojekt liegt. Dort wird dann schlussendlich dotnet-stryker ausgeführt. Der Parameter „–break-at 95“ erzwingt, dass der Buildschritt fehlschlägt, wenn weniger als 95% der Mutationen überleben.
Zuletzt wird der Report, den dotnet-stryker erstellt, als Artefakt abgelegt, so dass er bei Bedarf heruntergeladen werden kann. Dieser Schritt kann sicher noch eleganter konfiguriert werden, damit der Report, eine HTML Datei, direkt in GitLab eingesehen werden kann.
Fazit
Die Kombination aus automatisierten Tests, Coverage Analyse und CI ist ein mächtiges Werkzeug, um die Korrektheit sicherzustellen. Es fehlt aktuell noch eine Konfiguration, um auch die TestContainers im GitLab CI Prozess einzubinden. Wir bleiben dran…