Türkçe

JavaScript'te Test Odaklı Geliştirmede (TDD) ustalaşın. Bu rehber, Kırmızı-Yeşil-Yeniden Düzenle döngüsünü, Jest ile uygulamayı ve en iyi pratikleri kapsar.

JavaScript'te Test Odaklı Geliştirme: Global Geliştiriciler için Kapsamlı Bir Rehber

Şöyle bir senaryo hayal edin: büyük, eski bir sistemdeki kritik bir kod parçasını değiştirmekle görevlendirildiniz. Bir korku hissi sizi kaplıyor. Yaptığınız değişiklik başka bir şeyi bozar mı? Sistemin hala amaçlandığı gibi çalıştığından nasıl emin olabilirsiniz? Bu değişim korkusu, yazılım geliştirmede sıkça rastlanan bir rahatsızlıktır ve genellikle yavaş ilerlemeye ve kırılgan uygulamalara yol açar. Peki ya yazılımı güvenle oluşturmanın, hataları daha üretime ulaşmadan yakalayan bir güvenlik ağı yaratmanın bir yolu olsaydı? İşte bu, Test Odaklı Geliştirme'nin (TDD) vaadidir.

TDD yalnızca bir test tekniği değildir; yazılım tasarımına ve geliştirmeye yönelik disiplinli bir yaklaşımdır. Geleneksel "önce kodu yaz, sonra test et" modelini tersine çevirir. TDD ile, üretim kodunu yazmadan önce başarısız olan bir test yazarsınız. Bu basit tersine çevirmenin kod kalitesi, tasarım ve sürdürülebilirlik üzerinde derin etkileri vardır. Bu rehber, profesyonel geliştiricilerden oluşan küresel bir kitle için tasarlanmış, JavaScript'te TDD uygulamasının kapsamlı ve pratik bir incelemesini sunacaktır.

Test Odaklı Geliştirme (TDD) Nedir?

Özünde Test Odaklı Geliştirme, çok kısa bir geliştirme döngüsünün tekrarına dayanan bir geliştirme sürecidir. Özellikleri yazıp sonra test etmek yerine, TDD testin önce yazılması konusunda ısrar eder. Bu test, özellik henüz mevcut olmadığı için kaçınılmaz olarak başarısız olacaktır. Geliştiricinin görevi, o belirli testi geçirmek için mümkün olan en basit kodu yazmaktır. Test geçtikten sonra, kod temizlenir ve iyileştirilir. Bu temel döngü "Kırmızı-Yeşil-Yeniden Düzenle" (Red-Green-Refactor) döngüsü olarak bilinir.

TDD'nin Ritmi: Kırmızı-Yeşil-Yeniden Düzenle

Bu üç adımlı döngü, TDD'nin kalp atışıdır. Bu ritmi anlamak ve uygulamak, teknikte ustalaşmanın temelidir.

Bir küçük işlevsellik parçası için döngü tamamlandığında, bir sonraki parça için yeni bir başarısız testle yeniden başlarsınız.

TDD'nin Üç Kuralı

Çevik (Agile) yazılım hareketinin önemli bir figürü olan Robert C. Martin (genellikle "Uncle Bob" olarak bilinir), TDD disiplinini kodlayan üç basit kural tanımlamıştır:

  1. Başarısız bir birim testini geçirmek dışında hiçbir üretim kodu yazamazsınız.
  2. Başarısız olmak için yeterli olandan daha fazla birim testi yazamazsınız; ve derleme hataları da birer başarısızlıktır.
  3. Başarısız olan tek birim testini geçmek için yeterli olandan daha fazla üretim kodu yazamazsınız.

Bu kurallara uymak, sizi Kırmızı-Yeşil-Yeniden Düzenle döngüsüne zorlar ve üretim kodunuzun %100'ünün belirli, test edilmiş bir gereksinimi karşılamak için yazılmasını sağlar.

Neden TDD'yi Benimsemelisiniz? Global İş Gerekçesi

TDD, bireysel geliştiricilere muazzam faydalar sunarken, gerçek gücü takım ve iş düzeyinde, özellikle küresel olarak dağıtılmış ortamlarda fark edilir.

JavaScript TDD Ortamınızı Kurma

JavaScript'te TDD'ye başlamak için birkaç araca ihtiyacınız var. Modern JavaScript ekosistemi mükemmel seçenekler sunar.

Bir Test Yığınının Temel Bileşenleri

Basitliği ve hepsi bir arada yapısı nedeniyle örneklerimizde Jest kullanacağız. "Sıfır yapılandırma" deneyimi arayan ekipler için mükemmel bir seçimdir.

Jest ile Adım Adım Kurulum

TDD için yeni bir proje kuralım.

1. Projenizi başlatın: Terminalinizi açın ve yeni bir proje dizini oluşturun.

mkdir js-tdd-project
cd js-tdd-project
npm init -y

2. Jest'i kurun: Jest'i projenize bir geliştirme bağımlılığı olarak ekleyin.

npm install --save-dev jest

3. Test betiğini yapılandırın: `package.json` dosyanızı açın. `"scripts"` bölümünü bulun ve `"test"` betiğini değiştirin. TDD iş akışı için paha biçilmez olan bir `"test:watch"` betiği eklemeniz de şiddetle tavsiye edilir.

"scripts": {
  "test": "jest",
  "test:watch": "jest --watchAll"
}

`--watchAll` bayrağı, Jest'e bir dosya her kaydedildiğinde testleri otomatik olarak yeniden çalıştırmasını söyler. Bu, Kırmızı-Yeşil-Yeniden Düzenle döngüsü için mükemmel olan anında geri bildirim sağlar.

İşte bu kadar! Ortamınız hazır. Jest, `*.test.js`, `*.spec.js` olarak adlandırılan veya bir `__tests__` dizininde bulunan test dosyalarını otomatik olarak bulacaktır.

Pratikte TDD: Bir `CurrencyConverter` Modülü Oluşturma

TDD döngüsünü pratik, küresel olarak anlaşılan bir soruna uygulayalım: para birimleri arasında para dönüştürme. Adım adım bir `CurrencyConverter` modülü oluşturacağız.

1. İterasyon: Basit, Sabit Kurlu Dönüşüm

🔴 KIRMIZI: İlk başarısız testi yazın

İlk gereksinimimiz, belirli bir miktarı bir para biriminden diğerine sabit bir kur kullanarak dönüştürmektir. `CurrencyConverter.test.js` adında yeni bir dosya oluşturun.

// CurrencyConverter.test.js
const CurrencyConverter = require('./CurrencyConverter');

describe('CurrencyConverter', () => {
  it('bir miktarı USD\'den EUR\'ya doğru şekilde dönüştürmelidir', () => {
    // Hazırlık (Arrange)
    const amount = 10; // 10 USD
    const expected = 9.2; // Sabit bir kur varsayımıyla: 1 USD = 0.92 EUR

    // Eylem (Act)
    const result = CurrencyConverter.convert(amount, 'USD', 'EUR');

    // Doğrulama (Assert)
    expect(result).toBe(expected);
  });
});

Şimdi, terminalinizden test izleyicisini çalıştırın:

npm run test:watch

Test feci şekilde başarısız olacaktır. Jest, `TypeError: Cannot read properties of undefined (reading 'convert')` gibi bir şey bildirecektir. Bu bizim KIRMIZI durumumuzdur. Test başarısız oluyor çünkü `CurrencyConverter` mevcut değil.

🟢 YEŞİL: Testi geçecek en basit kodu yazın

Şimdi testi geçirelim. `CurrencyConverter.js` dosyasını oluşturun.

// CurrencyConverter.js
const rates = {
  USD: {
    EUR: 0.92
  }
};

const CurrencyConverter = {
  convert(amount, from, to) {
    return amount * rates[from][to];
  }
};

module.exports = CurrencyConverter;

Bu dosyayı kaydettiğiniz anda Jest testi yeniden çalıştıracak ve YEŞİL'e dönecektir. Testin gereksinimini karşılamak için mutlak minimum kodu yazdık.

🔵 YENİDEN DÜZENLEME: Kodu iyileştirin

Kod basit, ama şimdiden iyileştirmeler düşünebiliriz. İç içe geçmiş `rates` nesnesi biraz katı. Şimdilik yeterince temiz. En önemli şey, bir testle korunan çalışan bir özelliğimizin olmasıdır. Bir sonraki gereksinime geçelim.

2. İterasyon: Bilinmeyen Para Birimlerini Ele Alma

🔴 KIRMIZI: Geçersiz bir para birimi için test yazın

Bilmediğimiz bir para birimine dönüştürmeye çalışırsak ne olmalı? Muhtemelen bir hata fırlatmalıdır. Bu davranışı `CurrencyConverter.test.js` içinde yeni bir testte tanımlayalım.

// CurrencyConverter.test.js'de, describe bloğunun içinde

it('bilinmeyen para birimleri için bir hata fırlatmalıdır', () => {
  // Hazırlık (Arrange)
  const amount = 10;

  // Eylem (Act) & Doğrulama (Assert)
  // Jest'in toThrow metodunun çalışması için fonksiyon çağrısını bir ok fonksiyonu içine alıyoruz.
  expect(() => {
    CurrencyConverter.convert(amount, 'USD', 'XYZ');
  }).toThrow('Unknown currency: XYZ');
});

Dosyayı kaydedin. Test çalıştırıcısı hemen yeni bir başarısızlık gösterir. KIRMIZI çünkü kodumuz bir hata fırlatmıyor; `rates['USD']['XYZ']`'e erişmeye çalışıyor ve bu da bir `TypeError` ile sonuçlanıyor. Yeni testimiz bu kusuru doğru bir şekilde tespit etti.

🟢 YEŞİL: Yeni testi geçirin

Doğrulamayı eklemek için `CurrencyConverter.js` dosyasını değiştirelim.

// CurrencyConverter.js
const rates = {
  USD: {
    EUR: 0.92,
    GBP: 0.80
  },
  EUR: {
    USD: 1.08
  }
};

const CurrencyConverter = {
  convert(amount, from, to) {
    if (!rates[from] || !rates[from][to]) {
      // Daha iyi bir hata mesajı için hangi para biriminin bilinmediğini belirle
      const unknownCurrency = !rates[from] ? from : to;
      throw new Error(`Unknown currency: ${unknownCurrency}`);
    }
    return amount * rates[from][to];
  }
};

module.exports = CurrencyConverter;

Dosyayı kaydedin. Her iki test de şimdi geçiyor. YEŞİL'e geri döndük.

🔵 YENİDEN DÜZENLEME: Kodu temizleyin

`convert` fonksiyonumuz büyüyor. Doğrulama mantığı hesaplama ile karışmış durumda. Okunabilirliği artırmak için doğrulamayı ayrı bir özel fonksiyona çıkarabiliriz, ancak şimdilik hala yönetilebilir. Anahtar nokta, testlerimiz bize bir şeyi bozup bozmadığımızı söyleyeceği için bu değişiklikleri yapma özgürlüğüne sahip olmamızdır.

3. İterasyon: Asenkron Kur Çekme

Kurları sabit kodlamak gerçekçi değil. Modülümüzü, (taklit edilmiş) bir harici API'den kurları çekecek şekilde yeniden düzenleyelim.

🔴 KIRMIZI: Bir API çağrısını taklit eden asenkron bir test yazın

Öncelikle, dönüştürücümüzü yeniden yapılandırmamız gerekiyor. Artık belki bir API istemcisiyle örneklendirebileceğimiz bir sınıf olması gerekecek. Ayrıca `fetch` API'sini de taklit etmemiz gerekecek. Jest bunu kolaylaştırır.

Test dosyamızı bu yeni, asenkron gerçeğe uyacak şekilde yeniden yazalım. Yine mutlu yolu (happy path) test ederek başlayacağız.

// CurrencyConverter.test.js
const CurrencyConverter = require('./CurrencyConverter');

// Dış bağımlılığı taklit et
global.fetch = jest.fn();

beforeEach(() => {
  // Her testten önce taklit geçmişini temizle
  fetch.mockClear();
});

describe('CurrencyConverter', () => {
  it('kurları çekmeli ve doğru şekilde dönüştürmelidir', async () => {
    // Hazırlık (Arrange)
    // Başarılı API yanıtını taklit et
    fetch.mockResolvedValueOnce({
      json: () => Promise.resolve({ rates: { EUR: 0.92 } })
    });

    const converter = new CurrencyConverter('https://api.exchangerates.com');
    const amount = 10; // 10 USD

    // Eylem (Act)
    const result = await converter.convert(amount, 'USD', 'EUR');

    // Doğrulama (Assert)
    expect(result).toBe(9.2);
    expect(fetch).toHaveBeenCalledTimes(1);
    expect(fetch).toHaveBeenCalledWith('https://api.exchangerates.com/latest?base=USD');
  });

  // Ayrıca API hataları vb. için de testler eklerdik.
});

Bunu çalıştırmak bir sürü KIRMIZI ile sonuçlanacaktır. Eski `CurrencyConverter`'ımız bir sınıf değil, `async` bir metoda sahip değil ve `fetch` kullanmıyor.

🟢 YEŞİL: Asenkron mantığı uygulayın

Şimdi, `CurrencyConverter.js`'yi testin gereksinimlerini karşılayacak şekilde yeniden yazalım.

// CurrencyConverter.js
class CurrencyConverter {
  constructor(apiUrl) {
    this.apiUrl = apiUrl;
  }

  async convert(amount, from, to) {
    const response = await fetch(`${this.apiUrl}/latest?base=${from}`);
    if (!response.ok) {
      throw new Error('Döviz kurları çekilemedi.');
    }

    const data = await response.json();
    const rate = data.rates[to];

    if (!rate) {
      throw new Error(`Bilinmeyen para birimi: ${to}`);
    }

    // Testlerde ondalık sayı sorunlarından kaçınmak için basit yuvarlama
    const convertedAmount = amount * rate;
    return parseFloat(convertedAmount.toFixed(2));
  }
}

module.exports = CurrencyConverter;

Kaydettiğinizde, test YEŞİL'e dönmelidir. Finansal hesaplamalarda yaygın bir sorun olan ondalık sayı yanlışlıklarını ele almak için yuvarlama mantığı da eklediğimize dikkat edin.

🔵 YENİDEN DÜZENLEME: Asenkron kodu iyileştirin

`convert` metodu çok şey yapıyor: veri çekme, hata yönetimi, ayrıştırma ve hesaplama. Bunu, yalnızca API iletişiminden sorumlu ayrı bir `RateFetcher` sınıfı oluşturarak yeniden düzenleyebiliriz. `CurrencyConverter`'ımız daha sonra bu fetcher'ı kullanırdı. Bu, Tek Sorumluluk Prensibi'ne uyar ve her iki sınıfın da test edilmesini ve bakımını kolaylaştırır. TDD bizi bu daha temiz tasarıma yönlendirir.

Yaygın TDD Desenleri ve Anti-Desenleri

TDD'yi uyguladıkça, iyi çalışan desenleri ve sürtünmeye neden olan anti-desenleri keşfedeceksiniz.

İzlenecek İyi Desenler

Kaçınılması Gereken Anti-Desenler

Daha Geniş Geliştirme Yaşam Döngüsünde TDD

TDD bir boşlukta var olmaz. Modern Çevik ve DevOps uygulamalarıyla, özellikle küresel ekipler için harika bir şekilde bütünleşir.

Sonuç: TDD ile Yolculuğunuz

Test Odaklı Geliştirme, bir test stratejisinden daha fazlasıdır—yazılım geliştirmeye yaklaşımımızda bir paradigma kaymasıdır. Kalite, güven ve işbirliği kültürünü besler. Kırmızı-Yeşil-Yeniden Düzenle döngüsü, sizi temiz, sağlam ve sürdürülebilir koda yönlendiren istikrarlı bir ritim sağlar. Ortaya çıkan test paketi, ekibinizi gerilemelerden koruyan bir güvenlik ağı ve yeni üyeleri işe alan yaşayan bir dokümantasyon haline gelir.

Öğrenme eğrisi dik gelebilir ve başlangıçtaki tempo daha yavaş görünebilir. Ancak hata ayıklama süresindeki azalma, iyileştirilmiş yazılım tasarımı ve artan geliştirici güveni cinsinden uzun vadeli getirileri ölçülemez. TDD'de ustalaşma yolculuğu, disiplin ve pratik gerektiren bir yolculuktur.

Bugün başlayın. Bir sonraki projenizde küçük, kritik olmayan bir özellik seçin ve sürece kendinizi adayın. Önce testi yazın. Başarısız olmasını izleyin. Geçmesini sağlayın. Ve sonra, en önemlisi, yeniden düzenleyin. Yeşil bir test paketinden gelen güveni deneyimleyin ve yakında yazılımı başka bir şekilde nasıl geliştirdiğinizi merak edeceksiniz.