İçeriğe geç
academia.sh

Ders 09 / 13

Kırmızı–Yeşil–Yeniden Düzenle

Testin koddan önce yazıldığı üç adımlı döngünün her adımının koşturularak gösterilmesi, sabit değerle geçilen yeşilin üçgenlemeyle genelleştirilmesi ve yeniden düzenleme adımının güvence koşulu.

İçindekiler

Birim Test Pratiği konusunda her testte kod önce vardı: bir hata raporu, bir kural ya da bir yeniden düzenleme, sınanacak bir gerçekleştirimi hazır buldu. Test o gerçekleştirimi doğruladı, ölçtü ya da kırılganlığını gösterdi.

Bu konu sırayı tersine çevirir. Test önce yazıldığında artık bir doğrulama aracı değil, bir tasarım aracıdır: hangi işlevin hangi imzayla var olacağını, hangi girdiyi alacağını ve ne döndüreceğini test belirler. Bu tersine çevirmenin adı test odaklı geliştirmedir (test driven development) ve üç adımlı bir döngüyle işler.

Kurulacak yeni kural ödünç yenilemesidir. Bir üye elindeki kitabın süresini uzatabilir; yenilenen ödüncün son tarihi, yenileme gününden itibaren üye türünün ödünç süresi kadar ileri alınır.

Kırmızı: Henüz Olmayan Kodun Testi

Döngü, geçmeyen bir testle başlar. Testin çalışabilmesi için sınanan işlevin var olması gerekir; bu yüzden ilk yazılan şey, yalnız derlenmeye yetecek kadar boş bir gerçekleştirimdir.

// yenileme.mjs — surum 0: islev var, davranis yok
export function yenile(kayit, bugun) {
  return kayit;
}

Testin kendisi, henüz yazılmamış davranışı tarif eder.

// yenileme.test.mjs — kirmizi adim: tek kural, henuz gerceklestirim yok
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { yenile } from './yenileme.mjs';

test('ogrenci oduncu yenilendiginde son tarih bugunden yirmi sekiz gun sonradir', () => {
  const kayit = { uyeNo: 'U-17', uyeTuru: 'ogrenci', sonGun: 1028 };

  const yeni = yenile(kayit, 1020);

  assert.equal(yeni.sonGun, 1048);
});
node --test --test-reporter=tap yenileme.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
not ok 1 - ogrenci oduncu yenilendiginde son tarih bugunden yirmi sekiz gun sonradir
# tests 1
# pass 0
# fail 1

Bu adımda üç karar aynı anda verildi ve hiçbiri gerçekleştirim yazılırken verilmedi. İşlevin adı yenile, girdileri ödünç kaydı ile gün numarası, çıktısı güncellenmiş bir kayıt. Test odaklı geliştirmenin tasarım tarafı budur: arayüz, onu kullanan koddan türer.

Kırmızı koşumun güvencesi birinci derste anılan şeydir: testin düşebildiği görüldü. Yazıldığı anda yeşil olan bir test, yanlış bir şeyi sınıyor ya da hiçbir şeyi sınamıyor olabilir; bu ihtimal ancak düşen bir koşum görülerek elenir.

Yeşil: En Kısa Yol

İkinci adımın tek ölçütü testi geçirmektir. Doğru tasarım, genel çözüm ya da temiz kod bu adımın hedefi değildir; onlar üçüncü adımın işidir.

// yenileme.mjs — surum 1: testi gecirmenin en kisa yolu
export function yenile(kayit, bugun) {
  return { ...kayit, sonGun: 1048 };
}
node --test --test-reporter=tap yenileme.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
ok 1 - ogrenci oduncu yenilendiginde son tarih bugunden yirmi sekiz gun sonradir
# tests 1
# pass 1
# fail 0

Sabit değer döndüren bu sürüm açıkça yanlıştır ve kasıtlıdır. Yaptığı iş, testin gerçekten o değeri sınadığını kanıtlamaktır: kırmızıdan yeşile geçişin nedeni yazılan satırdır, başka bir şey değil.

Bu adım aynı zamanda bir teşhis aracıdır. Sabit değer yazıldığı hâlde test yeşile dönmüyorsa sorun gerçekleştirimde değil, testin kendisindedir.

Üçgenleme

Sabit değerin yanlışlığını kod değil, ikinci bir örnek ortaya çıkarır. Aynı kuralın farklı bir girdiyle yazılmış hâli, gerçekleştirimi genelleşmeye zorlar. Bu tekniğe üçgenleme (triangulation) denir.

// yenileme.test.mjs — surum 2: ikinci uye turu eklendi
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { yenile } from './yenileme.mjs';

test('ogrenci oduncu yenilendiginde son tarih bugunden yirmi sekiz gun sonradir', () => {
  const kayit = { uyeNo: 'U-17', uyeTuru: 'ogrenci', sonGun: 1028 };

  const yeni = yenile(kayit, 1020);

  assert.equal(yeni.sonGun, 1048);
});

test('standart uye oduncu yenilendiginde son tarih bugunden on dort gun sonradir', () => {
  const kayit = { uyeNo: 'U-42', uyeTuru: 'uye', sonGun: 1014 };

  const yeni = yenile(kayit, 1020);

  assert.equal(yeni.sonGun, 1034);
});
node --test --test-reporter=tap yenileme.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
ok 1 - ogrenci oduncu yenilendiginde son tarih bugunden yirmi sekiz gun sonradir
not ok 2 - standart uye oduncu yenilendiginde son tarih bugunden on dort gun sonradir
# tests 2
# pass 1
# fail 1

Döngü yeniden kırmızıya döndü ve bu sefer kırmızının işaret ettiği şey belirgin: sabit değer yetmiyor. Genelleştirme artık bir tahmin değil, düşen testin dayattığı bir zorunluluk.

// yenileme.mjs — surum 2: sabit deger yerine kural tablosu
export const KURALLAR = { ogrenci: { oduncGun: 28 }, uye: { oduncGun: 14 } };

export function yenile(kayit, bugun) {
  return { ...kayit, sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun };
}
node --test --test-reporter=tap yenileme.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
ok 1 - ogrenci oduncu yenilendiginde son tarih bugunden yirmi sekiz gun sonradir
ok 2 - standart uye oduncu yenilendiginde son tarih bugunden on dort gun sonradir
# tests 2
# pass 2
# fail 0

Yeniden Düzenle: Yeşilin Üzerinde Çalışmak

Üçüncü adımın tanımı kesindir: yeniden düzenleme (refactoring), dışarıdan gözlenen davranışı değiştirmeden iç yapıyı iyileştirmektir. Yeni kural eklenmez, yeni durum ele alınmaz, hata düzeltilmez. Yalnız yapı değişir.

// yenileme.mjs — surum 3: sure hesabi adlandirilmis bir isleve alindi
export const KURALLAR = { ogrenci: { oduncGun: 28 }, uye: { oduncGun: 14 } };

export function oduncSuresi(uyeTuru) {
  return KURALLAR[uyeTuru].oduncGun;
}

export function yenile(kayit, bugun) {
  return { ...kayit, sonGun: bugun + oduncSuresi(kayit.uyeTuru) };
}
node --test --test-reporter=tap yenileme.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
ok 1 - ogrenci oduncu yenilendiginde son tarih bugunden yirmi sekiz gun sonradir
ok 2 - standart uye oduncu yenilendiginde son tarih bugunden on dort gun sonradir
# tests 2
# pass 2
# fail 0

Bu adımı mümkün kılan şey, ondan önceki iki adımdır. Yeşil bir test kümesi olmadan yeniden düzenleme bir yeniden yazımdır ve doğruluğu ancak gözle denetlenebilir. Testler yeşilken yapılan her yapı değişikliği, koşumun kendisiyle doğrulanır.

Sekizinci dersteki ölçüm burada karşılığını bulur. Testler gerçekleştirime bağlıysa bu adım kırmızıya döner ve yeniden düzenleme cezalandırılır. Testler davranışa bağlıysa üçüncü adım serbesttir. Test odaklı geliştirme, testleri kullanan koddan türettiği için davranışa bağlı test yazmayı kolaylaştırır — ama garanti etmez; sözleşme ile iç ayrıntı ayrımını yine yazan kişi yapar.

Üç Adımın Böldüğü Sorular

Döngünün değeri, adımlarının aynı anda düşünülmesi gereken üç soruyu birbirinden ayırmasıdır.

Kırmızı adımda tek soru şudur: ne istiyorum? Kural nedir, arayüz nasıl kullanılacak, beklenen sonuç nedir? Gerçekleştirim hiç düşünülmez.

Yeşil adımda tek soru şudur: bu testi nasıl geçiririm? En kısa yol yeterlidir; tasarım kaygısı bu adımda bir dikkat dağınıklığıdır.

Yeniden düzenleme adımında tek soru şudur: elde duran kod nasıl daha iyi olur? Bu soruya verilen yanıtın doğru olup olmadığını testler bildirir.

Üç sorunun aynı anda düşünülmesi, sıradan bir programlama seansında sürekli yaşanan şeydir ve maliyeti dikkat maliyetidir. Döngü bu maliyeti üçe bölerek düşürür.

Özet

  • Test odaklı geliştirmede test bir doğrulama aracı değil tasarım aracıdır: işlevin adı, girdileri ve çıktısı onu kullanan testten türer.
  • Kırmızı adımın güvencesi testin düşebildiğinin görülmesidir; yazıldığı anda yeşil olan test yanlış bir şeyi sınıyor olabilir.
  • Yeşil adımın tek ölçütü testi geçirmektir; sabit değer döndüren sürüm, geçişin nedeninin yazılan satır olduğunu kanıtlar.
  • Üçgenleme, ikinci bir örnekle gerçekleştirimi genelleşmeye zorlar; genelleştirme bir tahmin değil düşen testin dayatmasıdır.
  • Yeniden düzenleme davranışı değiştirmeden iç yapıyı iyileştirir ve ancak yeşil bir test kümesinin üzerinde güvenli olur.

Sonraki Adım

Bu derste döngü iki kez döndü ve her turda tek bir kural ele alındı. Adım boyu bir seçimdir: aynı iş üç kuralı birden kapsayan tek bir turla da yürütülebilirdi. O seçimin bedeli ölçülebilir — düşen testin işaret ettiği yer, iki koşum arasında değişen satır sayısı ve geri dönülecek son yeşil noktaya olan uzaklık. Sonraki ders aynı özelliği iki farklı adım boyuyla kurar ve iki koşumu karşılaştırır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat