Rust: Crates und Continuous Integration – eine perfekte Mischung

Seite 2: Continuous Integration mit GitHub Actions

Inhaltsverzeichnis

Um bei jeder Änderung im Crate automatisiert hilfreiche Funktionen ausführen zu können, vertraut die Community auf Continuous Integration (CI). Dafür stehen zahlreiche Plattformen – darunter TravisCI oder Gitlab – parat, die CI-Prozesse unterstützen. Da große Teile der Entwicklung des Rust-Projektes auf GitHub ablaufen, beschränken sich die Autoren an dieser Stelle auf einen kurzen Überblick zu GitHub Actions. Als Teil der Versionsverwaltungsplattform ermöglicht Letzteres das automatisierte Bauen von Software für verschiedene Betriebssysteme und unterstützt derzeit Linux, Windows und macOS. Nach Änderungen am Sourcecode lässt sich eine Software auf all diesen Betriebssystemen automatisch bauen und testen.

Dazu ist im Verzeichnis .github/workflows des Repositorys eine YAML-Datei zu erzeugen, die beschreibt, wie die Software zu bauen und zu testen ist.

name: Test

on:
  push:
  pull_request:
    branches:
      - master

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macOS-latest]
        rust: [stable, nightly]
    steps:
    - uses: hecrj/setup-rust-action@v1
      with:
        rust-version: ${{ matrix.rust }}
    - uses: actions/checkout@master

Nach dem Label on folgt eine Beschreibung, unter welchen Bedingungen die Action auszuführen ist. Die im Beispiel unter dem Label jobs beschriebenen Schritte sollen nach jeder Änderung im Branch master erfolgen. jobs enthält eine Matrix, die die Konfiguration der einzelnen Testsysteme beschreibt. Die Action ist so konfiguriert, dass sowohl die Stable- als auch die Nightly-Version des Rust-Compilers sie jeweils unter macOS, Windows und Linux ausführt (insgesamt also sechsmal).

Die Zeilen nach dem Label steps beschreiben die Schritte, die auf den Systemen durchzuführen sind. setup-rust-action installiert den Rust-Compiler, anschließend erfolgt der Check-out des Branch master.

In der Regel möchten Entwickler sicherstellen, dass ihr Code im Repository stets frisch kompiliert vorliegt. Auf Basis des gezeigten Gerüsts lässt sich neu hinzugekommener Code automatisiert kompilieren. Dazu ist lediglich der folgende Schritt zu ergänzen:

      - uses: actions-rs/cargo@v1
        with:
          command: build

Geht das Kompilieren des Crate schief, scheitert der betreffende Erstellungsprozess in GitHub Actions und auf der Website des Repositorys erscheint ein Hinweis. Nach erfolgreichem Bauen lässt sich das Programm ausführen und testen. Dazu sind Tests zu erstellen, um das Programm mit unterschiedlichen Input-Daten zu prüfen. Darüber hinaus empfehlen sich Unit Tests. In Rust lassen sich solche Tests direkt im Sourcecode integrieren.

pub fn maximum(a: i32, b: i32) -> i32 {
    if a > b {
        a
    } else {
        b
    }
}

[cfg(test)]
mod tests {
    // Note this useful idiom: importing names from outer (for mod tests) scope.
    use super::*;

    #[test]
    fn test_maximum() {
        assert_eq!(maximum(1, 2), 2);
    }
}

Das Modul tests mit der Testfunktion test_maximum prüft im Beispiel die Funktion maximum. Testfunktionen sind mit #[test] gekennzeichnet. Dieser Code wird jedoch nur nach dem Aufrufen von cargo test erzeugt und ausgeführt. Alternativ lässt sich der Test auch automatisiert über die CI-Pipeline ausführen – mit den folgenden Ergänzungen in der Konfigurationsdatei:

      - uses: actions-rs/cargo@v1
        with:
          command: test

Für statistische Zwecke ist es wünschenswert, die Testabdeckung (Code Coverage) zu bestimmen. Sie beschreibt das Verhältnis tatsächlich getroffener Aussagen eines Tests zu den theoretisch möglichen Aussagen. Die Testumgebung von Rust bietet standardmäßig keine Unterstützung, die Erweiterung tarpaulin hilft aber weiter. Sie erstellt einen entsprechenden Report als Website oder reicht die Ergebnisse direkt an Services wie Coveralls oder CodeCov weiter.