Ana içeriğe geç

Test-Driven Development (TDD)

TDD, önce test yazıp ardından kodu geliştirmeyi esas alır; yanlış uygulama gereksiz testlere ve kırılgan test süitlerine yol açar.


1. Testi Kodu Yazdıktan Sonra Yazmak

Yanlış Kullanım: Önce kodu yazıp sonradan test eklemek.

// Önce kod yazıldı
public class PriceCalculator
{
    public decimal Calculate(decimal price, decimal taxRate)
    {
        return price + (price * taxRate);
    }
}

// Sonra test yazıldı - implementasyona bağımlı, tasarımı yönlendirmedi
[Fact]
public void Calculate_Test()
{
    var calc = new PriceCalculator();
    Assert.Equal(118, calc.Calculate(100, 0.18m));
}

İdeal Kullanım: Red-Green-Refactor döngüsünü takip edin.

// 1. RED: Önce başarısız test yazın
[Fact]
public void Calculate_WithEighteenPercentTax_ReturnsPriceWithTax()
{
    var calculator = new PriceCalculator();

    var result = calculator.Calculate(100, 0.18m);

    Assert.Equal(118m, result);
}

// 2. GREEN: Testi geçirecek minimum kodu yazın
public class PriceCalculator
{
    public decimal Calculate(decimal price, decimal taxRate)
    {
        return price + (price * taxRate);
    }
}

// 3. REFACTOR: Kodu iyileştirin, testler hala geçmeli

2. Çok Büyük Adımlarla İlerlemek

Yanlış Kullanım: Karmaşık senaryoyu tek seferde test etmeye çalışmak.

[Fact]
public void PlaceOrder_WithDiscountAndTaxAndShipping_CalculatesCorrectTotal()
{
    var service = new OrderService();
    var order = new Order
    {
        Items = new List<OrderItem>
        {
            new() { Price = 100, Quantity = 2 },
            new() { Price = 50, Quantity = 1 }
        },
        DiscountCode = "SUMMER20",
        ShippingMethod = "express"
    };

    var result = service.PlaceOrder(order);

    Assert.Equal(236.8m, result.Total); // Çok fazla hesaplama, hata bulmak zor
}

İdeal Kullanım: Küçük adımlarla, her seferinde bir davranışı test edin.

[Fact]
public void CalculateSubtotal_WithMultipleItems_ReturnsSumOfPrices()
{
    var calculator = new OrderCalculator();
    var items = new List<OrderItem>
    {
        new() { Price = 100, Quantity = 2 },
        new() { Price = 50, Quantity = 1 }
    };

    var subtotal = calculator.CalculateSubtotal(items);

    Assert.Equal(250m, subtotal);
}

[Fact]
public void ApplyDiscount_WithTwentyPercent_ReducesSubtotal()
{
    var calculator = new OrderCalculator();

    var discounted = calculator.ApplyDiscount(250m, 0.20m);

    Assert.Equal(200m, discounted);
}

[Fact]
public void CalculateTax_WithEighteenPercent_AddsToPrice()
{
    var calculator = new OrderCalculator();

    var withTax = calculator.CalculateTax(200m, 0.18m);

    Assert.Equal(236m, withTax);
}

3. Edge Case’leri Test Etmemek

Yanlış Kullanım: Sadece happy path test etmek.

[Fact]
public void Divide_TenByTwo_ReturnsFive()
{
    Assert.Equal(5, Calculator.Divide(10, 2));
}
// Sıfıra bölme, negatif sayılar, overflow test edilmemiş

İdeal Kullanım: Edge case’leri de test edin.

[Fact]
public void Divide_TenByTwo_ReturnsFive()
{
    Assert.Equal(5, Calculator.Divide(10, 2));
}

[Fact]
public void Divide_ByZero_ThrowsDivideByZeroException()
{
    Assert.Throws<DivideByZeroException>(() => Calculator.Divide(10, 0));
}

[Fact]
public void Divide_NegativeNumbers_ReturnsPositiveResult()
{
    Assert.Equal(5, Calculator.Divide(-10, -2));
}

[Fact]
public void Divide_ZeroByAny_ReturnsZero()
{
    Assert.Equal(0, Calculator.Divide(0, 5));
}

4. Testleri Birbirine Bağımlı Yazmak

Yanlış Kullanım: Testlerin belirli bir sırada çalışmasını gerektirmek.

private static int _createdUserId;

[Fact]
public void Test1_CreateUser()
{
    var user = _service.Create("Ali");
    _createdUserId = user.Id; // Diğer testler bu ID'ye bağımlı
}

[Fact]
public void Test2_GetUser()
{
    var user = _service.GetById(_createdUserId); // Test1 çalışmadıysa başarısız
    Assert.Equal("Ali", user.Name);
}

İdeal Kullanım: Her testi bağımsız ve izole yazın.

[Fact]
public void Create_WithValidName_ReturnsUserWithId()
{
    var user = _service.Create("Ali");

    Assert.True(user.Id > 0);
    Assert.Equal("Ali", user.Name);
}

[Fact]
public void GetById_WithExistingUser_ReturnsUser()
{
    var created = _service.Create("Veli");

    var found = _service.GetById(created.Id);

    Assert.Equal("Veli", found.Name);
}

5. Refactor Adımını Atlama

Yanlış Kullanım: Green’den sonra refactor yapmadan devam etmek.

public decimal CalculateDiscount(Customer customer, decimal amount)
{
    if (customer.Type == "Premium" && amount > 1000) return amount * 0.20m;
    if (customer.Type == "Premium") return amount * 0.10m;
    if (customer.Type == "Gold" && amount > 1000) return amount * 0.15m;
    if (customer.Type == "Gold") return amount * 0.05m;
    return 0;
    // Testler geçiyor ama kod karmaşık, yeni kural eklemek zor
}

İdeal Kullanım: Testler geçtikten sonra refactor yapın.

// Testler hala geçiyor, kod temiz
public decimal CalculateDiscount(Customer customer, decimal amount)
{
    var baseRate = customer.Type switch
    {
        "Premium" => 0.10m,
        "Gold" => 0.05m,
        _ => 0m
    };

    var bonusRate = amount > 1000 ? 0.10m : 0m;

    return amount * (baseRate + bonusRate);
}