Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 6 Min.

GitHub Actions umstellen

Eine Jobmatrix bringt die lokale Testaufteilung auf mehrere Runner. Damit daraus ein verlässlicher CI-Lauf entsteht, müssen Shard-Nummern, Fehlerstatus und Ergebnisdateien zusammenpassen. Der Workflow zeigt die nötigen Schritte.
© EMGenie

Der vorangegangene Teil 1 der Serie „Schnellere .NET-CI-Pipelines mit Test Sharding“ hat die deterministische Testaufteilung erklärt und an acht xUnit-Testfällen gezeigt, wie sich die Auswahl eines Shards lokal anzeigen lässt. Dieser Beitrag zeigt, wie GitHub Actions die Teilmengen in getrennten Jobs ausführt. Dabei erhält jeder Job denselben Quellstand und dieselbe Gesamtzahl an Shards, aber eine andere Shard-Nummer. Erst diese Kombination ermöglicht eine vollständige Aufteilung ohne Überschneidungen.

In der Matrix

Eine Matrix erzeugt mehrere Jobs aus derselben Definition. Für Sharding unterscheiden sie sich zunächst nur durch eine Zahl; alles andere muss übereinstimmen: neben dem Quellstand auch die Paketversionen und die Testkonfiguration. Teilt ein Job die Suite durch vier und ein anderer durch drei, entstehen Lücken oder Überschneidungen. Auch Tests, die je nach Betriebssystem bedingt laufen, verhindern eine gemeinsame verlässliche Aufteilung, weil die Jobs dann unterschiedliche Testlisten sehen.

Basis ist das MTP-Testprojekt aus dem vorangegangenen Teil 1 mit Meziantou.ShardedTest 1.1.0. Im Repository liegen die Projektdatei, ArithmeticTests.cs, global.json mit der MTP-Auswahl und .config/dotnet-tools.json. Jeder Matrixjob erhält eine eigene Arbeitsumgebung und baut das Testprojekt selbst. Dieser einfache Aufbau verursacht zwar wiederholte Arbeit, benötigt dafür aber noch keinen Transfer von Build-Ergebnissen zwischen Jobs.

Die Matrix vollständig definieren

Die folgende Datei liegt unter .github/workflows/sharded-tests.yml. matrix.shard nimmt die Werte 1 bis 4 an, und GitHub erzeugt für jeden Wert einen Job. strategy.job-total enthält bei dieser eindimensionalen Matrix die Zahl 4. env übergibt beide Werte als SHARD und SHARDS an die Shell. Der erste Aufruf erhält somit --shard-index 1 --total-shards 4, der zweite Index 2 bei unverändertem Nenner. Die Nummerierung ab eins ist eine Vorgabe von ShardedTest.

Die Datei sharded-tests.yml ist folgendermaßen aufgebaut:

 

name: Sharded tests
on: [push, pull_request, workflow_dispatch]
permissions:
  contents: read
jobs:
  tests:
    runs-on: ubuntu-24.04
    timeout-minutes: 20
    strategy:
      fail-fast: false
      max-parallel: 4
      matrix:
        shard: [1, 2, 3, 4]
    env:
      TEST_PROJECT: tests/ShardDemo.Tests/ShardDemo.Tests.csproj
      SHARD: ${{ matrix.shard }}
      SHARDS: ${{ strategy.job-total }}
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-dotnet@v6
        with:
          dotnet-version: '10.0.x'
      - run: dotnet tool restore
      - run: dotnet restore "$TEST_PROJECT"
      - run: dotnet build "$TEST_PROJECT" -c Release --no-restore
      - name: Run shard
        run: |
          dotnet tool run sharded-test \
            --shard-index "$SHARD" --total-shards "$SHARDS" \
            --project "$TEST_PROJECT" \
            --configuration Release --no-build \
            --results-directory "$GITHUB_WORKSPACE/results/$SHARD" \
            --report-trx \
            --report-trx-filename 'tests_{pid}_{time}.trx'
      - name: Keep test reports
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@v7
        with:
          name: test-results-${{ matrix.shard }}
          path: results/${{ matrix.shard }}/*.trx
          if-no-files-found: error
          retention-days: 7

 

checkout lädt den Quellstand, setup-dotnet installiert das SDK. dotnet tool restore stellt die Werkzeuge aus dem Manifest bereit; dotnet restore lädt die Projektabhängigkeiten. Danach baut dotnet build das Projekt ohne erneuten Restore. Der Testschritt verwendet dieses Release-Kompilat. Der absolute Ergebnispfad führt in den Arbeitsordner des Jobs; der Upload sammelt ausschließlich dessen TRX-Dateien ein. Das Zeitlimit beendet einen hängen gebliebenen Job nach spätestens 20 Minuten.

Die gezeigten Action-Hauptversionen entsprechen dem Recherchestand vom 17. September 2026. Für reproduzierbare Abläufe fixiert das Team geprüfte Commit-SHAs und SDK-Patches; 10.0.x lässt SDK-Updates zu. Eigene Runner benötigen eine zu den Actions passende Runner-Version.

max-parallel: 4 erlaubt bis zu vier gleichzeitige Matrixjobs, reserviert aber keine Kapazität. Verfügt das Konto oder der Runner-Pool nur über zwei freie Plätze, laufen die Jobs in mehreren Wellen. Für eine erste Messung ist deshalb die Warteschlange genauso relevant wie die eigentliche Testdauer.

Sobald die Matrix zusätzliche Dimensionen wie Betriebssystem oder Ziel-Framework enthält, ist strategy.job-total nicht mehr die Shard-Anzahl pro Umgebung. Dann erhält jede Umgebung eine eigene vollständige Shard-Gruppe mit explizit gesetztem Nenner, also --total-shards. Vier Shards auf zwei Betriebssystemen bedeuten acht Jobs, aber weiterhin vier Shards je Testsuite.

Fehler sichtbar lassen

Mit fail-fast: false lässt GitHub die übrigen Matrixjobs nach einem Testfehler weiterlaufen. Ohne diese Einstellung könnte ein früher Fehler in Shard 1 die anderen Jobs abbrechen, bevor deren Befunde vorliegen. Das Weiterlaufen liefert mehr Diagnosematerial, kostet aber zusätzliche Runner-Zeit. Der fehlgeschlagene Job bleibt fehlgeschlagen. continue-on-error hätte eine andere Wirkung: Diese Option erlaubt einen Fehler, ohne deshalb den gesamten Workflow scheitern zu lassen. Sie gehört daher nicht an verpflichtende Testjobs.

Innerhalb eines Shards gilt eine weitere Grenze: Bei vielen Testnamen kann die Filterzeichenkette zu lang werden. ShardedTest teilt die Auswahl dann in Filterbatches, also Gruppen für mehrere nacheinander gestartete dotnet test-Aufrufe. Nach dem ersten fehlgeschlagenen Aufruf beendet Version 1.1.0 den Shard. fail-fast: false steuert nur die Matrix und ändert daran nichts. Geplante Auswahl und tatsächlich abgeschlossene Tests können daher voneinander abweichen.

Der Upload-Schritt läuft auch nach einem fehlgeschlagenen Testschritt, sofern niemand den Workflow abgebrochen hat. Ein fehlender Report lässt diesen Schritt scheitern. Für die Demo ist das sinnvoll, weil jeder Shard Tests enthält. Bei bewusst erlaubten leeren Shards braucht das Team stattdessen eine ausdrückliche Regel für erwartete Testzahlen. Ein Prozessabbruch kann zudem unvollständige Reports hinterlassen.

Warum ein Dateiname pro Shard nicht genügt

Die Shard-Nummer trennt die Ergebnisverzeichnisse. Zusätzlich enthalten die TRX-Dateinamen Prozess-ID und Zeitstempel. Das ist bei den beschriebenen Filterbatches wichtig: Jeder dotnet test-Aufruf schreibt seinen eigenen Bericht. Ein fester Name wie results.trx könnte dabei frühere Ergebnisse desselben Shards überschreiben.

Die TRX-Erweiterung bietet die Platzhalter {pid} und {time} seit MTP 2.3. Das in Teil 1 dieser Serie fixierte Paket 2.3.3 passt dazu. Die einfachen Anführungszeichen geben die Vorlage unverändert an die Testanwendung weiter; der Reporter ersetzt die Platzhalter. Ohne installiertes TRX-Paket kennt die Testanwendung --report-trx nicht.

GitHub bewahrt die Dateien sieben Tage als herunterladbare Artefakte auf. Der Upload erzeugt noch keine gemeinsame Testübersicht und erhält den übergeordneten Shard-Ordner nicht. Für die Auswertung aus Teil 4 entpacken wir daher test-results-1 nach results/1, test-results-2 nach results/2 und entsprechend weiter. So bleiben sämtliche Reports ihrem Shard zugeordnet.

Build und Discovery auseinanderhalten

ShardedTest entdeckt vor jeder Auswahl zunächst die Tests. Mit dem vorgelagerten Build und --no-build verhindern wir, dass diese Discovery und mögliche Filterbatches wiederholt den Build anstoßen. Die Kosten für Prozessstart und Discovery bleiben dennoch pro Shard bestehen. Ein NuGet-Cache kann zwar Downloads verkürzen, ersetzt aber keine gebauten Testdateien.

Bei langen Builds kann ein gemeinsamer Buildjob seine Ergebnisse an die Shards verteilen. Das lohnt sich erst, wenn die eingesparte Kompilierung den Upload, die Downloads und die zusätzliche Abhängigkeit übersteigt. Das Artefakt muss auch Laufzeitdateien, Abhängigkeiten und benötigte Testdaten enthalten. Ein Verzeichnis mit ausschließlich der Test-DLL genügt nicht. Nutzt die Ausführung weiterhin den Projektpfad, sind außerdem die erforderlichen Projekt- und MSBuild-Zustände zu beachten.

Zwischenfazit: Vier Jobs mit nachvollziehbarem Ergebnis

Die Matrix verteilt jetzt dieselbe Suite auf vier Jobs, lässt Fehler sichtbar und bewahrt deren Reports auf. Ob das schneller ist, hängt auch von Buildkosten und freien Runnern ab. Weil zudem SDK-Version, Lockdateien und Cachezustand die Laufzeit beeinflussen, gehören sie ins Messprotokoll. Im nächsten Teil dieser Serie übertragen wir den Aufbau nach GitLab und ergänzen die Ergebnisdarstellung im Merge Request.

Neueste Beiträge

Outlook und Mailintegration mit Microsoft Graph - Connected Apps mit .NET, Teil 2
Eine .NET-App meldet Benutzer an, liest Outlook-Nachrichten aus dem Posteingang und bereitet E-Mails für Anzeige und weitere Verarbeitung auf.
8 Minuten
Warum Deine KI-Rechnung mit der Architektur wächst - Interview
KI soll Kosten senken. In der Praxis erleben viele Unternehmen beim Skalieren eine böse Überraschung, erklärte Dimitri Blatner von ITech Progress auf der Open Stage der DWX 2026. Developer World wollte von ihm wissen, wo Sprachmodelle Geld verbrennen und wie Du mit bewusster Architektur gegensteuerst.
30. Sep 2026
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

Das könnte Dich auch interessieren

Tests gezielt verteilen - Schnellere .NET-CI-Pipelines mit Test Sharding, Teil 1
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.
6 Minuten
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
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige