Code Coverage Testing mit .NET – dein C# Code unter der Lupe

Jan-Hendrik Precht

Jan-Hendrik Precht begeistert sich für die Themen "lebenslanges Lernen" und "systemische Unterstützung in Personalentwicklung und Vertrieb". Zum Einen, weil Ihn sowohl die Vielfalt an möglichen Themen als auch innovative Steuerungsmöglichkeiten faszinieren. Zum Anderen, weil letztendlich alle Beteiligten etwas davon haben.
code-coverage-title

Testing lautet der zweite Vorname jedes ernsthaften Softwareentwicklers. Aber es geht noch ein bisschen mehr als das Standard-Testing mit dem xUnit Framework. Durch zusätzliche Analyse der Code Coverage beförderst du die Qualität deiner Unit-Tests auf ein höheres Niveau. Der Aufwand ist gering, denn .NET hat die benötigten Tools bereits mit an Bord.

Vorteile

Du bekommst ein besseres Gespür für dein Coding, denn:

  • Du findest nicht getesteten Code.
  • Du findest nicht erreichbaren (toten) Code.
  • Du identifizierst fehlende (nicht implementierte) Funktionalität leichter.
  • Du misst die Komplexität deines Codes.

Testprojekt erstellen

Der folgende Code Block zeigt, wie du ein xUnit Testprojekt erstellst und den passenden Testdaten Collector zu deinem Projekt hinzufügst. Außerdem musst du einmalig einen globalen Reportgenerator installieren.

# create test project
dotnet new xunit -n Traperto.XUnit.Tests
# add test data collector
cd Traperto.XUnit.Tests
dotnet add package coverlet.collector
# install global reportgenerator
dotnet tool install -g dotnet-reportgenerator-globaltool

Tests implementieren

Du implementierst deine Tests nach der bekannten xUnit Methodik. Wenn du dazu erste Starthilfe brauchst, dann findest du viele Beispiele auf GitHub im xUnit Repository abgelegt. Für den ersten beispielhaften Test schreibst du dir eine zu testende Und-Logik und speicherst diese in einem separaten .NET Class-Library Projekt.

namespace Traperto.Units.Logical;

public static class LogicalGate
{
    public static bool And(bool a, bool b)
    {
        if (a != true) return false;
        return b == true;
    }
}

Anschließend implementierst du einen ersten Testfall im zuvor erstellten Testprojekt. Für den vollständigen Test der Und-Logik kannst du noch drei weitere Testfälle nach der gezeigten Methode schreiben.

using Traperto.Units.Logical;
using Xunit;
 
namespace Traperto.XUnit.Tests.TestCases;

public class LogicalGateTest
{
    [Fact]
    public void Should_BeFalse()
    {
        Assert.False(LogicalGate.And(/*a*/ false, /*b*/ true));
    }
}

Tests ausführen

Wie du deine Tests per Skript ausführen kannst, dass zeigt dir der folgende Code Block. Du erstellst dafür ein Shell-Skript im Projektverzeichnis direkt neben der *.csproj Projektdatei.

#!/bin/sh
dotnet test --collect:"XPlat Code Coverage"

Du startest das Skript dann über deine Shell. Denk vorab daran dein Skript ausführbar zu machen.

./test_code_coverage.sh

Nach der Testausführung könnte dein Projektverzeichnis dann so aussehen.

.
├── TestCases
├── TestResults
│   └── 390ff9f7-6d5f-4d38-ab6b-84e74b5a3d76
│       └── coverage.cobertura.xml
├── Traperto.XUnit.Tests.csproj
└── test_code_coverage.sh

Testreport generieren

Wie du dann einen Report aus den gesammelten Testdaten generierst, das kannst du dir im folgenden Code Block anschauen. Am besten schreibst du dir auch hier wieder ein kurzes Shell-Skript.

#!/bin/sh
GUID="$1"
EXPORT_DIR="./TestResults/${GUID}"

reportgenerator "-reports:${EXPORT_DIR}/coverage.cobertura.xml" "-targetdir:${EXPORT_DIR}/coveragereport" -reporttypes:Html

Die Reportgenerierung (inkl. Angabe der Guid zu den Testdaten) startest du dann wie folgt.

./test_report.sh 390ff9f7-6d5f-4d38-ab6b-84e74b5a3d76

Nach der Testausführung und Reportgenerierung könnte dein Projektverzeichnis dann so aussehen.

.
├── TestCases
├── TestResults
│   └── 390ff9f7-6d5f-4d38-ab6b-84e74b5a3d76
│       ├── coverage.cobertura.xml
│       └── coveragereport
├── Traperto.XUnit.Tests.csproj
├── test_code_coverage.sh
└── test_report.sh

Im Verzeichnis coveragereport findest du nun einen HTML-Report, den du über deinen Browser öffnen kannst.

Tests analysieren

Der Testreport liefert dir Informationen zu Kennzahlen der Abdeckung und Komplexität (McCabe-Metrik) deines getesteten Codes. Über Gruppierungen und Filter kannst du deinen Code von der Ebene der Namespaces bis runter zur Ebene der Methoden analysieren. Hier bekommst du einen ersten Einblick in den Testreport zum besprochenen Beispiel.

Mit dem gezeigten Testfall erzielst du eine Zeilenabdeckung (Line coverage) von 75% und eine Zweigabdeckung (Branch coverage) von 50%. Alle grünen Zeilen und Zweige sind durch dein Testing komplett abgedeckt. Die gelben und roten Zeilen bzw. Zweige sind hingegen nur teilweise oder gar nicht getestet worden, hier kannst du noch nachbessern. Dein Code erreicht nach McCabe eine geringe zyklomatische Komplexität von 2 und ist unbedenklich.

Wie bereits oben erwähnt könntest du durch weitere gezielte Testfälle, eine Abdeckung von jeweils 100% Zeilen- und Zweigabdeckung erreichen. Probier das doch einfach mal aus!

Fazit

Schon dieses simple Beispiel zeigt dir hoffentlich, welche zusätzlichen Analysemöglichkeiten Code Coverage Testing dir bieten kann. Es liegt jetzt natürlich in deinem Ermessen, welche Code Coverage für dein Testing erforderlich ist. Wenn du erste Erfahrungen gemacht hast, dann definierst du dir am besten bereits vorab ein hinreichendes Endekriterium für deine Code Coverage Tests.

Den Code zum Blog findest du auch auf GitHub.

Happy Testing!