Andere CI-Systeme
TestDetta funktioniert auf jeder CI, die .NET und Git ausführt. Außerhalb von GitHub Actions genügen zwei Befehle und ein Ordner.
- Auf dem Standard-Branch führt
td record --maps-dir <maps>die gesamte Testsuite einmal mit Coverage aus und speichert eine Coverage-Map pro Testprojekt unter<maps>/<commit>/. - In einem Pull oder Merge Request stellt
td test --base origin/<target> --maps-dir <maps>die neueste Map an oder vor der Merge-Base wieder her. Es nimmt die nächstgelegene innerhalb von 50 First-Parent-Commits (--max-map-age) und überbrückt die Änderungen dazwischen. Dann führt es nur die betroffenen Tests aus.
<maps> ist ein gewöhnlicher Ordner. Legen Sie ihn dort ab, wo Ihre CI Dateien zwischen Pipelines aufbewahren kann:
- auf einem von den Agents gemeinsam genutzten Laufwerk, wie es On-Premises üblich ist:
/srv/testdetta-maps/<repository>; - in einem Objektspeicher (S3, Azure Blob, GCS, MinIO), der vorher und nachher synchronisiert wird.
TestDetta selbst nutzt für Maps nie das Netzwerk.
Was jede Einrichtung benötigt
- Vollständige Git-Historie. TestDetta vergleicht mit der Merge-Base und durchläuft die Historie, um Maps zu finden. Shallow Clones können beides nicht.
- Der Ziel-Branch ist abgerufen, sodass
origin/<target>existiert. Rufen Sie ihn mit einer expliziten Refspec ab:+refs/heads/<target>:refs/remotes/origin/<target>. CI-Klone beschränken die Refspecs des Remotes oft auf den eigenen Branch der Pipeline (die von GitLab und Bitbucket tun das), und dann aktualisiertgit fetch origin <target>allein nurFETCH_HEAD, undorigin/<target>fehlt weiterhin. - Die Lizenz in
TESTDETTA_LICENSE(einer maskierten oder geheimen Variable) oder in einer Datei, dieTESTDETTA_LICENSE_FILEbenennt. Öffentliche Repositorys benötigen auf GitLab CI und GitHub Actions keine; Jenkins, Azure DevOps und Bitbucket geben nicht an, ob ein Repository öffentlich ist, daher benötigt dort jedes Repository eine. Ohne gültige Lizenz laufen alle Tests, und der Build schlägt deswegen nie fehl. Siehe Lizenz. - Exitcodes:
0Erfolg,1Analyse fehlgeschlagen,2ungültige Befehlszeile,3Tests fehlgeschlagen. - Aufräumen: Maps alter Commits werden nur für 50 Commits benötigt. Löschen Sie ältere Ordner mit einem geplanten Job oder einer Lifecycle-Regel des Speichers.
Installation
dotnet tool install --global <package>
Der Paketname wird zum Launch festgelegt. Bis dahin bauen Sie TestDetta aus dem Quellcode mit dotnet build src/TestDetta.Cli -c Release -o testdetta-bin und rufen überall dort, wo unten td steht, dotnet testdetta-bin/TestDetta.Cli.dll auf.
GitLab CI
variables:
GIT_DEPTH: "0" # full history
TESTDETTA_MAPS: /srv/testdetta-maps/$CI_PROJECT_PATH # a disk the runners share; or sync with object storage
record-coverage:
stage: test
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- td record --maps-dir "$TESTDETTA_MAPS"
affected-tests:
stage: test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- git fetch --no-tags origin "+refs/heads/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME:refs/remotes/origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
- td test --base "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" --maps-dir "$TESTDETTA_MAPS" --summary-file testdetta-summary.md
artifacts:
when: always
paths: [testdetta-summary.md]
Setzen Sie TESTDETTA_LICENSE unter Settings → CI/CD → Variables, je nach Bedarf maskiert und geschützt. TestDetta erkennt öffentliche Projekte über CI_PROJECT_VISIBILITY.
Jenkins
Eine deklarative Multibranch-Pipeline:
pipeline {
agent any
environment {
TESTDETTA_LICENSE = credentials('testdetta-license') // a "secret text" credential
TESTDETTA_MAPS = "/srv/testdetta-maps/${env.JOB_NAME.split('/')[0]}"
}
options { skipDefaultCheckout() }
stages {
stage('Checkout') {
steps {
checkout scmGit(branches: scm.branches, userRemoteConfigs: scm.userRemoteConfigs,
extensions: [cloneOption(shallow: false, depth: 0, noTags: true, reference: '')])
}
}
stage('Record coverage') {
when { branch 'main' }
steps { sh 'td record --maps-dir "$TESTDETTA_MAPS"' }
}
stage('Affected tests') {
when { changeRequest() }
steps {
sh 'git fetch --no-tags origin "+refs/heads/$CHANGE_TARGET:refs/remotes/origin/$CHANGE_TARGET"'
sh 'td test --base "origin/$CHANGE_TARGET" --maps-dir "$TESTDETTA_MAPS"'
}
}
}
}
Jenkins klont oft mit Anmeldedaten in der Remote-URL. TestDetta entfernt sie, bevor es den Lizenz-Scope abgleicht, und loggt sie nie. Wenn der Remote nicht das Repository benennt, das die Lizenz abdeckt (zum Beispiel bei einem lokalen Mirror), setzen Sie TESTDETTA_LICENSE_SCOPE, etwa auf git.acme.corp/scm/pay/app.
Azure DevOps Pipelines
trigger: [main]
pr: [main]
steps:
- checkout: self
fetchDepth: 0
- script: td record --maps-dir "$(TESTDETTA_MAPS)"
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
env:
TESTDETTA_LICENSE: $(TestDettaLicense) # a secret pipeline variable
- script: |
target="${SYSTEM_PULLREQUEST_TARGETBRANCH#refs/heads/}"
git fetch --no-tags origin "+refs/heads/$target:refs/remotes/origin/$target"
td test --base "origin/$target" --maps-dir "$(TESTDETTA_MAPS)" --summary-file "$(Agent.TempDirectory)/testdetta.md"
echo "##vso[task.uploadsummary]$(Agent.TempDirectory)/testdetta.md"
condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))
env:
TESTDETTA_LICENSE: $(TestDettaLicense)
TESTDETTA_MAPS verweist auf einen Netzwerkpfad auf selbst gehosteten Agents. Auf von Microsoft gehosteten Agents synchronisieren Sie den Ordner mit Azure Blob Storage (siehe Objektspeicher).
Bitbucket Pipelines
clone:
depth: full
pipelines:
branches:
main:
- step:
script:
- td record --maps-dir maps
- ./sync-maps.sh upload maps # see "Object storage"
pull-requests:
'**':
- step:
script:
- git fetch --no-tags origin "+refs/heads/$BITBUCKET_PR_DESTINATION_BRANCH:refs/remotes/origin/$BITBUCKET_PR_DESTINATION_BRANCH"
- ./sync-maps.sh download maps "origin/$BITBUCKET_PR_DESTINATION_BRANCH"
- td test --base "origin/$BITBUCKET_PR_DESTINATION_BRANCH" --maps-dir maps
Legen Sie TESTDETTA_LICENSE als geschützte Repository-Variable an.
Objektspeicher statt gemeinsamem Laufwerk
Auf gehosteten Runnern gibt es kein gemeinsames Laufwerk. Laden Sie den neuen Ordner nach der Aufzeichnung hoch. Laden Sie vor dem Testen nur die Ordner herunter, die verwendet werden können: die Merge-Base und bis zu 50 First-Parent-Commits davor. Laden Sie sie vom neuesten an herunter und hören Sie beim ersten vorhandenen auf. Ein Beispiel für S3; jedes Werkzeug mit cp und ls funktioniert genauso:
#!/usr/bin/env bash
# sync-maps.sh upload <maps> | after td record on the default branch
# sync-maps.sh download <maps> <base> | before td test on a pull request
set -euo pipefail
remote="s3://my-bucket/testdetta-maps/my-repository"
case "$1" in
upload)
aws s3 cp --recursive "$2/$(git rev-parse HEAD)" "$remote/$(git rev-parse HEAD)" ;;
download)
for sha in $(git rev-list --first-parent -n 51 "$(git merge-base "$3" HEAD)"); do
if aws s3 ls "$remote/$sha/" > /dev/null 2>&1; then
aws s3 cp --recursive "$remote/$sha" "$2/$sha"
break
fi
done ;;
esac
Wenn es keine Map gibt
Die ersten Pull Requests nach der Einrichtung oder solche, die weit zurückliegen, finden keine Map in Reichweite. TestDetta wählt dann nur anhand von Namen aus, was sicher ist, aber mehr auswählt, und vermerkt das in seinem Log. Die Auswahl wird präzise, sobald td record auf dem Standard-Branch gelaufen ist.