Regression Test Nedir ve Nasıl Otomatize Edilir?

Bir sabah işe geliyorsunuz, ekibiniz dün gece yeni bir özellik deploy etti, her şey yolunda görünüyor. Ama öğleden sonra müşteri desteği aramaya başlıyor: “Ödeme sayfası çalışmıyor.” Bakıyorsunuz, yeni eklenen bir fonksiyon, eskiden mükemmel çalışan bir modülü kırmış. İşte tam bu an, regression testinin neden var olduğunu anlıyorsunuz.

Regression Test Nedir?

Regression test, yazılıma yeni bir değişiklik yapıldıktan sonra mevcut işlevselliğin hâlâ doğru çalıştığını doğrulamak için yapılan test sürecidir. “Regresyon” kelimesi, sistemin daha önce çalışan bir duruma geri dönmesi anlamına gelir. Yani eski ve düzgün çalışan şeylerin yeni değişikliklerden zarar görüp görmediğini kontrol ederiz.

Bu test türü özellikle şu durumlarda kritik öneme sahiptir:

  • Yeni özellik ekleme sonrasında
  • Bug fix yapıldığında (bir hatayı düzeltirken başka bir hatayı açabilirsiniz)
  • Refactoring işlemlerinde
  • Kütüphane veya bağımlılık güncellemelerinde
  • Veritabanı şema değişikliklerinde

Manuel regression testi küçük projelerde bile birkaç günü alabilir. Orta ölçekli bir uygulamada ise bu süre haftaları bulur. Bu yüzden otomatizasyon burada sadece kolaylık değil, zorunluluktur.

Otomatik Regression Testin Temelleri

Regression testini otomatize etmeden önce neyin test edileceğine karar vermeniz gerekiyor. Her şeyi test etmeye çalışmak hem maliyetli hem de sürdürülemez. Pratikte şu piramit mantığını benimsiyoruz:

  • Unit testler: En alt katman, en hızlı çalışan, en çok yazılan
  • Integration testler: Bileşenler arası etkileşimi test eder
  • End-to-end testler: Kullanıcı akışlarını simüle eder, en yavaş

Regression testleri genellikle bu üç katmana da dokunur ama asıl odak, kritik kullanıcı akışları ve daha önce hata çıkmış noktalardır.

Test Ortamı Kurulumu

Gerçek dünyada test ortamı kurulumu çoğu zaman testlerin kendisinden daha zor olur. Docker ile izole bir ortam kurmak iyi bir başlangıç noktasıdır:

# docker-compose.test.yml
version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.test
    environment:
      - NODE_ENV=test
      - DB_HOST=testdb
      - REDIS_HOST=testredis
    depends_on:
      - testdb
      - testredis

  testdb:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: testuser
      POSTGRES_PASSWORD: testpass
    tmpfs:
      - /var/lib/postgresql/data

  testredis:
    image: redis:7-alpine
    tmpfs:
      - /data

tmpfs kullanımına dikkat edin. Test veritabanını disk yerine RAM’de çalıştırmak testleri ciddi ölçüde hızlandırır ve her test başlangıcında temiz bir slate sağlar.

Python ile Regression Test Yazma

Python projelerinde pytest ekosistemi regression testler için oldukça güçlüdür. Basit bir API regression test örneği:

# requirements-test.txt
pytest==7.4.0
pytest-asyncio==0.21.0
httpx==0.25.0
pytest-postgresql==5.0.0
factory-boy==3.3.0
# tests/regression/test_payment_flow.py
import pytest
import httpx

BASE_URL = "http://localhost:8000"

@pytest.fixture(autouse=True)
def reset_db(postgresql):
    """Her testten önce veritabanını temizle"""
    yield
    postgresql.execute("TRUNCATE TABLE orders, payments CASCADE")
    postgresql.commit()

class TestPaymentRegression:
    """Ödeme akışı regression testleri"""

    def test_payment_completes_after_cart_update(self, client):
        """
        Bug #1247: Sepet güncellemesi sonrası ödeme başarısız oluyordu.
        Bu test o bug'ın regrese etmediğini garantiler.
        """
        # Sepet oluştur
        cart_response = client.post("/api/cart", json={
            "user_id": "test-user-001",
            "items": [{"product_id": "prod-1", "quantity": 2}]
        })
        assert cart_response.status_code == 201
        cart_id = cart_response.json()["cart_id"]

        # Sepeti güncelle (bug'ın tetiklendiği nokta)
        update_response = client.put(f"/api/cart/{cart_id}", json={
            "items": [{"product_id": "prod-1", "quantity": 3}]
        })
        assert update_response.status_code == 200

        # Ödeme yap - eskiden burada 500 dönüyordu
        payment_response = client.post("/api/payments", json={
            "cart_id": cart_id,
            "payment_method": "credit_card",
            "card_token": "tok_test_visa"
        })
        assert payment_response.status_code == 200
        assert payment_response.json()["status"] == "completed"

Testin içine yazdığım yoruma dikkat edin: hangi bug’ı önlediğini ve ticket numarasını belirtin. Altı ay sonra bu testi gören bir ekip arkadaşınız context’i anlayabilsin.

Shell Script ile Smoke Test Otomasyonu

Özellikle legacy sistemlerde veya polyglot ortamlarda, dil-agnostik bir yaklaşım olarak shell script tabanlı regression testler gayet işe yarar:

#!/bin/bash
# regression_smoke_test.sh

set -euo pipefail

BASE_URL="${APP_URL:-http://localhost:8080}"
FAILED_TESTS=0
PASSED_TESTS=0

log_pass() { echo "✓ PASS: $1"; ((PASSED_TESTS++)); }
log_fail() { echo "✗ FAIL: $1"; ((FAILED_TESTS++)); }

assert_http_status() {
    local description="$1"
    local expected_status="$2"
    local url="$3"
    local method="${4:-GET}"
    local body="${5:-}"

    actual_status=$(curl -s -o /dev/null -w "%{http_code}" 
        -X "$method" 
        -H "Content-Type: application/json" 
        ${body:+-d "$body"} 
        "$url")

    if [ "$actual_status" -eq "$expected_status" ]; then
        log_pass "$description (HTTP $actual_status)"
    else
        log_fail "$description (Beklenen: $expected_status, Gelen: $actual_status)"
    fi
}

# Temel endpoint testleri
assert_http_status "Health check endpoint" 200 "$BASE_URL/health"
assert_http_status "API versiyonu endpoint" 200 "$BASE_URL/api/v1/version"
assert_http_status "Yetkisiz erişim reddi" 401 "$BASE_URL/api/v1/users/profile"
assert_http_status "Olmayan kaynak 404 döner" 404 "$BASE_URL/api/v1/nonexistent"

# POST endpoint testi
assert_http_status "Login endpoint çalışıyor" 200 
    "$BASE_URL/api/v1/auth/login" 
    "POST" 
    '{"username":"testuser","password":"testpass123"}'

echo ""
echo "Sonuç: $PASSED_TESTS geçti, $FAILED_TESTS başarısız"

[ "$FAILED_TESTS" -eq 0 ] || exit 1

Bu scripti production deploy öncesi ve sonrası çalıştırmak, temel işlevselliğin ayakta olduğunu hızla doğrular.

CI/CD Pipeline’a Entegrasyon

Regression testlerin gerçek değeri, her kod değişikliğinde otomatik olarak çalıştırıldığında ortaya çıkar. GitLab CI örneği:

# .gitlab-ci.yml
stages:
  - build
  - unit-test
  - integration-test
  - regression-test
  - deploy

variables:
  POSTGRES_DB: testdb
  POSTGRES_USER: testuser
  POSTGRES_PASSWORD: testpass

regression-tests:
  stage: regression-test
  image: python:3.11-slim
  services:
    - name: postgres:15-alpine
      alias: testdb
    - name: redis:7-alpine
      alias: testredis
  before_script:
    - pip install -r requirements-test.txt
    - python manage.py migrate --settings=config.test_settings
    - python manage.py loaddata tests/fixtures/regression_baseline.json
  script:
    - pytest tests/regression/ 
        --tb=short 
        --junitxml=reports/regression-results.xml
        -v
        --timeout=120
  artifacts:
    when: always
    reports:
      junit: reports/regression-results.xml
    expire_in: 30 days
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == "main"'
  allow_failure: false

allow_failure: false satırı kritik. Regression test başarısız olursa pipeline durmalı ve merge engellenmelidir. Bazı ekipler bunu esnekleştirmek ister, ama bu kötü bir alışkanlık.

Test Verisi Yönetimi

Regression testlerin en sık tökezlediği yer test verisi yönetimidir. Sabit (hardcoded) veri kullanan testler zamanla kırılganlaşır. Factory pattern kullanın:

# tests/factories.py
import factory
from factory.django import DjangoModelFactory
from decimal import Decimal
import random

class UserFactory(DjangoModelFactory):
    class Meta:
        model = 'accounts.User'

    username = factory.Sequence(lambda n: f'testuser_{n}')
    email = factory.LazyAttribute(lambda obj: f'{obj.username}@example.com')
    is_active = True

class ProductFactory(DjangoModelFactory):
    class Meta:
        model = 'catalog.Product'

    name = factory.Faker('product_name', locale='tr_TR')
    price = factory.LazyFunction(
        lambda: Decimal(str(round(random.uniform(10.0, 1000.0), 2)))
    )
    stock = factory.LazyFunction(lambda: random.randint(1, 100))
    is_available = True

class OrderFactory(DjangoModelFactory):
    class Meta:
        model = 'orders.Order'

    user = factory.SubFactory(UserFactory)
    status = 'pending'
    total_amount = factory.LazyFunction(
        lambda: Decimal(str(round(random.uniform(50.0, 5000.0), 2)))
    )

# Kullanım
# tests/regression/test_order_regression.py
def test_order_status_transitions():
    order = OrderFactory(status='pending')
    # test devam eder...

Paralel Test Çalıştırma ve Hız Optimizasyonu

Regression test suitleri büyüdükçe çalışma süresi uzar. 300 testin 20 dakika sürmesi, geliştiricilerin testi atlamasına yol açar. pytest-xdist ile paralel çalıştırın:

# Mevcut CPU sayısına göre otomatik worker belirleme
pytest tests/regression/ -n auto --dist=worksteal

# Belirli worker sayısı ile
pytest tests/regression/ -n 4

# Yavaş testleri profillemek için
pytest tests/regression/ --durations=10

Paralel test çalıştırmada dikkat edilmesi gereken en önemli nokta test izolasyonudur. Testler birbirinin verisine dokunmamalıdır. Bu yüzden her test için unique ID’ler ve transaction rollback kullanın:

# conftest.py
import pytest
from django.test import TestCase

@pytest.fixture(scope='function')
def db_transaction(db):
    """
    Her test fonksiyonu için transaction başlat ve rollback yap.
    Paralel testlerde data isolation sağlar.
    """
    from django.db import transaction
    with transaction.atomic():
        savepoint = transaction.savepoint()
        yield
        transaction.savepoint_rollback(savepoint)

Gerçek Dünya Senaryosu: Legacy Sistem Regression Suite

Hiç sıfırdan regression suite kurmak zorunda kaldınız mı? Gerçekten zorlu bir süreç. Bir e-ticaret sisteminde bunu nasıl yaptığımızı paylaşayım.

Önce mevcut sistemi kara kutu olarak ele alıp kritik akışları belgeledik. Sonra bu akışları API seviyesinde test ettik:

#!/bin/bash
# scripts/capture_baseline.sh
# Mevcut sistemin davranışını baseline olarak kaydet

BASELINE_DIR="tests/baselines"
API_URL="http://production-readonly.internal"

mkdir -p "$BASELINE_DIR"

endpoints=(
    "/api/v1/products?category=electronics&limit=10"
    "/api/v1/products/featured"
    "/api/v1/categories"
    "/api/v1/config/checkout"
)

for endpoint in "${endpoints[@]}"; do
    filename=$(echo "$endpoint" | sed 's/[^a-zA-Z0-9]/_/g' | sed 's/__*/_/g')
    echo "Capturing: $endpoint"
    curl -s "$API_URL$endpoint" | python3 -m json.tool > "$BASELINE_DIR/${filename}.json"
    echo "Saved to: $BASELINE_DIR/${filename}.json"
done

echo "Baseline capture tamamlandı: $BASELINE_DIR"

Sonra bu baseline’ları regression testlerde referans olarak kullandık. Yeni deploy sonrası aynı endpointlerin response’larını baseline ile karşılaştırdık. Response schema değişirse alarm çalıştı.

Flaky Test Sorunu

Regression testlerin en sinir bozucu düşmanı “flaky test”lerdir. Bazen geçer, bazen geçmez. Sebebini bulmak saatler alabilir. Genellikle şu sebeplerden kaynaklanır:

  • Race condition (asenkron işlemlerde bekleme eksikliği)
  • Hardcoded zaman değerleri (time.sleep(2) gibi)
  • Test sırası bağımlılığı
  • Dış servis bağımlılığı

Flaky testleri tespit etmek için:

# Aynı testi 10 kez çalıştır, kaçı geçiyor?
pytest tests/regression/test_payment_flow.py 
    --count=10 
    -v 
    2>&1 | grep -E "(PASSED|FAILED|ERROR)"

# pytest-rerunfailures ile flaky testleri geçici olarak yönet
# ama asıl sorunu mutlaka düzelt
pytest tests/regression/ --reruns=2 --reruns-delay=1

Flaky test bulduğunuzda hemen xfail veya skip ile işaretleyin ama bir ticket açın ve takip edin. “Zaten bazen geçiyor” diyerek bırakmayın.

Test Sonuçlarını İzleme ve Raporlama

Regression testlerin ne zaman, neden başarısız olduğunu takip etmek önemlidir. Sadece “geçti/geçmedi” değil, trend analizi yapın:

# pytest-html ile görsel rapor
pytest tests/regression/ 
    --html=reports/regression_$(date +%Y%m%d_%H%M%S).html 
    --self-contained-html

# Allure ile daha detaylı raporlama
pytest tests/regression/ --alluredir=./allure-results
allure generate ./allure-results -o ./allure-report --clean
allure open ./allure-report

Allure raporları özellikle ekibe regression test durumunu sunmak için çok değerlidir. Hangi test suitelarının ne kadar sürdüğünü, geçmiş run’larla karşılaştırmalı başarı oranlarını görsel olarak sunabilirsiniz.

Regression Test Yazarken Yapılan Yaygın Hatalar

Uzun yıllar boyunca pek çok ekipte gördüğüm hatalar:

  • Çok geniş scope: “Her şeyi test edelim” yaklaşımı. Önce kritik path’leri belirleyin.
  • Test bağımlılığı: A testi B testinin çıktısına bakıyor. Her test bağımsız çalışabilmeli.
  • Yavaş testleri görmezden gelme: 5 dakika süren bir test kimse tarafından sık çalıştırılmaz.
  • Başarısız testleri skip etme: “Şimdi vakti yok” derken teknik borç birikir.
  • Sadece happy path testi: Edge case’leri, hata durumlarını ve sınır değerlerini test edin.
  • Test ortamını production’dan farklı tutma: Test geçiyor ama production’da patlıyor. Ortam farkı genellikle sebeptir.

Sonuç

Regression testi, “bu yine bozuldu mu” sorusunu sistematik ve güvenilir bir şekilde cevaplamanın tek yoludur. Başlangıçta test yazmak zaman alır, doğru. Ama bir kez kurulduğunda, her deployment’ta saatler kazandırır ve geceleri rahat uyumanızı sağlar.

Sıfırdan başlıyorsanız önerin şu olsun: dün müşteriden gelen ilk complaint’i, dün production’da kapattığınız ilk bug’ı alın ve onun için bir regression testi yazın. Sonra bir daha, bir daha. Bir ay sonra bakarsınız, farkında olmadan solid bir suite oluşmuş.

Otomatizasyon tarafında da mükemmeli beklemeyin. CI’a bağlamak için beş dakika yetersizse, en azından her sabah manuel tetiklenebilir hale getirin. Sonra CI’a taşırsınız. Süreç iteratif.

En iyi regression test, yazılmış ve çalışan olandır.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir