GitHub Actions ile Otomatik Test Çalıştırma
Bir projede testlerin sadece “bende çalışıyor” seviyesinde kalması, ekip büyüdükçe kaosa davet çıkarmak demektir. GitHub Actions ile otomatik test çalıştırma tam da bu noktada devreye giriyor: her push, her pull request, her merge işleminde testlerinizin otomatik koşmasını sağlıyor ve “ben göndermeden önce test ettim” bahanesini ortadan kaldırıyor.
GitHub Actions Nedir ve Neden Test Otomasyonu İçin İdeal?
GitHub Actions, GitHub’ın kendi bünyesinde sunduğu CI/CD platformudur. Ayrı bir servis kurmanıza, Jenkins sunucusu ayağa kaldırmanıza ya da harici bir CI aracına para ödemenize gerek kalmadan, doğrudan repository’niz içinde iş akışları tanımlayabilirsiniz.
Test otomasyonu açısından bakıldığında birkaç kritik avantajı var:
- Sıfır altyapı maliyeti: Public repolar için tamamen ücretsiz, private repolar için aylık 2000 dakika ücretsiz kota
- Paralel çalışma: Aynı anda birden fazla test süitini farklı ortamlarda koşturabilirsiniz
- Matrix builds: Tek bir workflow dosyasıyla Python 3.9, 3.10, 3.11 gibi farklı versiyonları test edebilirsiniz
- Marketplace entegrasyonu: Binlerce hazır action ile hızlıca entegrasyon kurabilirsiniz
- Pull request entegrasyonu: Test sonuçları doğrudan PR üzerinde görünür, yeşil tik olmadan merge engellenebilir
Temel Workflow Yapısı
GitHub Actions workflow’ları .github/workflows/ dizini altındaki YAML dosyalarıyla tanımlanır. Her YAML dosyası bağımsız bir iş akışıdır. İsim seçiminde özgürsünüz ama anlamlı isimler hayat kurtarır: test.yml, ci.yml, python-tests.yml gibi.
En temel bir test workflow’u şu şekilde görünür:
# .github/workflows/test.yml
name: Test Suite
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Kodu checkout et
uses: actions/checkout@v4
- name: Python kur
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Bağımlılıkları yükle
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Testleri çalıştır
run: pytest tests/ -v
Bu dosyayı repository’nize ekledikten sonra her main veya develop branch’ine push yaptığınızda, her main‘e açılan pull request’te testler otomatik koşacaktır.
Tetikleyiciler: Ne Zaman Çalışsın?
on: bloğu workflow’un ne zaman tetikleneceğini belirler. Test otomasyonu için en sık kullanılan tetikleyiciler şunlardır:
- push: Belirtilen branch’lere kod gönderildiğinde
- pull_request: PR açıldığında, güncellendiğinde veya yeniden açıldığında
- schedule: Cron ifadesiyle belirli aralıklarla
- workflow_dispatch: Manuel tetikleme butonu
Gerçek dünya senaryosunda çoğu ekip hem push hem pull_request tetikleyicisini birlikte kullanır. Ama şöyle bir incelik var: sadece belirli dosya değişikliklerinde testlerin koşmasını isteyebilirsiniz. Örneğin dokümantasyon değişikliği testleri tetiklememeli:
on:
push:
branches: [ main, develop ]
paths:
- '**.py'
- 'requirements*.txt'
- '.github/workflows/**'
pull_request:
branches: [ main ]
paths:
- '**.py'
- 'requirements*.txt'
paths filtresi sayesinde sadece Python dosyaları veya bağımlılık dosyaları değiştiğinde workflow tetiklenir. Bu özellikle büyük monorepolarda çalışma süresini ve maliyeti ciddi ölçüde azaltır.
Gece yarısı tam çalışan testler için cron tetikleyicisi de işe yarar:
on:
schedule:
- cron: '0 2 * * *' # Her gece saat 02:00 UTC
push:
branches: [ main ]
Matrix Builds ile Çoklu Ortam Testi
Bir Python kütüphanesi geliştiriyorsunuz ve kullanıcılarınız farklı Python versiyonları kullanıyor. Her versiyonu ayrı ayrı test etmek için matrix strategy kullanabilirsiniz:
name: Python Matrix Tests
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.9', '3.10', '3.11', '3.12']
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Python ${{ matrix.python-version }} kur
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Bağımlılıkları yükle
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Lint kontrolü
run: flake8 src/ tests/
- name: Testleri çalıştır
run: pytest tests/ -v --tb=short
- name: Coverage raporu
run: pytest --cov=src --cov-report=xml
- name: Coverage yükle
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
fail-fast: false ayarı önemli: varsayılan olarak bir matrix kombinasyonu başarısız olursa diğerleri iptal edilir. Bu ayarla tüm kombinasyonların sonucunu görürsünüz.
Node.js Projesi için Gerçek Dünya Senaryosu
Bir e-ticaret uygulaması geliştirdiğinizi düşünün. Frontend React, backend Express.js. Her ikisi için de ayrı test workflow’ları veya tek bir workflow içinde paralel job’lar kullanabilirsiniz:
name: Full Stack CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
backend-tests:
name: Backend Tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Node.js kur
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: backend/package-lock.json
- name: Backend bağımlılıkları yükle
working-directory: ./backend
run: npm ci
- name: Veritabanı migration çalıştır
working-directory: ./backend
env:
DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
run: npm run db:migrate
- name: Unit testleri çalıştır
working-directory: ./backend
env:
DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
REDIS_URL: redis://localhost:6379
JWT_SECRET: test-secret-key
NODE_ENV: test
run: npm test -- --coverage
frontend-tests:
name: Frontend Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Node.js kur
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Frontend bağımlılıkları yükle
working-directory: ./frontend
run: npm ci
- name: Tip kontrolü
working-directory: ./frontend
run: npm run type-check
- name: Unit testleri çalıştır
working-directory: ./frontend
run: npm test -- --watchAll=false --coverage
- name: Build kontrolü
working-directory: ./frontend
run: npm run build
Burada dikkat çekici birkaç nokta var. services: bloğuyla PostgreSQL ve Redis’i sidecar container olarak ayağa kaldırıyoruz. Testler bu gerçek servislerle çalışıyor, mock yerine. health-check parametreleri de kritik: veritabanı tamamen hazır olmadan testler başlamasın diye bekleme mekanizması kuruyoruz.
Bağımlılık Önbellekleme ile Hızlanma
Her workflow çalışmasında npm veya pip paketlerini sıfırdan indirmek hem zaman hem de bant genişliği israfıdır. Önbellekleme şart:
- name: pip önbelleği
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Bağımlılıkları yükle
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
hashFiles fonksiyonu requirements dosyasının hash’ini key’e ekler. Dosya değişmezse önbellek kullanılır, değişirse yeni önbellek oluşturulur. Bu basit optimizasyon 3-4 dakikalık workflow’ı 1-2 dakikaya indirebilir.
Secrets ile Güvenli Ortam Değişkenleri
API anahtarları, veritabanı şifreleri ve diğer hassas bilgileri asla YAML dosyasına yazmayın. GitHub’ın Secrets özelliğini kullanın.
Repository ayarlarından Settings > Secrets and variables > Actions yolunu izleyerek secret ekleyebilirsiniz. Sonra workflow’da şöyle kullanırsınız:
- name: Entegrasyon testleri
env:
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: pytest tests/integration/ -v -m "integration"
Organizasyon seviyesinde secret tanımlayarak birden fazla repository’de paylaşım da yapabilirsiniz. Özellikle mikro servis mimarilerinde bu özellik hayat kurtarır.
Test Sonuçlarını Pull Request’e Yansıtma
GitHub Actions’ın en güzel özelliklerinden biri test sonuçlarını PR üzerinde görünür kılmak. JUnit XML formatındaki test çıktıları doğrudan GitHub arayüzüne entegre olabiliyor:
- name: Testleri çalıştır
run: |
pytest tests/
--junitxml=test-results/junit.xml
--cov=src
--cov-report=xml:coverage.xml
--cov-report=html:coverage-html
-v
continue-on-error: true
- name: Test sonuçlarını yayınla
uses: dorny/test-reporter@v1
if: always()
with:
name: pytest Results
path: test-results/junit.xml
reporter: java-junit
fail-on-error: true
- name: Coverage artefaktını yükle
uses: actions/upload-artifact@v3
if: always()
with:
name: coverage-report
path: coverage-html/
retention-days: 7
if: always() koşulu önemli: testler başarısız olsa bile sonuç yayınlama adımı çalışsın istiyoruz. continue-on-error: true ise test adımının başarısız olmasının workflow’u hemen durdurmasını engelliyor, önce sonuçları yayınlıyoruz sonra genel durumu değerlendiriyoruz.
Coverage Eşiği ile Kalite Kapısı
Test coverage’ı sadece raporlamak yetmez, belirli bir eşiğin altına düşünce build’i kırmak gerekir. Bu sayede coverage zamanla erimez:
- name: Coverage kontrolü
run: |
coverage run -m pytest tests/
coverage report --fail-under=80
coverage xml
- name: Coverage badge güncelle
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: schneegans/[email protected]
with:
auth: ${{ secrets.GIST_SECRET }}
gistID: your-gist-id-here
filename: coverage-badge.json
label: coverage
message: ${{ env.COVERAGE_PERCENT }}%
color: ${{ env.BADGE_COLOR }}
--fail-under=80 parametresi coverage %80’in altına düşerse exit code 1 döner ve workflow başarısız olur. Bu değeri projenizin olgunluğuna göre ayarlayın, başta %60-70’ten başlayıp zamanla artırın.
Branch Protection ile Zorunlu Testler
Workflow’ları yazdınız, testler çalışıyor. Ama geliştiriciler yeşil tik beklemeden merge edebiliyorsa tüm bu çaba yarım kalır. Repository ayarlarından branch protection rules ekleyin:
Settings > Branches > Add branch protection ruleRequire status checks to pass before mergingseçeneğini aktif edin- Zorunlu check olarak workflow job’larınızı seçin
Bu ayar yapıldıktan sonra testler başarısız olan PR’lar merge edilemez. Ekip liderinin “bak işte yeşil olacak, sonra merge edersin” demesine gerek kalmaz.
Paralel Test Çalıştırma ve Sharding
Test süitiniz büyüdükçe çalışma süresi de uzar. 500 test için 5 dakika kabul edilebilir, ama 5000 test için 50 dakika beklenmez. Test sharding ile testleri paralele bölebilirsiniz:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- name: Python kur
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Bağımlılıkları yükle
run: pip install -r requirements-dev.txt
- name: Test shard ${{ matrix.shard }} çalıştır
run: |
pytest tests/
--shard-id=${{ matrix.shard }}
--num-shards=4
--junitxml=results-${{ matrix.shard }}.xml
-v
- name: Test sonuçlarını yükle
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results-shard-${{ matrix.shard }}
path: results-${{ matrix.shard }}.xml
merge-results:
needs: test
runs-on: ubuntu-latest
if: always()
steps:
- name: Sonuçları indir
uses: actions/download-artifact@v3
- name: Sonuçları birleştir ve kontrol et
run: |
# Tüm shard sonuçlarını kontrol et
for file in test-results-shard-*/results-*.xml; do
if grep -q 'failures="[^0]"|errors="[^0]"' "$file"; then
echo "HATA: $file içinde başarısız testler var"
exit 1
fi
done
echo "Tüm shardlar başarılı!"
pytest-shard eklentisiyle testleri eşit parçalara bölebilirsiniz. 4 paralel runner ile teorik olarak 4 kat hızlanma sağlanır.
Workflow Debugging İpuçları
Workflow’lar bazen sürpriz davranışlar sergileyebilir. Birkaç debug tekniği:
- name: Debug ortam bilgileri
run: |
echo "Runner OS: ${{ runner.os }}"
echo "GitHub ref: ${{ github.ref }}"
echo "Event name: ${{ github.event_name }}"
python --version
pip list
env | sort
- name: SSH debug (sadece hata durumunda)
uses: mxschmitt/action-tmate@v3
if: ${{ failure() && inputs.debug_enabled }}
timeout-minutes: 30
action-tmate özellikle güçlü bir araç. Workflow başarısız olduğunda runner’a SSH bağlantısı açıyor, canlı olarak inceleyebiliyorsunuz. Production’da kullanmayın ama sorun gidermede altın değerinde.
Gerçek Dünya Sorunları ve Çözümleri
Sahada en sık karşılaşılan birkaç senaryo:
Flaky testler: Bazen geçip bazen başarısız olan testler. --reruns ile otomatik yeniden deneme:
- name: Testleri çalıştır (yeniden deneme ile)
run: pytest tests/ --reruns=3 --reruns-delay=2 -v
Timeout sorunları: Uzun süren testler runner timeout’una takılabilir. Job seviyesinde timeout ayarlayın:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
Disk alanı: Büyük Docker image’ları veya build artefaktları runner diskini doldurabiliyor. Gereksiz şeyleri temizleyin:
- name: Docker cache temizle
run: docker system prune -f
Sonuç
GitHub Actions ile otomatik test altyapısı kurmak, bir kerelik yatırımla uzun vadede büyük getiri sağlar. “Bende çalışıyor” kültüründen “main’e merge edildiyse çalışıyordur” güvenine geçmek ciddi bir zihinsel rahatlama sağlar.
Başlangıç için önerilen sıra şöyle: önce temel bir workflow ile testlerinizi GitHub’da koşturun, sonra önbellekleme ekleyip hızlandırın, ardından branch protection ile zorunlu hale getirin. Matrix builds ve sharding gibi ileri özellikler proje büyüdükçe devreye girer.
En önemli nokta şu: mükemmel bir CI pipeline’ı baştan kurmak zorunda değilsiniz. Küçük başlayın, her sprint biraz daha iyileştirin. Test olmayan ama çalışan bir pipeline, mükemmel ama hiç tamamlanmayan bir tasarımdan her zaman daha değerlidir.
