İçeriğe geç
academia.sh

Ders 09 / 12

Davranış Odaklı Test

Dört satırlık bir senaryo metni bir adım haritasına karşı çalıştırılıyor; haritada karşılığı olmayan adım bir koşumda sessizce atlanıyor, başka bir koşumda programı durduruyor. Aynı dört adım tam eşleştiğinde bile, kontrol adımlarının iddiası kaldırılmış bir haritayla koşturulunca yanlış bir değer üzerinden yine 'geçti' diyor.

İçindekiler

Önceki derste sınanan sistem büyüdü: birim kurulumunda sahtelenen bir bağımlılık gerçek gerçekleştirimiyle geri geldi ve testin gördüğü sınıf yolu iki tipe çıktı. Bu derste büyüyen taraf farklı: sınanan kod değil, testin kendisi düzyazıya yaklaşıyor. Bir senaryo, sıradan cümlelerle yazılmış bir adım listesidir — “depoya 10 birim eklendi”, “depo miktarı 7 olmalı” gibi — ve bu cümlelerin adım haritasında çalıştırılabilir bir karşılığı olması beklenir. Soru şu: metindeki dört adımın kaçının gerçek bir karşılığı var, karşılığı olmayan adım koşumu nasıl etkiliyor, ve adımların tümü eşleşse bile bu senaryonun doğru çalıştığı anlamına mı geliyor?

Senaryo dilinin çekiciliği, teknik olmayan bir okurun da adımları anlayabilmesindedir — “depoya 10 birim eklendi” cümlesi Java bilmeyen biri için de bir anlam taşır. Ama bu okunabilirlik bir bedel taşır: düzyazı, derleyicinin denetleyebileceği bir sözdizimi değildir. Bir yöntem adı yanlış yazıldığında derleyici hemen durur; bir senaryo satırı yanlış yazıldığında ya da hiç uygulanmadığında, bunu fark eden yalnız koşumun kendisidir. Bu ders, o farkın nasıl fark edildiğini ve fark edilmemesinin ne anlama geldiğini koşturarak gösteriyor.

Davranış odaklı geliştirme kavramı M21/K02 Birim Testi ve Test Odaklı Geliştirme kursunda kuruldu ve burada yeniden tanıtılmaz. Bu dersin eklediği tek soru, senaryo metninin bildirdiği adımlar ile adım haritasında gerçekten bulunan adımlar arasındaki eşleşmedir.

Bu soru, aslında dersin başından beri sorduğumuz sorunun bir varyasyonu. Birinci derste soru “bu tip test sınıf yolunda var mı” idi ve tip somut, derlenmiş bir .class dosyasıydı. Beşinci derste tip yerini bir cümleye bırakıyor: senaryo metnindeki her satır, adım haritasında kendisini karşılayacak bir davranışa ihtiyaç duyuyor. Fark bir adlandırma sorunundan ibaret değil — bir tipin sınıf yolunda bulunup bulunmadığını derleyici bize söyler, ama bir metin satırının bir davranışla eşleştiğini kimse bize söylemez; bu ders o eşleşmeyi elle kurup elle sayıyor.

Senaryo Bir Bildirimdir, Adım Haritası Karşılığını Verir

  • TS21. Senaryo adımları burada düz dizeler olarak eşleştirilir (Map<String, ...>). Gerçek çerçevelerin örüntü eşleme, düzenli ifadeyle adım tanımlama ve parametre çıkarma mekanizması burada kurulmaz.
  • TS22. Bir adımın “eşleşmesi”, metninin haritada birebir bir anahtarla bulunmasıdır. Bu, adımın doğru çalıştığını değil, yalnız çalıştırılabilir bir karşılığı olduğunu gösterir — ikisi ayrı sorulardır.
  • TS23. “Sessiz” ve “durduran” iki ayrı koşum stratejisi burada elle yazılır. Gerçek çerçevelerin bu ikisi arasında hangisini varsayılan seçtiği ya da nasıl yapılandırıldığı burada modellenmez.

Senaryo metni dört satırdan oluşuyor ve bir record dizisi olarak bildiriliyor — üçüncü derste veri kümesinin bildirilme biçimiyle aynı yöntem, burada veri yerine düzyazı adımlar taşıyor. Adım haritası ise her satırın metnini bir çalıştırılabilir gövdeye bağlıyor. Aşağıdaki eksikHarita dördüncü adımın metnini hiç içermiyor — bilinçli bir eksiklik. sessizKosum karşılığı bulunamayan adımı atlayıp devam ediyor, durduranKosum aynı durumda bir istisna fırlatıp koşumu kesiyor.

Haritanın kendisi bir LinkedHashMap — sıradan bir eşleme değil, eklenme sırasını koruyan bir eşleme. Bu, senaryonun kendi sırasını değil, haritanın kayıt sırasını koruyor; adımların hangi sırayla çalıştığına haritanın kendisi karar vermiyor, o kararı METIN dizisindeki sıra veriyor. Sıra korumasının burada tek pratik faydası, iki haritayı yan yana okurken adımların hangi sırayla tanımlandığını görebilmek — koşumun kendisi METIN dizisini baştan sona dolaşıyor, haritanın iç sırasına bakmıyor.

Adım haritasının kendisi, ilk derste kurulan test sınıf yolu kavramının doğal bir uzantısı. eksikHarita ve bosIddialiHarita üretim kodunun hiçbir yerinde geçmiyor; ikisi de yalnız test tarafının bildiği, test tarafının koştuğu birer eşleme. Senaryo metninin kendisi de öyle — METIN dizisi üretim kodunun okuduğu bir veri değil, testin okuduğu bir bildirim. Bu derste yeni olan, bu bildirimin düzyazı biçiminde durması ve karşılığının bir tip adı değil bir dize anahtarı olmasıdır.

// Senaryo.java — senaryo metni bir bildirimdir, adim haritasinda karsiligi bulunamayan adim sessiz mi kaliyor durduruyor mu
import java.util.*;
import java.util.function.Consumer;

public class Senaryo {

    record Adim(String metin) {}

    static final Adim[] METIN = {
        new Adim("depoya 10 birim eklendi"),
        new Adim("depodan 3 birim azaltildi"),
        new Adim("depo miktari 7 olmali"),
        new Adim("depo miktari eksiye dusmemeli"),
    };

    // -- adim haritasi eksik: dorduncu adimin hicbir kaydi yok --------------------
    static Map<String, Consumer<int[]>> eksikHarita() {
        Map<String, Consumer<int[]>> h = new LinkedHashMap<>();
        h.put("depoya 10 birim eklendi", d -> d[0] += 10);
        h.put("depodan 3 birim azaltildi", d -> d[0] -= 3);
        h.put("depo miktari 7 olmali", d -> { if (d[0] != 7) throw new AssertionError("miktar 7 degil: " + d[0]); });
        return h;
    }

    // -- adim haritasi tam: dordu de kayitli, ama kontrol adimlarinin iddiasi kaldirilmis --
    static Map<String, Consumer<int[]>> bosIddialiHarita() {
        Map<String, Consumer<int[]>> h = new LinkedHashMap<>();
        h.put("depoya 10 birim eklendi", d -> d[0] += 10);
        h.put("depodan 3 birim azaltildi", d -> d[0] -= 2); // kasitli hata: 3 degil 2 azaltiyor
        h.put("depo miktari 7 olmali", d -> {});            // iddia kaldirilmis
        h.put("depo miktari eksiye dusmemeli", d -> {});    // iddia kaldirilmis
        return h;
    }

    static void sessizKosum(Adim[] senaryo, Map<String, Consumer<int[]>> harita, int[] durum) {
        int eslesen = 0;
        for (Adim a : senaryo) {
            Consumer<int[]> uygulama = harita.get(a.metin());
            if (uygulama == null) { System.out.println("atlaniyor    : " + a.metin()); continue; }
            uygulama.accept(durum);
            eslesen++;
        }
        System.out.println("eslesen adim : " + eslesen + "/" + senaryo.length);
        System.out.println("sonuc        : gecti, depo=" + durum[0]);
    }

    static void durduranKosum(Adim[] senaryo, Map<String, Consumer<int[]>> harita, int[] durum) {
        int eslesen = 0;
        for (Adim a : senaryo) {
            Consumer<int[]> uygulama = harita.get(a.metin());
            if (uygulama == null) throw new IllegalStateException("adimin karsiligi yok: " + a.metin());
            uygulama.accept(durum);
            eslesen++;
        }
        System.out.println("eslesen adim : " + eslesen + "/" + senaryo.length);
        System.out.println("sonuc        : gecti, depo=" + durum[0]);
    }

    public static void main(String[] args) {
        System.out.println("-- eksik harita, sessiz kosum --");
        sessizKosum(METIN, eksikHarita(), new int[]{0});

        System.out.println("-- eksik harita, durduran kosum --");
        try {
            durduranKosum(METIN, eksikHarita(), new int[]{0});
        } catch (IllegalStateException e) {
            System.out.println("durdu        : " + e.getMessage());
        }

        System.out.println("-- tam harita, iddiasi kaldirilmis kontrol adimlari --");
        sessizKosum(METIN, bosIddialiHarita(), new int[]{0});
    }
}
-- eksik harita, sessiz kosum --
atlaniyor    : depo miktari eksiye dusmemeli
eslesen adim : 3/4
sonuc        : gecti, depo=7
-- eksik harita, durduran kosum --
durdu        : adimin karsiligi yok: depo miktari eksiye dusmemeli
-- tam harita, iddiasi kaldirilmis kontrol adimlari --
eslesen adim : 4/4
sonuc        : gecti, depo=8

İlk iki koşum aynı eksikHarita’yı, aynı dört satırlık senaryoyu kullanıyor; fark yalnız karşılığı bulunamayan adıma verilen tepkide. sessizKosum dördüncü adımı atlıyor, üç adımı çalıştırıyor, “3/4” diyor ve “gecti” ile bitiriyor — senaryo metninde dört satır olduğu hâlde, koşum yalnız üçünü gerçekten işletti. durduranKosum ise aynı eksikliği bir istisnaya çeviriyor: hangi adımın metninin karşılıksız kaldığı doğrudan hata mesajında görünüyor, ve koşum orada duruyor, kalan adımlar hiç işletilmiyor.

Bu iki strateji arasındaki fark, kursun daha önceki derslerinde görülen sessiz/koşum ayrımının senaryo metni düzeyindeki karşılığı. Sessiz strateji, eksik bir adımı fark edilmeden geçiştiriyor — üç adımlık bir senaryo, dört adımlık bir senaryo gibi raporlanıyor, “3/4” satırı okunmazsa hiç görünmüyor. Durduran strateji ise eksikliği bir koşum hatasına çeviriyor ve neyin eksik olduğunu adının içine yazıyor. Hiçbiri “doğru” strateji değil; biri yazım hatası yapılmış bir adımı erken yakalar, öteki senaryonun geri kalanının hâlâ çalışabildiğini görmeyi sağlar. Seçim, senaryo metnini kimin yazdığına ve o metnin ne sıklıkla değiştiğine bağlıdır.

Sessiz stratejinin riski, “3/4” satırının gözden kaçmasının hiç zor olmamasıdır. Bir senaryo takımı onlarca satırlık metinler biriktirdiğinde, her koşumun ürettiği eşleşme oranını tek tek okumak gerçekçi değildir; çoğu rapor yalnız “geçti” ya da “düştü” özetini gösterir, eşleşme sayısını göstermez. Bu durumda sessiz strateji, hiç yazılmamış bir adımı hiç fark edilmeden aylarca taşıyabilir — senaryo metni bir davranışı bildiriyor gibi görünür, ama o davranış hiçbir zaman gerçekten koşmamıştır. Durduran strateji bu riski ortadan kaldırır, ama karşılığında senaryo metnindeki her küçük yazım farkını (eksik bir boşluk, değişen bir sayı) bir koşum hatasına çevirir; metin sık değişiyorsa bu, gerçek kusurlardan çok metin bakımı hatalarını raporlamaya başlar. İki stratejinin birlikte var olması bu yüzden tesadüfi değil: hangi hatanın erken yakalanması, hangisinin görmezden gelinebilir olduğu bir mühendislik kararıdır ve senaryo metninin kimin tarafından, ne sıklıkla yazıldığına bağlıdır.

Adım Sayısının Tam Olması Doğruluğu Göstermez

  • TS24. Kasıtlı hata — azaltmanın 3 yerine 2 uygulanması — yalnız sınırlayıcı ölçümü göstermek için üçüncü koşumda tanıtılır. İlk iki koşum doğru işleyen bir azaltma kullanır; üçünün karşılaştırılabilir olması için yalnız tek bir değişken değiştirilir.
  • TS25. “İddiası kaldırılmış” adım burada boş bir gövdeyle (d -> {}) modellenir. Adımın haritada bir karşılığı vardır ve eşleşme sayılır, yalnız o karşılık hiçbir şeyi denetlemez.

Üçüncü koşum ilk ikisinden iki noktada ayrılıyor. Birincisi, bosIddialiHarita dört adımın dördünü de içeriyor — hiçbir adım atlanmıyor, hiçbir istisna fırlamıyor, eşleşen adım sayısı “4/4”. İkincisi, bu haritadaki azaltma adımı kasıtlı olarak yanlış: 3 yerine 2 azaltıyor. Doğru işleseydi depo miktarı 7 olurdu; bu koşumda 8 kalıyor. eksikHarita kullanan ilk koşumda “depo miktari 7 olmali” adımı gerçek bir kontrol taşıyordu ve yanlış bir değeri yakalardı. bosIddialiHarita’da aynı metin haritada duruyor, eşleşiyor, ama gövdesi boş — hiçbir karşılaştırma yapmıyor. Sonuç satırı yine “gecti, depo=8” diyor; “gecti” kelimesi burada yalnızca istisna fırlamadığı anlamına geliyor, değerin doğru olduğu anlamına gelmiyor.

Bu üçüncü koşumun ilk iki koşumla aynı senaryo metnini paylaşması bilinçli bir tercih. Metin hiç değişmedi — dört satır, aynı sırayla, aynı kelimelerle. Değişen yalnız o metnin arkasındaki uygulamadır: bir sürümde üçüncü adım gerçek bir sayı karşılaştırması taşıyordu, diğerinde aynı adım hiçbir şey yapmıyor. Senaryo metnini okuyan biri bu farkı göremez — metin ikisinde de birebir aynı satırı taşıyor. Farkı görmenin tek yolu, adım haritasının kendisini okumak ya da koşturup gerçek değeri gözlemlemektir; metnin kendisi hiçbir zaman arkasındaki uygulamanın ne yaptığını garanti etmez.

Bu, kursun beşinci payının kapanış gözlemi: eşleşen adım sayısının tam olması — senaryonun her satırının bir karşılığı olması — senaryonun doğru çalıştığını göstermez. Bir metnin her adımının adım haritasında bir karşılığı bulunması, o karşılığın içinde gerçekten bir iddia yazılı olduğu anlamına gelmiyor; adım haritasındaki bir giriş, boş bir gövdeyle de tamamen geçerli bir eşleşme sayılıyor. “4/4 eşleşti” ile “senaryo doğru çalıştı” iki ayrı önermedir, ve bu ders ikisini aynı koşumda birbirinden ayırarak gösteriyor: sayı tamken bile bir hata sessizce geçebiliyor.

Bu üç koşumu yan yana koyunca kursun ölçü sorusunun son biçimi ortaya çıkıyor. Önceki derslerde soru “bu tip hangi sınıf yolunda bulunmak zorunda” biçimindeydi ve tip somut bir .class dosyasıydı. Burada “tip” yerine bir adım duruyor, “sınıf yolu” yerine bir adım haritası duruyor, ama soru aynı kalıba oturuyor: bu adımın adım haritasında bir karşılığı var mı, ve o karşılık gerçekten bir şey mi denetliyor. Senaryo metni bir bildirimdir; adım haritası ise o bildirimin çalıştırılabilir karşılığıdır, ve ikisi arasında bir boşluk kaldığında — ister eksik bir kayıt, ister boş bir gövde olarak — o boşluk metinden okunarak değil, yalnız koşturarak görülür.

Bu payın beş dersi boyunca aynı soru beş ayrı biçimde soruldu. Birinci derste soru bir çerçeve tipinin test sınıf yolunda bulunup bulunmadığıydı; ikincisinde aynı adı taşıyan iki gerçekleştirimden hangisinin koşum sınıf yolunda durduğuydu; üçüncüsünde bir kurulumun hangi kapsamda çalıştığıydı; dördüncüsünde gerçek bir bağımlılığın sınıf yoluna girip girmediğiydi. Bu son derste soru, bir davranışın adım haritasında bir karşılığı olup olmadığına indirgendi. Beşinde de ortak olan, sorunun kaynak metinden değil, gerçekten koşturarak yanıtlanmasıydı — bir tipin, bir kurulumun ya da bir adımın ne yaptığı, yazıldığı biçimden değil, çalıştırıldığında ürettiği sonuçtan okunuyordu.

Beş dersin de paylaştığı ikinci bir örüntü, “tam” görünen bir sonucun yeterlilik anlamına gelmemesiydi. Birinci derste tam bir test sınıf yolu bile çerçeve eksikse çöküyordu; ikincide hatasız tamamlanan bir koşum yanlış bir sayı taşıyordu; üçüncüde geçen bir satır yalnız sırası uygun düştüğü için geçiyordu; dördüncüde sessiz bir koşum bir sınır ihlalini hiç görmüyordu. Bu son ders aynı örüntüyü adım sayısı üzerinden tekrarladı: “4/4” görünen bir senaryo, arkasında hiçbir gerçek kontrol taşımayabiliyordu. Bu tekrar tesadüfi değil — kursun tezinin kendisi budur: derlenmiş, koşan, “tamam” diyen bir program, hangi soruya yanıt verdiği söylenmeden okunamaz.

Özet

  • Senaryo metni dört adımlık bir bildirim olarak yazıldı; adım haritası bu adımların çalıştırılabilir karşılıklarını verdi.
  • Karşılığı bulunamayan bir adım bir koşumda sessizce atlandı (3/4 eşleşti), başka bir koşumda bir istisnayla koşumu durdurdu.
  • Dört adımın dördü de eşleştiğinde (“4/4”), bu tek başına senaryonun doğru çalıştığını göstermedi.
  • Kontrol adımlarının iddiası kaldırılmış bir haritayla koşulan senaryo, yanlış bir değer (8, beklenen 7) üzerinden yine “gecti” dedi.
  • Davranış odaklı geliştirme kavramı M21/K02’de kuruldu; bu derste eklenen tek soru, senaryo metni ile adım haritasındaki karşılığı arasındaki eşleşmeydi.

Sonraki Adım

Bu payın beş dersinde testin gördüğü sınıf yolunu biz kurduk: hangi tipin, hangi adımın o yolda duracağını baştan biliyorduk, çünkü hepsini kendi ellerimizle yazdık — sahte tipin adını da, adım haritasının anahtarlarını da testi yazan taraf belirledi. Sıradaki konu bunun tam tersini soruyor. Bir günlükleme çağrısı, kendisini karşılayacak gerçekleştirimin adını hiç yazmaz — kaynak metinde yalnız bir cepheye seslenir, hangi sınıfın o çağrıyı karşılayacağını belirtmez. O adın koşum sınıf yoluna nasıl girdiği, kim koyduğu ve iki gerçekleştirim birden bulunduğunda hangisinin seçildiği, sıradaki konunun sorusu.

İ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