Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 6 Min.

Tests gezielt verteilen

Lange Testsuiten verzögern das Feedback im Pull Request. Meziantou.ShardedTest verteilt die Testfälle auf mehrere CI-Jobs und verspricht dadurch mehr Geschwindigkeit. Dieser Einstieg zeigt, wie die Aufteilung funktioniert und welche Voraussetzungen nötig sind.
© EMGenie

Automatisierte Tests prüfen Änderungen bei jedem Build. Mit der Suite wächst allerdings die Zeit, die ein Pull Request auf die Testergebnisse wartet. Test Sharding verteilt diese Arbeit auf mehrere Continuous-Integration-Jobs (CI-Jobs). Die Testauswahl übernimmt beispielsweise die Bibliothek Meziantou.ShardedTest – ein .NET-Kommandozeilentool, das den Anwendungscode aufruft. Dieser Beitrag erklärt das Verfahren und schafft die Projektgrundlage für GitHub Actions, GitLab CI und den abschließenden Laufzeitvergleich.

Wo die Zeit vergeht

Ein CI-Job lädt meist Quellcode und Pakete, baut das Projekt, ermittelt die Testfälle, die sogenannte Discovery, und führt sie aus. Die Maschine beziehungsweise Ausführungsumgebung, die den Job abarbeitet, heißt Runner. Welcher dieser Schritte die Laufzeit bestimmt, entscheidet über den Nutzen von Sharding: Dominiert der Build, löst eine andere Testverteilung das Hauptproblem kaum. Beansprucht dagegen die Ausführung unabhängiger Tests den größten Teil der Laufzeit, können zusätzliche Runner helfen.

Ein Shard enthält dabei einen Teil der Testsuite. Bei vier Shards starten vier Jobs mit unterschiedlichen Teilmengen der Tests. Stehen genügend Runner bereit, arbeiten sie gleichzeitig. Die Dauer des Gesamtlaufs bestimmt dann der zuletzt fertige Job. Vergleichbar ist das Ergebnis aber nur, wenn alle Shards zusammen dieselben Tests abdecken wie der bisherige, gemeinsame Lauf. Ein kurzer grüner Job allein belegt daher noch keine erfolgreiche Beschleunigung.

Drei Ebenen der Parallelität

Testframeworks wie xUnit können Tests innerhalb einer Assembly parallel ausführen. Eine Assembly ist hier das kompilierte Testprojekt. Bei xUnit bildet standardmäßig jede Testklasse eine eigene Testcollection, und mehrere Klassen lassen sich auch ausdrücklich zusammenfassen. Im Modus collections laufen verschiedene Collections parallel, die Tests innerhalb einer Collection nacheinander. Seit xUnit v3 4.0 erlaubt der Modus all zusätzliche Parallelität innerhalb einer Collection. Die konkrete Konfiguration bestimmt also, welche Tests gleichzeitig laufen dürfen.

Darüber liegt die Parallelisierung mehrerer Testmodule oder Ziel-Frameworks durch den Testrunner. Sharding ergänzt eine dritte Ebene: Mehrere CI-Jobs erhalten unterschiedliche Teilmengen derselben Suite. ShardedTest berechnet diese Teilmengen, reserviert aber keine Maschinen. GitHub Actions beziehungsweise GitLab CI starten die Jobs auf verfügbaren CI-Runnern. Testrunner und CI-Runner übernehmen somit unterschiedliche Aufgaben.

Diese Ebenen lassen sich kombinieren. Greifen die Tests auf eine gemeinsame Datenbank zu, erhöhen vier Shards mit jeweils mehreren parallel laufenden Tests allerdings auch die gleichzeitigen Zugriffe. Zudem synchronisiert eine Collection keine getrennten CI-Prozesse: Tests, die nur aufgrund gemeinsamer Framework-Sperren zuverlässig funktionieren, brauchen vor dem Sharding eine andere Ressourcenaufteilung.

Was Meziantou.ShardedTest auswählt

Die Beispiele in diesem Teil und den folgenden Teilen verwenden Meziantou.ShardedTest 1.1.0, veröffentlicht am 18. August 2026. Diese zum Recherchezeitpunkt aktuelle Version benötigt .NET 10. Die feste Versionsangabe sorgt dafür, dass lokale Aufrufe und CI-Jobs dasselbe Werkzeug verwenden.

Jeder Shard ruft zunächst dotnet test --list-tests für die gesamte Suite auf. Danach sortiert das Tool die Anzeigenamen mit StringComparer.Ordinal, also unabhängig von der eingestellten Sprache. Aus der Listenposition, beginnend bei null, berechnet es dann die Shardnummer: Position modulo Shardanzahl, also der Rest der Division, plus eins. Bei drei Shards gehören daher die Positionen 0 und 3 zu Shard 1. Für sechs sortierte Tests sieht das so aus:

 

ShardListenpositionenTests
10 und 3A und D
21 und 4B und E
32 und 5C und F

 

Das ist eine Verteilung im Rundlauf, kein Hashing des Testnamens und keine Gewichtung nach Laufzeiten. Gleich viele Tests je Shard können deshalb sehr unterschiedliche Arbeit bedeuten. Ein zusätzlicher Test am Anfang der Sortierung verschiebt außerdem die nachfolgenden Zuordnungen. Deterministisch bedeutet hier: Gleiche entdeckte Testliste und gleiche Shardanzahl ergeben dieselbe Aufteilung. Eine dauerhaft feste Heimat jedes Tests verspricht das Verfahren nicht.

Weil jeder Job seine Zuordnung selbst berechnet, braucht es keinen zentralen Verteiler. Alle Jobs müssen dafür mit demselben Testkatalog arbeiten. Tests dürfen auch nicht voraussetzen, dass vorher ein anderer Test die notwendigen Daten erzeugt hat: Dieser könnte auf einem anderen Runner liegen.

Zur Ausführung erzeugt das Tool Filter auf DisplayName, den angezeigten Testnamen. Bei einer xUnit-Theory kann dieser auch die Parameter eines Datensatzes enthalten. So lassen sich Datensätze derselben Methode einzeln auswählen, sofern die Discovery sie einzeln meldet. Dynamische Datenquellen und doppelte Anzeigenamen brauchen einen Vergleich mit dem vollständigen Testlauf: Jeder Job muss dieselbe Liste sehen und jeder Filter die beabsichtigten Fälle treffen.

Eine gemeinsame Projektgrundlage

Die Serie verwendet .NET 10 und xUnit auf Microsoft.Testing.Platform, kurz MTP. MTP übernimmt den Start der Testanwendung und die Kommunikation mit dotnet test. xUnit definiert und prüft die Testfälle. ShardedTest kann auch VSTest ansteuern. Dessen Kommandozeilenoptionen unterscheiden sich jedoch von denen des nativen MTP-Modus. Dieser erfordert .NET SDK 10 und MTP ab Version 1.7. Allein die Installation des SDK stellt bestehende Testprojekte nicht um.

Alle folgenden Befehle starten im Repository-Hauptverzeichnis. Dort wählt global.json den MTP-Modus. Bereits vorhandene SDK-Einstellungen bleiben erhalten. Für reproduzierbare Messungen sollte die Datei zusätzlich eine konkrete SDK-Version festlegen:

 

{
  "test": {
    "runner": "Microsoft.Testing.Platform"
  }
}

 

Die Datei tests/ShardDemo.Tests/ShardDemo.Tests.csproj hat folgenden Inhalt:

 

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <OutputType>Exe</OutputType>
    <IsPackable>false</IsPackable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="xunit.v3.mtp-v2"
                      Version="4.0.0" />
    <PackageReference
      Include="Microsoft.Testing.Extensions.TrxReport"
      Version="2.3.3" />
  </ItemGroup>
</Project>

 

Die fixierten Pakete entsprechen der Kombination in den Funktionstests von ShardedTest 1.1.0. xunit.v3.mtp-v2 bringt xUnit mit MTP-Anbindung mit. Der OutputType Exe macht das Testprojekt zur ausführbaren Testanwendung. Das TRX-Paket ergänzt Ergebnisberichte für die CI. TRX ist Microsofts XML-Format für Testergebnisse. Im selben Ordner liegt die Datei ArithmeticTests.cs mit folgenden acht festen Datensätzen:

 

using Xunit;
namespace ShardDemo.Tests;
public class ArithmeticTests
{
    [Theory]
    [InlineData(1, 2)]
    [InlineData(2, 4)]
    [InlineData(3, 6)]
    [InlineData(4, 8)]
    [InlineData(5, 10)]
    [InlineData(6, 12)]
    [InlineData(7, 14)]
    [InlineData(8, 16)]
    public void Doubles(int input, int expected)
        => Assert.Equal(expected, input * 2);
}

 

Bild 1 zeigt die erfolgreiche Testausführung in Visual Studio 2026 Community.

Erfolgreiche Testausführung in Visual Studio (Bild 1)

Erfolgreiche Testausführung in Visual Studio (Bild 1)

© Autor

Theory kennzeichnet einen parametrisierten Test. Jedes InlineData-Paar liefert input und expected. Assert.Equal prüft das Rechenergebnis (siehe Bild 2). So entstehen aus nur einer Testmethode acht einzeln entdeckbare Testfälle.

Eine Testmethode, acht Testfälle: xUnit führt jeden InlineData-Datensatz als eigenen Fall aus (Bild 2)

Eine Testmethode, acht Testfälle: xUnit führt jeden InlineData-Datensatz als eigenen Fall aus (Bild 2)

© Autor

Die Shell-Listings verwenden Bash, unter PowerShell lassen sich die Aufrufe jeweils in eine Zeile schreiben. Das lokale Toolmanifest .config/dotnet-tools.json hält die Werkzeugversion für Entwicklung und CI fest. Die folgenden Aufrufe zeigen Beispiele für die Nutzung der Demo:

 

dotnet new tool-manifest
dotnet tool install Meziantou.ShardedTest --version 1.1.0
dotnet build tests/ShardDemo.Tests -c Release
dotnet tool run sharded-test --shard-index 1 --total-shards 4 \
  --project tests/ShardDemo.Tests/ShardDemo.Tests.csproj \
  --configuration Release --no-build --list-tests

 

Existiert bereits ein Toolmanifest, entfällt dessen Neuanlage. Der Build erzeugt zunächst das Release-Kompilat. --list-tests zeigt anschließend nur die Auswahl von Shard 1, hier zwei der acht Fälle. Ohne die Option führt das Tool sie aus. Für die übrigen Shards wechselt --shard-index auf 2, 3 und 4; --total-shards bleibt 4. --no-build verhindert erneutes Bauen und impliziert --no-restore. Bei MTP kennzeichnet --project ausdrücklich den Projektpfad. Bild 3 zeigt die Ausgabe auf der Konsole mit der Testauswahl aller vier Shards.

Jeder Shard erhält zwei der acht Testfälle. Die Auswahl folgt der ordinal sortierten Liste der Anzeigenamen (Bild 3)

Jeder Shard erhält zwei der acht Testfälle. Die Auswahl folgt der ordinal sortierten Liste der Anzeigenamen (Bild 3)

© Autor

Zwischenfazit: Von der lokalen Auswahl zur CI

Die kleine Suite demonstriert die Zuordnung, bewirkt jedoch keinen realistischen Performancegewinn: Ihr Prozessstart dürfte mehr kosten als die Assertions, und lokal nacheinander gestartete Shards beschleunigen ohnehin nichts. Der nächste Teil überträgt denselben Aufruf in GitHub Actions, wo mehrere Jobs gleichzeitig arbeiten können. Sinnvoll messen lässt sich der Nutzen des Shardings aber erst mit einer ausreichend großen Suite.

Neueste Beiträge

Kalenderdaten mit Microsoft Graph - Connected Apps mit .NET, Teil 1
Eine .NET-App meldet Benutzer an, lädt eine Kalenderwoche und bereitet Outlook-Termine für die Anzeige auf.
6 Minuten
KI ersetzt keine Softskills – sie deckt nur gnadenlos auf, wer keine hat - Interview
Auf der Open Stage der DWX 2026 vertrat Recruiting-Expertin Laura Rheinwald von ITech Progress in ihrem Vortrag die Meinung, dass die KI die Kommunikation zwischen Menschen verändert. Developer World wollte von ihr wissen, in welcher Weise.
23. Sep 2026
Kostenkontrolle bei KI-Coding-Agenten
Best Practices für eine ökonomisch nachhaltige Entwicklung.
8 Minuten

Das könnte Dich auch interessieren

Connected Apps mit .NET - Graph API als Integrationsplattform
Microsoft Graph bietet .NET-Entwicklern einheitlichen Zugang zu den Daten und Diensten von Microsoft 365 – für moderne, sichere und kontextbezogene Business-Anwendungen.
17 Minuten
Konfiguration und Verwaltung von Self-Hosted Agents
Mit der großen Flexibilität von selbst gehosteten Agents ist die große Verantwortung verbunden, diese sicher zu konfigurieren und zu betreiben.
10 Minuten
21. Aug 2025
Managed DevOps Pools - Azure DevOps Pipelines Security
Agent Pools als Managed Service mit einfacher Integration in private Netzwerke und Authentisierung mittels Managed Identity tragen deutlich zur Sicherheit der Agent-Infrastruktur bei.
7 Minuten
7. Aug 2025
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige