Continuous Integration einrichten


Eine Continuous Integration (CI) für deinen Track einzurichten ist sehr wichtig, denn sie hilft, Fehler aufzuspüren.

GitHub Actions

Exercism-Repos (einschließlich Track-Repos) verwenden GitHub Actions, um ihre CI auszuführen. GitHub Actions basieren auf Workflows, die Skripte definieren, die automatisch ausgeführt werden, sobald ein bestimmtes Ereignis eintritt (z. B. wenn ein Commit gepusht wird). Weitere Informationen zu GitHub-Actions-Workflows findest du in der Workflow-Dokumentation.

Vorinstallierte Workflows

Tracks werden mit einer Reihe vorinstallierter Workflows ausgeliefert, von denen du die meisten nicht ändern solltest (sie heißen gemeinsame Workflows). Einen Workflow solltest du aber ändern, nämlich den Workflow test.yml.

Test-Workflow

Ziel des Workflows test.yml ist es, zu überprüfen, dass die Übungen des Tracks in ordentlichem Zustand sind. Der Workflow ist so eingerichtet, dass er automatisch läuft (in der GitHub-Actions-Terminologie: ausgelöst wird), wenn ein Push in den Branch main oder in den Branch eines Pull Requests erfolgt.

Der Workflow selbst sollte nicht viel tun, außer:

  • Den Code auschecken (bereits implementiert)
  • Abhängigkeiten installieren (z. B. Pakete installieren, optional)
  • Tooling installieren (z. B. ein SDK installieren, optional)
  • Das Skript verify-exercises ausführen (bereits implementiert)

Das verify-exercises-Skript implementieren

Wie erwähnt werden die Übungen über ein Skript verifiziert, nämlich das Skript bin/verify-exercises (Bash). Dieses Skript ist fast fertig und macht Folgendes:

  • Es iteriert über alle Übungsverzeichnisse
  • Dann, für jedes Übungsverzeichnis:
    • Kopiert es die Beispiel-/Exemplarlösung in die (Stub-)Lösungsdateien (bereits implementiert)
    • Ruft es die Funktion unskip_tests auf, in der du Tests in deinen Testdateien wieder aktivieren kannst (optional)
    • Ruft es die Funktion run_tests auf, in der du die Tests ausführen solltest (erforderlich)

Die Funktionen run_tests und unskip_tests sind die einzigen Dinge, die du implementieren musst.

Tests wieder aktivieren

Wenn dein Track das Überspringen von Tests unterstützt, müssen wir sicherstellen, dass beim Verifizieren der Beispiel-/Exemplarlösung einer Übung keine Tests übersprungen werden. Im Allgemeinen gibt es zwei Arten, wie Tracks das „Wiederaktivieren“ von Tests unterstützen:

  1. Annotationen/Code/Text aus den Testdateien entfernen. Zum Beispiel test.skip in test ändern.
  2. Eine Umgebungsvariable bereitstellen. Zum Beispiel SKIP_TESTS=false setzen.

Annotationen/Code/Text aus den Testdateien entfernen

Wenn das Überspringen von Tests dateibasiert ist (die erste Option oben), passe die Funktion unskip_tests an, um die Testdateien zu ändern (der vorhandene Code kümmert sich bereits um die Schleife über die Testdateien).

Note

Die Funktion unskip_test läuft auf einer Kopie eines Übungsverzeichnisses, du kannst die Dateien also nach Belieben ändern.

Beispiel

Die bin/verify-exercises file des Arturo-Tracks verwendet sed, um die Tests in den Testdateien wieder zu aktivieren:

unskip_tests() {
    jq -r '.files.test[]' .meta/config.json | while read -r test_file; do
        sed -i 's/test.skip/test/g' "${test_file}"
    done
}

Eine Umgebungsvariable bereitstellen

Caution

Wenn das Wiederaktivieren von Tests das Setzen einer Umgebungsvariablen erfordert, stelle sicher, dass sie in der Funktion run_tests gesetzt wird.

Tests ausführen

Die Funktion run_tests ist dafür zuständig, die Tests einer Übung auszuführen. Wenn die Funktion aufgerufen wird, wurden die Beispiel-/Exemplardateien bereits in die (Stub-)Lösungsdateien kopiert, du musst also nur den richtigen Befehl aufrufen, um die Tests auszuführen.

Die Funktion muss als Exit-Code Null zurückgeben, wenn alle Tests bestehen, andernfalls einen Exit-Code ungleich Null.

Note

Die Funktion run_tests läuft auf einer Kopie eines Übungsverzeichnisses, du kannst die Dateien also nach Belieben ändern.

Option 1: Das Tooling der Sprache verwenden

Die Standardoption für das Skript verify-exercises ist, das Tooling der Sprache (SDK/Binary/usw.) zu verwenden, was die meisten Tracks auch tun. Jeder Track hat seine eigene Art, die Tests auszuführen, aber meistens ist es einfach ein einzelner Befehl.

Beispiel

Die bin/verify-exercises file des Arturo-Tracks ändert die Funktion run_tests, um einfach den Befehl arturo mit der Testdatei aufzurufen:

run_tests() {
    arturo tester.art
}

Option 2: Das Test-Runner-Docker-Image verwenden

Die zweite Option ist, die Übungen zu verifizieren, indem du den Test-Runner des Tracks ausführst. Das setzt natürlich voraus, dass der Track einen funktionierenden Test-Runner hat.

Wenn dein Track noch keinen Test-Runner hat, kannst du entweder:

  • einen funktionierenden Test-Runner bauen, oder
  • Option 1 verwenden und direkt das Tooling der Sprache nutzen

Am Standard-Skript bin/verify-exercises müssen folgende Änderungen vorgenommen werden:

  1. Überprüfen, dass der Befehl docker verfügbar ist
  2. Das Test-Runner-Docker-Image pullen (herunterladen)
  3. Mit docker run das Test-Runner-Docker-Image für jede Übung ausführen
  4. Mit jq überprüfen, dass die vom Docker-Container zurückgegebene Datei results.json anzeigt, dass alle Tests bestanden wurden
  5. Die Funktion unskip_test und den Aufruf dieser Funktion entfernen
Note

Der Hauptvorteil dieses Ansatzes ist, dass er am besten nachbildet, wie die Tests in Produktion (auf der Website) ausgeführt werden. Damit ist es weniger wahrscheinlich, dass in Produktion Dinge fehlschlagen, die in der CI bestanden haben. Der Nachteil dieses Ansatzes ist, dass er meist langsamer ist, weil das Docker-Image gepullt werden muss und Docker Overhead mit sich bringt.

Beispiel

Die bin/verify-exercises file des Unison-Tracks fügt die Prüfung hinzu, dass der Befehl docker installiert ist:

required_tool docker

Dann pullt er das Test-Runner-Image des Tracks:

docker pull exercism/unison-test-runner

Anschließend ändert er die Funktion run_tests, um mit docker run den Test-Runner für die aktuelle Übung (die sich im Arbeitsverzeichnis befindet) auszuführen, gefolgt von einem jq-Befehl, um den richtigen Status zu prüfen:

run_tests() {
    local slug

    slug="${1}"

    docker run \
        --rm \
        --network none \
        --mount type=bind,src="${PWD}",dst=/solution \
        --mount type=bind,src="${PWD}",dst=/output \
        --tmpfs /tmp:rw \
        exercism/unison-test-runner "${slug}" "/solution" "/output"
    jq -e '.status == "pass"' "${PWD}/results.json" >/dev/null 2>&1
}

Schließlich müssen wir den Aufruf des Befehls run_tests anpassen, da er jetzt den Slug benötigt:

run_tests "${slug}"

Den Test-Workflow implementieren

Jetzt, wo das Skript verify-exercises fertig ist, ist es Zeit, den Workflow test.yml abzuschließen. Wie das geht, hängt davon ab, welche Option für die Implementierung des Skripts verify-exercises gewählt wurde.

Option 1: Das Tooling der Sprache verwenden

Wenn das Skript verify-exercises direkt das Tooling der Sprache verwendet, muss der Test-Workflow Folgendes installieren:

  • Abhängigkeiten des Sprach-Toolings, wie openssh oder einen C/C++-Compiler.
  • Das Sprach-Tooling, wie ein SDK oder Binary. Wenn die Installation des Sprach-Toolings das installierte Binary bzw. die installierten Binaries nicht zum Pfad hinzufügt, stelle sicher, dass du sie zum Systempfad von GitHub Actions hinzufügst.

Sobald das erledigt ist, sollte das Skript verify-exercises wie erwartet funktionieren, und du hast die CI erfolgreich eingerichtet!

Ein Beispiel findest du im Test-Workflow test.yml des Arturo-Tracks:

name: Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:

jobs:
  ci:
    runs-on: ubuntu-22.04

    steps:
      - name: Checkout repository
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332

      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install libgtk-3-dev libwebkit2gtk-4.0-dev libmpfr-dev

      - name: Install Arturo
        run: bin/install-arturo
        env:
          GH_TOKEN: ${{ github.token }}

      - name: Verify all exercises
        run: bin/verify-exercises

Option 2: Das Test-Runner-Docker-Image verwenden

Die zweite Option ist, die Übungen zu verifizieren, indem du den Test-Runner des Tracks ausführst. Diese Option erfordert zwei Dinge:

  1. Der Track hat einen funktionierenden Test-Runner
  2. Das Skript verify-exercises verwendet das Test-Runner-Docker-Image, um die Tests einer Übung auszuführen

Wenn dein Track noch keinen Test-Runner hat, kannst du entweder:

  • einen funktionierenden Test-Runner bauen, oder
  • Option 1 verwenden und direkt das Tooling der Sprache nutzen

Dieser Ansatz hat ein paar Vorteile:

  1. Du musst im Test-Workflow keine Abhängigkeiten/Tooling installieren (die wurden bereits im Docker-Image installiert)
  2. Der Ansatz bildet am besten nach, wie die Tests in Produktion (auf der Website) ausgeführt werden, was die Wahrscheinlichkeit von Produktionsproblemen verringert.

Der größte Nachteil ist, dass er wahrscheinlich langsamer ist, weil das Docker-Image gepullt werden muss und Docker Overhead mit sich bringt.

Es gibt ein paar Möglichkeiten, das Test-Runner-Docker-Image zu pullen:

  1. Das Image in der Datei verify-exercises herunterladen. Diesen Ansatz wählt der Unison-Track.
  2. Das Image im Workflow herunterladen. Diesen Ansatz wählt der Standard-ML-Track.
  3. Das Image im Workflow bauen. Diesen Ansatz wählt der 8th-Track.

Welchen Ansatz solltest du also wählen? Wir empfehlen, mindestens Option 1 zu implementieren, damit das Skript verify-exercises eigenständig ist. Wenn dein Image besonders groß ist, kann es sinnvoll sein, auch Option 3 zu implementieren, die das gebaute Docker-Image im Cache von GitHub Actions ablegt. Nachfolgende Läufe können das Docker-Image dann einfach aus dem Cache lesen, statt es herunterzuladen, was besser für die Performance sein kann (bitte messen, um sicherzugehen).

Option 3: Das verify-exercises-Skript innerhalb des Test-Runner-Docker-Images ausführen

Eine dritte, alternative Option ist eine Mischung aus den beiden vorherigen Optionen. Hier verwenden wir ebenfalls das Test-Runner-Docker-Image, nur führen wir diesmal das Skript verify-exercises innerhalb dieses Docker-Images aus. Um diese Option zu ermöglichen, müssen wir den Container des Workflows auf den Test-Runner setzen:

container:
  image: exercism/vimscript-test-runner

Dann können wir die Schritte zur Installation von Abhängigkeiten und Tooling überspringen (die wurden bereits im Test-Runner-Docker-Image installiert) und mit dem Ausführen des Skripts bin/verify-exercises fortfahren.

Beispiel

Der Test-Workflow test.yml des vimscript-Tracks verwendet diese Option:

name: Verify Exercises

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

jobs:
  ci:
    runs-on: ubuntu-24.04
    container:
      image: exercism/vimscript-test-runner

    steps:
      - name: Checkout repository
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332

      - name: Verify all exercises
        run: bin/verify-exercises