Diğer CI sistemleri
TestDetta, .NET ve git çalıştıran her CI'da çalışır. GitHub Actions dışında iki komut ve bir klasör işi görür.
- Varsayılan branch'te
td record --maps-dir <maps>tüm test paketini kapsam ölçümüyle bir kez çalıştırır ve her test projesi için<maps>/<commit>/altına bir kapsam haritası kaydeder. - Bir pull ya da merge request'te
td test --base origin/<target> --maps-dir <maps>, merge base'deki ya da ondan önceki en yeni haritayı geri yükler. 50 first-parent commit (--max-map-age) içindeki en yakın haritayı alır ve aradaki değişiklikleri köprüler. Ardından yalnızca etkilenen testleri çalıştırır.
<maps> sıradan bir klasördür. Onu CI'ınızın pipeline'lar arasında dosya tutabildiği herhangi bir yerde saklayın:
- agent'ların paylaştığı bir disk, şirket içi kurulumlarda yaygındır:
/srv/testdetta-maps/<repository>; - öncesinde ve sonrasında senkronize edilen nesne depolama (S3, Azure Blob, GCS, MinIO).
TestDetta'nın kendisi haritalar için hiçbir zaman ağı kullanmaz.
Her kurulumun ihtiyaç duydukları
- Tam git geçmişi. TestDetta, merge base ile karşılaştırma yapar ve haritaları bulmak için geçmişi dolaşır. Shallow clone'lar ikisini de yapamaz.
- Hedef branch'in fetch edilmiş olması; böylece
origin/<target>mevcut olur. Onu açık bir refspec ile fetch edin:+refs/heads/<target>:refs/remotes/origin/<target>. CI clone'ları remote'un refspec'lerini çoğu zaman pipeline'ın kendi branch'iyle sınırlar (GitLab ve Bitbucket öyle yapar); bu durumda tek başınagit fetch origin <target>yalnızcaFETCH_HEAD'i günceller veorigin/<target>eksik kalır. - Lisans:
TESTDETTA_LICENSEiçinde (maskelenmiş ya da secret bir değişken) ya daTESTDETTA_LICENSE_FILEile belirtilen bir dosyada. GitLab CI ve GitHub Actions'ta açık depolar lisansa ihtiyaç duymaz; Jenkins, Azure DevOps ve Bitbucket bir deponun açık olup olmadığını bildirmez, bu yüzden oralarda her depo lisans gerektirir. Geçerli bir lisans olmadan tüm testler çalışır ve derleme bu yüzden asla başarısız olmaz. Bkz. Lisans. - Çıkış kodları:
0başarılı,1analiz başarısız oldu,2geçersiz komut satırı,3testler başarısız oldu. - Temizlik: eski commit'lerin haritalarına yalnızca 50 commit boyunca ihtiyaç duyulur. Daha eski klasörleri zamanlanmış bir job ya da bir depolama yaşam döngüsü kuralıyla silin.
Kurulum
dotnet tool install --global <package>
Paket adı lansmanda belirlenecek. O zamana kadar TestDetta'yı kaynaktan dotnet build src/TestDetta.Cli -c Release -o testdetta-bin ile derleyin ve aşağıda td geçen her yerde dotnet testdetta-bin/TestDetta.Cli.dll çağırın.
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]
TESTDETTA_LICENSE değişkenini Settings → CI/CD → Variables altında, gerektiği gibi masked ve protected olarak tanımlayın. TestDetta açık projeleri CI_PROJECT_VISIBILITY üzerinden algılar.
Jenkins
Bildirimsel (declarative) bir 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 çoğu zaman remote URL'sinde kimlik bilgileriyle clone yapar. TestDetta bunları lisans kapsamını eşleştirmeden önce çıkarır ve hiçbir zaman loglamaz. Remote, lisansın kapsadığı depoyu adlandırmıyorsa (örneğin yerel bir mirror), TESTDETTA_LICENSE_SCOPE ayarlayın, örneğin 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, self-hosted agent'larda bir paylaşıma işaret eder. Microsoft-hosted agent'larda onu Azure Blob depolamayla senkronize edin (bkz. Nesne depolama).
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
TESTDETTA_LICENSE değişkenini secured bir depo değişkeni olarak tanımlayın.
Paylaşılan disk yerine nesne depolama
Barındırılan (hosted) runner'larda paylaşılan bir disk yoktur. Kayıttan sonra yeni klasörü yükleyin. Test etmeden önce yalnızca kullanılabilecek klasörleri indirin: merge base ve ondan önceki en fazla 50 first-parent commit. Onları en yeniden başlayarak indirin ve mevcut olan ilkinde durun. S3 için bir örnek; cp ve ls komutları olan her araç aynı şekilde çalışır:
#!/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
Harita olmadığında
Kurulumdan sonraki ilk pull request'ler ya da çok geride kalanlar, erişilebilir mesafede bir harita bulamaz. TestDetta bu durumda yalnızca isimlere göre seçer; bu güvenlidir ama daha fazlasını seçer ve bunu logunda belirtir. td record varsayılan branch'te çalıştıktan sonra seçim isabetli hale gelir.