Back to Blog

Testing

February 16, 20264 min
Unit TestingIntegration TestingEnd-to-End Testing
Testing

Testing: Unit, Integration, E2E

Bu bölümde hangi test türünü ne zaman kullanacağımızı ve testlerin sistem kalitesine neden doğrudan etki ettiğini ele alıyoruz. Amaç, en düşük maliyetle en yüksek güveni sağlayan bir test stratejisi kurmaktır.

Neden Test Yazarız?

Testler yalnızca “hata bulmak” için değildir; sürdürülebilir geliştirme için bir güvenlik ağıdır.

  • Bir yeri düzeltirken başka bir yeri bozmayı engeller (regression)
  • Yeni özellik eklediğinde bozulmayı erken yakalar
  • Canlı ortamda hata yakalamak daha maliyetlidir
  • Kodun ne yapması gerektiğini anlatan canlı bir dokümantasyon görevi görür
  • Refactoring sürecini güvenli hale getirir

Test Piramidi

Test stratejisi genellikle “piramid” yaklaşımıyla kurulur:

  • Unit: %60–70 (çok, hızlı)
  • Integration: %20–30 (orta, daha yavaş)
  • E2E: %5–10 (az, en yavaş)

Neden her şeyi E2E ile test etmiyoruz?

  • E2E testler yavaştır
  • kırılgandır (flaky)
  • hata olduğunda kök sebebi net söylemeyebilir
  • bakım maliyeti yüksektir

Bu yüzden ağırlık Unit testlerde olmalı, E2E ise yalnızca kritik akışları kapsamalıdır.

1) Unit Test

Unit test, sistemin en küçük işlevsel birimlerini (fonksiyon, method, class) izole şekilde test eder.

Ne Yapar?

  • Business logic’i izole test eder
  • Dış bağımlılıkları mock’lar (DB, dış API, cache)
  • Milisaniyeler içinde çalışır
  • Çok sayıda yazılabilir ve CI/CD için idealdir

Neden Unit Test Yazarız?

  • Hataları erken yakalar
  • Kodun güvenilirliğini artırır
  • Değişikliklerin yan etkisini azaltır
  • Refactoring’i kolaylaştırır
  • Dokümantasyon niteliği taşır

AAA Döngüsü

  • Arrange: Test verisini ve bağımlılıkları hazırla
  • Act: Test edilen fonksiyonu çalıştır
  • Assert: Beklenen sonucu doğrula

Mocking Nedir?

Mocking, dış bağımlılıkların gerçek implementasyonunu test ortamında kontrol edilebilir sahte nesnelerle değiştirmektir. Böylece test deterministik olur ve dış sistemlerden etkilenmez.

Unit test örneği:

describe('create-category', () => {
  const mockCategory = {
    id: 1,
    name: "cat1",
    description: "this is a category",
    isActive: true,
    slug: "cat.com"
  };

  beforeEach(() => {
    jest.clearAllMocks();
  });

  test('Should create category', async () => {
    CategoryModel.create.mockResolvedValue(mockCategory);

    const result = await CategoryService.createCategory(mockCategory);

    expect(result).toEqual(mockCategory);
    expect(CategoryModel.create).toHaveBeenCalledTimes(1);
  });

  test('Should throw error if create category fails', async () => {
    CategoryModel.create.mockRejectedValue(new Error('Db error'));

    await expect(
      CategoryService.createCategory(mockCategory)
    ).rejects.toThrow('Db error');
  });
});

2) Integration Test

Integration test, birden fazla modülün birlikte doğru çalışıp çalışmadığını test eder.

Ne Yapar?

  • API + DB gibi bileşenlerin birlikte çalışmasını doğrular
  • gerçek query’ler çalışır
  • config, migration, connection, transaction gibi sorunları yakalar
  • unit testten daha yavaştır

Burada kullanılan DB production DB değildir; testler için ayrı bir test DB kullanılır (Docker/test container/in-memory DB gibi).

Örnek (Jest + Supertest):

it('should save user to database and return 201', async () => {
  const response = await request(app)
    .post('/api/users')
    .send({ email: 'test@example.com', password: '123' });

  expect(response.status).toBe(201);

  const user = await User.findOne({ where: { email: 'test@example.com' } });
  expect(user).not.toBeNull();
  expect(user.password).not.toBe('123'); // Hash kontrolü
});

Test Isolation

Integration testlerde testler birbirini etkilememelidir. Bu yüzden:

  • her testten önce/sonra DB temizlenir (truncate)
  • her test kendi verisini üretir
  • testler bağımsız çalışır

3) E2E Test

E2E test, gerçek kullanıcı senaryosunu baştan sona doğrular. Frontend’den başlayıp backend, DB ve gerekiyorsa üçüncü parti servislerin uçtan uca çalışmasını kontrol eder.

Özellikleri

  • En yavaş test türüdür
  • UI değişikliklerinden kolay etkilenir
  • bakım maliyeti yüksektir
  • bu yüzden sadece kritik akışlar test edilir

Kritik akış örnekleri:

  • kayıt olma ve login
  • sepete ekleme ve ödeme
  • şifre sıfırlama

Araç örneği: Cypress

Unit vs Integration Karşılaştırması

BaşlıkUnit Test (Mock)Integration Test (Real DB)
SeviyeFonksiyon / ClassServis / Modül / API
AmaçKod doğru mu?Parçalar birlikte doğru mu?
BağımlılıklarMock/Stubs/FakesGerçek bağımlılıklar
DBYok (mock)Var (test DB)
HızÇok hızlıDaha yavaş
Yakalanan HatalarAlgoritmik / iş mantığıConfig, DB, migration, connection, transaction
CI/CDÇok uygunSeçili adımlarda

Neyi Test Etmeyiz?

  • Third-party kütüphanelerin kendi doğruluğu (ör. bcrypt çalışıyor mu)
  • Framework/dil özellikleri (Express routing, Array.map)
  • Mantık içermeyen boilerplate kod
  • Private helper fonksiyonları tek tek test etmek
  • Sadece getter/setter gibi trivial fonksiyonlar
  • Sadece loglama/print işlemleri