Unit Test Nedir Ne Değildir?

Unit Test Nedir Ne Değildir?

Unit test nedir? — Çok detaylı açıklama (ve ne olmadığı)

Aşağıda unit test (ünit testi) kavramını derinlemesine, örneklerle ve “ne olmadığı” sorusuna özellikle odaklanarak anlattım. Hem teorik hem pratik (C#/.NET) noktalarına değindim.


Kısa tanım

Unit test: Bir yazılımın en küçük bağımsız birimi (genelde bir metot ya da sınıf) üzerinde, dış bağımlılıklardan izole edilmiş şekilde çalışan, deterministik (her çalıştırmada aynı sonucu veren), hızlı ve küçük kapsamlı test. Amaç: tek bir birimin davranışının doğru olduğunu kanıtlamak.


Unit test’in ayırt edici özellikleri

  • İzole: Test edilen birim haricindeki tüm dış bağımlılıklar (DB, dosya sistemi, ağ, zaman, 3rd-party servisler) test double’larla (mock, stub, fake) gizlenir.
  • Küçük kapsam: Tek bir sorumluluk / davranış kontrol edilir.
  • Hızlı: Milisaniye–saniye seviyesinde çalışmalı; CI’de çok sayıda test çalıştırılabilir olmalı.
  • Deterministik: Rastgelelik, zaman, paralellik, dış servislere bağlılık gibi öğeler sonucu etkilememeli.
  • Tek bir davranış / beklenen sonuç: Arrange–Act–Assert (AAA) pattern’i takip edilir.
  • Otomatik: dotnet test, CI (GitHub Actions, Azure DevOps vb.) ile otomatik çalıştırılmaya uygun.

Unit test değildir — (çok önemli: ne olmadığı)

Unit test’i anlamak için “ne olmadığını” bilmek kritik.

  1. Integration test (entegrasyon testi) değildir

    • Integration test, birimlerin birlikte doğru çalışıp çalışmadığını, gerçek bağımlılıklar (DB, HTTP servis, message broker) ile doğrular. Unit test bu gerçek bağımlılıkları kullanmaz; izolasyon esastır.
  2. End-to-End (E2E) veya system test değildir

    • Tarayıcı simülasyonu, kullanıcı akışları, UI ve back-end entegrasyonu E2E kapsamına girer. Unit test kullanıcı etkileşimini veya UI’yi test etmez.
  3. Performance / Load test değildir

    • Yük, stres, performans metrikleri; büyük veriler, concurrency senaryoları test edilmez. Unit test performans sorunlarını yakalamaz (istisnai davranışları erken yakalatır ama geniş performans ölçümleri için yetersizdir).
  4. Security / Penetration test değildir

    • Açık tarama, sert saldırılar, yetki kontrolü testleri, fuzzing, vb. unit testlerle yapılmaz.
  5. Chaos/Resilience test değildir

    • Kaos testleri (fault-injection, simüle edilen altyapı kopmaları) altyapı/entegre senaryolarda uygulanır; unit testler bu düzeye girmez.
  6. Dokümantasyonun yerine geçmez

    • Unit testler kod davranışı hakkında iyi bir belge olabilir ama tek başına kullanıcı gereksinim dokümanının, mimari belgelerin veya acceptance kriterlerinin yerine geçmez.
  7. Kod kalitesi veya tasarım yerine tek başına çözüm değildir

    • Unit testler iyi tasarımı teşvik eder ama kodun okunaklılığı, performansı, güvenliği vs. başka süreçler gerektirir.
  8. Tüm hataları yakalamaz

    • Birim testleri fonksiyonel hataların belirli sınıflarını yakalar; sistem-entegrasyon hatalarını kaçırabilir.
  9. White-box test olarak private metodları test etme gerekçesi değildir

    • Private metotları doğrudan test etmek genelde antipattern. Public API üzerinden davranışı test etmek tercih edilir.

Unit test ile diğer test türlerinin karşılaştırması (özet)

  • Unit: küçük, izole, hızlı, deterministik.
  • Integration: birimlerin entegrasyonu, gerçek bağımlılıklar, daha yavaş.
  • E2E/System: kullanıcı akışları, tüm sistem, en yavaş en pahalı.
  • Performance/Load: ölçeklenebilirlik, gecikme, throughput.
  • Security/Chaos: dayanıklılık, saldırılara karşı güvenlik, hata toleransı.

(Test piramidi: çok sayıda unit, daha az integration, daha az E2E.)


Unit test yazarken dikkat edilmesi gereken prensipler / iyi uygulamalar

  1. AAA (Arrange / Act / Assert) yapısını kullan:

    • Arrange: test verileri, mock hazırlıkları.
    • Act: metodu çağır.
    • Assert: beklenen sonucu doğrula.
  2. Her test tek bir davranışı doğrulasın

    • Bir testin tek bir mantıksal iddiası (expectation) olmalı.
  3. İsimlendirme

    • Pattern önerisi: MethodName_StateUnderTest_ExpectedBehavior
      Örnek: PlaceOrder_WhenInventorySufficient_ReturnsTrue.
  4. Testleri deterministik yap

    • Zaman, rastgele sayı, ortam değişkenleri, paralellik gibi bağımlılıklardan kaçın.
    • Zaman için IDateTimeProvider kullan ve gerçek DateTime.Now’u mock’la.
  5. Gerçek dışı (slow) kaynakları mockla / fake’le

    • DB, HTTP, file system, network, message queue gerçek olarak kullanılmasın.
  6. Dependency Injection (DI) kullan

    • Sınıfları test edilebilir yapmak için constructor injection tercih et.
  7. Test double’ları doğru kullan

    • Stub: belirli veri/cevap döndürür.
    • Mock: davranış doğrulama (verify çağrısı).
    • Fake: basit çalışan implementasyon (ör. in-memory DB).
    • Spy: çağrıları kayıt eden test double.
  8. Small & Fast

    • Her test hızlı çalışmalı; CI’de sürekli çalıştırılmalı.
  9. Test verisini yönet

    • Inline veri küçük senaryolar için, parametrik testler ([Theory] + [InlineData]) tekrarları azaltır.
    • Karmaşık test verisi için builder veya AutoFixture kullan.
  10. Assertions için zengin kütüphaneler

    • Daha anlaşılır hata mesajları için FluentAssertions, Shouldly gibi kütüphaneler faydalıdır.
  11. Kod kapsamı (code coverage) akılcı kullanılmalı

    • Yüksek coverage hedefi iyi, ama %100 otomatik hedef olmamalı. Önemli olan “anlamlı” testler.
  12. Refactor korkusunu azalt

    • İyi unit test yazımı refactor’u güvenli kılar.

C#/.NET için pratik örnek — very small, çalışır durumda

Prod kodu (örnek):

// src/MyApp/Services/IInventory.cs
public interface IInventory
{
    bool Reserve(string sku, int quantity);
}

// src/MyApp/Services/OrderService.cs
public class OrderService
{
    private readonly IInventory _inventory;
    public OrderService(IInventory inventory) => _inventory = inventory;

    public bool PlaceOrder(string sku, int quantity)
    {
        if (string.IsNullOrWhiteSpace(sku)) throw new ArgumentException(nameof(sku));
        if (quantity <= 0) throw new ArgumentException(nameof(quantity));

        var reserved = _inventory.Reserve(sku, quantity);
        return reserved;
    }
}

Unit test (xUnit + Moq):

// tests/MyApp.UnitTests/OrderServiceTests.cs
using Xunit;
using Moq;

public class OrderServiceTests
{
    [Fact]
    public void PlaceOrder_WhenInventoryReserves_ReturnsTrue()
    {
        // Arrange
        var inventoryMock = new Mock<IInventory>();
        inventoryMock.Setup(x => x.Reserve("sku-1", 2)).Returns(true);
        var svc = new OrderService(inventoryMock.Object);

        // Act
        var result = svc.PlaceOrder("sku-1", 2);

        // Assert
        Assert.True(result);
        inventoryMock.Verify(x => x.Reserve("sku-1", 2), Times.Once);
    }

    [Theory]
    [InlineData(null, 1)]
    [InlineData("", 1)]
    [InlineData("sku-1", 0)]
    public void PlaceOrder_InvalidArguments_Throws(string sku, int qty)
    {
        var inventoryMock = new Mock<IInventory>();
        var svc = new OrderService(inventoryMock.Object);

        Assert.Throws<ArgumentException>(() => svc.PlaceOrder(sku, qty));
    }
}

Çalıştırma

  • Test projesini oluşturduktan sonra: dotnet test ./tests/MyApp.UnitTests/MyApp.UnitTests.csproj

Örnek proje / klasör yapısı (öneri)

/src
  /MyApp
    /Services
      OrderService.cs
    MyApp.csproj
/tests
  /MyApp.UnitTests
    OrderServiceTests.cs
    MyApp.UnitTests.csproj

Sık yapılan hatalar / anti-pattern’ler

  • Gerçek DB/HTTP kullanan unit testler → Flaky ve yavaş.
  • Test içinde Thread.Sleep → Flaky. Bekleme yerine event/wait-handle, condition polling (timeout ile) veya mock kullan.
  • Aşırı mocking: Her şeyi mock’lamak testin anlamsız olmasına yol açabilir. Sınırlı sayıda mock, gerçek in-memory fakes tercih edilebilir.
  • Test bağımlılığı: Testler birbirine bağlı olmamalı; sıradan bağımlı testler kırıldığında zincirleme sorun olur.
  • Testler çok büyük / çok fazla assert: Okunması zor, yönetimi zor.
  • Private method’ları test etmeye çalışmak: Genelde public API üzerinden test et.

Ne zaman Integration/Test kullanmalı? (pratik rehber)

  • Birim dışında veri tutarlılığı, transaction, ORM mapping, SQL sorguları, veya dış servislerin gerçek davranışını doğrulamak istiyorsanız integration test yazın.
  • Unit testler iş mantığını doğrularken; veri erişimi, migration, schema değişiklikleri için integration testler gerekir.

İleri teknikler (özellikle büyük projelerde faydalı)

  • Property-based testing (FsCheck) — rastgele örneklerle invariants doğrulama.
  • Mutation testing (Stryker.NET) — testlerin gerçek çarpıcılığını ölçer.
  • Contract testing (Pact) — servisler arası sözleşmelerin korunması.
  • Test doubles fabrikaları / builders / AutoFixture — test veri üretimini kolaylaştırır.
  • Test categorizations: unit, integration, smoke gibi kategorilere ayırıp CI’de farklı pipeline’lar kullanın.

Özet — Unit test’in özüne indirgeme

  • Unit test: tek bir küçük birimi izole şekilde doğrulayan, hızlı, deterministik testtir.
  • Unit test değildir: sistemin tamamını, performansı, güvenliği veya entegrasyon davranışını doğrulayan testler.
  • En iyi sonuç: unit + integration + E2E kombinasyonu (test piramidi) ile elde edilir.

admin

Leave a Reply

Your email address will not be published. Required fields are marked *