İçeriğe geç
academia.sh

Ders 15 / 19

Katman Testleri

Dilim testi ile tam bağlam testi arasındaki fark bir sıfat değil bir sayı; aynı kaptan geçen iki kurulum, ne kadar bileşen kurdukları sayılarak yan yana konur. Sınırlayıcı ölçüm, dilimin dışında kalan bir bileşenin kurulum açıklaması hiç çalışmasa bile dilim testinin sonucunun değişmediğini ve testin yine geçtiğini gösterir.

İçindekiler

Önceki ders belirtecin taşıdığı iddiaları ve doğrulamanın kaynağını tek bir çağrı üzerinden ölçtü. Bu ders geri çekilip soruyu büyütüyor: bir kabın gerçekte kaç bileşeni ayağa kaldırdığı, ve bir testin bu bileşenlerin hangisini gerçekten kurduğu. İki test kurulumu modelleniyor — dilim testi ve tam bağlam testi — ve aralarındaki fark bir sıfatla değil bir sayıyla ölçülüyor: kabın kurduğu bileşen sayısı. Dilim testi kaç bileşen ister, tam bağlam testi kaç bileşen kurar, ve dilimin dışında kalan bir şey bozulduğunda dilim testi bunu fark eder mi? Katman testlerinin kapsamı ve amacı M21/K03 Tümleşim, Sözleşme ve Uçtan Uca Test ile M08/K05 Derleme, Test ve Ekosistem kurslarında kuruldu ve burada tekrarlanmıyor; bu ders yalnız iki kurulumun kabın gözünden ne kadar farklı olduğunu sayıyla gösteriyor.

“Katman” sözcüğü burada web-and-data-layer konusundaki katmanlara işaret ediyor — denetleyici, servis, veri erişimi. Bir dilim testi genellikle bu katmanlardan yalnız birini hedefliyor ve altındaki ya da üstündeki katmanı bir sahte gerçekleştirimle değiştiriyor; bu ders o değişimin nesne kabı üzerinden nasıl yapıldığını ve sonucun nasıl sayılabildiğini gösteriyor.

İki Kurulumu Yan Yana Koymak: Bileşen Sayısı

Modellenen uygulama dört parçadan kuruluyor: bir veri deposu arayüzü ve onun gerçek gerçekleştirimi, bir servis arayüzü ve onun gerçek gerçekleştirimi, bir denetleyici bu servise bağımlı, ve servisle hiç ilgisi olmayan bağımsız bir günlük kaydedici. Aynı Kap sınıfı framework-core konusunda kurulan nesne kabıyla aynı düzenek — yapıcı bağımlılıklarını yansımayla çözüyor, @Kur işaretli yöntemleri kurulumdan sonra çağırıyor — burada yalnız bu derste kullanılan parçası yeniden yazılıyor.

// Kur.java — kurulumdan sonra cagrilacak yontemi isaretleyen aciklama tipi (yardimci kaynak, main yok)
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
@interface Kur { }
// Kap.java — yapici bagimliliklarini yansimayla cozen, kurdugu her bilesenin sayisini tutan kucuk bir nesne kabi (yardimci kaynak, main yok)
import java.lang.reflect.*;
import java.util.*;

class Kap {
    final Map<Class<?>, Class<?>> baglar = new HashMap<>();
    final List<Object> kurulanlar = new ArrayList<>();

    Kap bagla(Class<?> arayuz, Class<?> gerceklestirim) { baglar.put(arayuz, gerceklestirim); return this; }

    @SuppressWarnings("unchecked")
    <T> T kur(Class<T> tip) throws Exception {
        Class<?> gercek = tip.isInterface() ? baglar.get(tip) : tip;
        Constructor<?> yapici = gercek.getDeclaredConstructors()[0];
        Class<?>[] parametreTipleri = yapici.getParameterTypes();
        Object[] argumanlar = new Object[parametreTipleri.length];
        for (int i = 0; i < argumanlar.length; i++) argumanlar[i] = kur(parametreTipleri[i]);
        yapici.setAccessible(true);
        Object ornek = yapici.newInstance(argumanlar);
        for (Method m : gercek.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Kur.class)) { m.setAccessible(true); m.invoke(ornek); }
        }
        kurulanlar.add(ornek);
        return (T) ornek;
    }

    static void goster(String etiket, Object deger) {
        System.out.printf("%-52s: %s%n", etiket, deger);
    }
}

Uygulamanın dört bileşeni ayrı dosyalarda duruyor. GercekServis bir kurulum kancası taşıyor — durum alanı kurulmadi ile başlıyor, @Kur işaretli hazirla yöntemi çağrıldığında hazir’e dönüyor. SahteServis ise dilim testinin elinde tuttuğu, gerçek servisin yerine geçen bir sahte — hiçbir kurulum kancası yok, çünkü ihtiyaç da yok. Yapıcısı da boş: GercekServis bir VeriDeposu istiyor, SahteServis hiçbir şey istemiyor. Bu fark de dilim testinin bileşen sayısını küçültüyor — SahteServis’in kurulması BellekDeposu’nu hiç tetiklemiyor, çünkü çözülecek bir bağımlılığı yok.

// VeriDeposu.java — veri erisiminin arayuzu (yardimci kaynak, main yok)
interface VeriDeposu { String oku(); }
// BellekDeposu.java — veri deposunun gercek gerceklestirimi (yardimci kaynak, main yok)
class BellekDeposu implements VeriDeposu {
    public String oku() { return "veri"; }
}
// Servis.java — is mantiginin arayuzu (yardimci kaynak, main yok)
interface Servis { String islemYap(); }
// GercekServis.java — servisin gercek gerceklestirimi; kurulum kancasi calisana kadar durumu 'kurulmadi' (yardimci kaynak, main yok)
class GercekServis implements Servis {
    final VeriDeposu depo;
    String durum = "kurulmadi";

    GercekServis(VeriDeposu depo) { this.depo = depo; }

    @Kur void hazirla() { durum = "hazir"; }

    public String islemYap() { return durum + ":" + depo.oku(); }
}
// SahteServis.java — dilim testinin elinde tuttugu sahte gerceklestirim, hicbir kurulum kancasi tasimiyor (yardimci kaynak, main yok)
class SahteServis implements Servis {
    public String islemYap() { return "sahte-veri"; }
}
// Denetleyici.java — servise bagimli, dilim testinin asil hedefi (yardimci kaynak, main yok)
class Denetleyici {
    final Servis servis;
    Denetleyici(Servis servis) { this.servis = servis; }
    String yanit() { return servis.islemYap(); }
}
// GunlukKaydedici.java — servisle hicbir bagimliligi olmayan, tam baglamin bagimsiz bir parcasi (yardimci kaynak, main yok)
class GunlukKaydedici {
    String durum() { return "hazir"; }
}

İki ayrı nesne kabı kuruluyor: dilimKabi, Servis’i SahteServis’e bağlıyor; tamKabi, aynı arayüzü GercekServis’e bağlıyor. İkisi de aynı Denetleyici’yi istiyor, ikisi de aynı kur yöntemini çağırıyor — fark yalnız bağlama tablosunda.

// BilesenSayisi.java — ayni Denetleyici, iki ayri kaptan istendiginde kac bilesen kuruluyor
public class BilesenSayisi {
    public static void main(String[] args) throws Exception {
        Kap dilimKabi = new Kap()
                .bagla(Servis.class, SahteServis.class)
                .bagla(VeriDeposu.class, BellekDeposu.class);
        Denetleyici dilimDenetleyici = dilimKabi.kur(Denetleyici.class);

        Kap tamKabi = new Kap()
                .bagla(Servis.class, GercekServis.class)
                .bagla(VeriDeposu.class, BellekDeposu.class);
        Denetleyici tamDenetleyici = tamKabi.kur(Denetleyici.class);
        tamKabi.kur(GunlukKaydedici.class);

        Kap.goster("dilim testi, kurulan bilesen sayisi", dilimKabi.kurulanlar.size());
        Kap.goster("tam baglam testi, kurulan bilesen sayisi", tamKabi.kurulanlar.size());
        Kap.goster("dilim testi, Denetleyici.yanit()", dilimDenetleyici.yanit());
        Kap.goster("tam baglam testi, Denetleyici.yanit()", tamDenetleyici.yanit());
    }
}
dilim testi, kurulan bilesen sayisi                 : 2
tam baglam testi, kurulan bilesen sayisi            : 4
dilim testi, Denetleyici.yanit()                    : sahte-veri
tam baglam testi, Denetleyici.yanit()               : hazir:veri

GT12. Dilim testi de tam bağlam testi de aynı Kap sınıfından geçiyor; aralarındaki fark kodun yapısında değil, kabın kurduğu bileşen sayısında ölçülüyor. GT13. Dilim testi sınadığı Denetleyici’nin doğrudan bağımlılığını bile nesne kabı üzerinden bir sahte gerçekleştirimle kuruyor — SahteServis de kurulanlar listesine giriyor — ama toplam bileşen sayısı yine tam bağlam testinden az: iki değil dört. GT14. Tam bağlam testi bağımlılık zincirinin tamamını (GercekServis, BellekDeposu) ve zincire hiç girmeyen bağımsız bileşenleri (GunlukKaydedici) de kuruyor; Denetleyici’nin ihtiyaç duymadığı bir bileşen bile tam bağlamın parçası oluyor, çünkü tam bağlam testi bütün uygulamayı ayağa kaldırıyor.

kur yönteminin kendisi hangi testin hangi kurulumda olduğunu hiç bilmiyor; tek bildiği bağlama tablosu ve yapıcı imzaları. Aynı yöntem, Servis parametresini gördüğünde baglar haritasına bakıyor ve orada ne bulursa onu kuruyor — dilimKabi için SahteServis, tamKabi için GercekServis. Fark testin niyetinde, kodun kendisinde değil. Bu da bileşen sayısının neden önemli olduğunu gösteriyor: dilim testi az bileşen kurduğu için hızlı çalışıyor, çünkü yansımayla kurulan her bileşen bir yapıcı çözümlemesi ve gerekiyorsa bir kurulum kancası çağrısı taşıyor; tam bağlam testi bunların hepsini yapıyor ve dört katına çıkan bileşen sayısı kabaca dört katına çıkan bir kurulum maliyeti demek.

Dilimin Dışında Kalan Açıklama

GercekServis sınıfının kurulum kancası yalnız nesne kabı onu kurduğunda çalışıyor. Aynı sınıf elle new ile kurulursa @Kur işaretli hazirla yöntemi hiç çağrılmıyor — bu, framework-core konusunda kurulan sınırlayıcı okumanın aynısı, burada bir servis sınıfı üzerinde tekrar ölçülüyor.

// DilimDisindaKalan.java — GercekServis'in kurulum kancasi nesne kabi disinda hic calismiyor; dilim testi bundan tamamen bagimsiz
public class DilimDisindaKalan {
    public static void main(String[] args) throws Exception {
        Kap dilimKabi = new Kap()
                .bagla(Servis.class, SahteServis.class)
                .bagla(VeriDeposu.class, BellekDeposu.class);
        Denetleyici dilimDenetleyici = dilimKabi.kur(Denetleyici.class);

        Kap tamKabi = new Kap()
                .bagla(Servis.class, GercekServis.class)
                .bagla(VeriDeposu.class, BellekDeposu.class);

        GercekServis elleKurulan = new GercekServis(new BellekDeposu());
        Servis kapUzerindenKurulan = tamKabi.kur(Servis.class);

        Kap.goster("GercekServis, elle kurulmus, @Kur hic calismadi", elleKurulan.islemYap());
        Kap.goster("GercekServis, nesne kabi uzerinden kurulmus, @Kur calisti", kapUzerindenKurulan.islemYap());
        Kap.goster("dilim testi, Denetleyici.yanit() (GercekServis'ten bagimsiz)", dilimDenetleyici.yanit());
    }
}
GercekServis, elle kurulmus, @Kur hic calismadi     : kurulmadi:veri
GercekServis, nesne kabi uzerinden kurulmus, @Kur calisti: hazir:veri
dilim testi, Denetleyici.yanit() (GercekServis'ten bagimsiz): sahte-veri

İlk iki satır aynı sınıfın iki farklı kuruluş biçimini gösteriyor: elle kurulduğunda kurulmadi:veri, nesne kabı üzerinden kurulduğunda hazir:veri — açıklama yalnız ikincisinde uygulanıyor. GT15. Bir kurulum açıklaması yalnız nesne kabı onu kurduğunda uygulanır; elle kurulan aynı sınıf açıklamayı hiç görmez, çünkü açıklamayı okuyan ve çağıran taraf kabın kendisidir. Üçüncü satır ise dersin asıl noktası: dilim testinin sonucu — sahte-veriGercekServis’in elle mi nesne kabı üzerinden mi kurulduğundan tamamen bağımsız, çünkü dilim testi GercekServis’i hiç görmüyor. GercekServis’in kurulum kancası bozulsa, kaldırılsa, hatta sınıfın kendisi tamamen silinse, dilim testi bundan haberdar olmaz.

GT16. Dilimin dışında kalan bir bileşenin açıklaması hiç uygulanmasa bile dilim testinin sonucu değişmez ve test yine geçer — bu, dersin sınırlayıcı ölçümü. Dilim testi Denetleyici’nin Servis’i doğru çağırıp çağırmadığını doğruluyor; GercekServis’in kurulum kancasının çalışıp çalışmadığını hiç doğrulamıyor, çünkü doğrulama alanının dışında. Daha az bileşen kurmak daha az şey doğrulamak demek — dilim testi hızlı ve odaklı, ama GercekServis’e ait her açıklama, her kurulum kancası, dilimin görüş alanının tamamen dışında kalıyor. Ortak tanımın kusur sınıflaması bu durumu tam karşılıyor: dilim testi açısından GercekServis’in bozulan kurulum kancası ne hata veriyor ne sessiz kalıyor — testin kendisi açısından tümüyle etkisiz, çünkü test o kancanın varlığından habersiz. Bu boşluğu kapatan tek şey tam bağlam testi, çünkü yalnız o kurulum GercekServis’i gerçekten ayağa kaldırıyor ve kancasının çalıştığını kontrol edebiliyor.

İki test türü birbirinin yerine geçmiyor, ikisi farklı sorulara yanıt veriyor:

Soru Dilim testi Tam bağlam testi
Kurulan bileşen sayısı 2 4
Denetleyici’nin Servis’i doğru çağırdığı doğrulanıyor mu evet evet
GercekServis’in kurulum kancası doğrulanıyor mu hayır evet
GunlukKaydedici’nin varlığı doğrulanıyor mu hayır evet

Tablo tek bir sonuca çıkıyor: dilim testinin ucuzluğu bir eksiklikle geliyor, ve bu eksiklik rastgele değil — kabın o testte hiç kurmadığı her bileşen, dilimin görüş alanının dışında kalıyor. Bir kod tabanı yalnız dilim testleriyle korunuyorsa, bileşenler arasındaki gerçek bağlantı hiçbir zaman koşturarak sınanmamış oluyor; bu boşluğu ancak tam bağlam testi, kurduğu bileşen sayısıyla orantılı bir maliyet karşılığında kapatabiliyor.

Pratikte bir kod tabanı ikisini birden tutuyor, ikisini de aynı görevle değil: dilim testleri her değişiklikte sık koşuyor, çünkü az bileşen kurdukları için hızlılar ve Denetleyici gibi tek bir katmanın davranışını hızla doğruluyor; tam bağlam testleri daha seyrek koşuyor, çünkü daha çok bileşen kurdukları için daha yavaşlar, ama karşılığında bileşenler arasındaki gerçek bağlantıyı sınıyor. Bu derste ölçülen sayı — dilim testinde 2, tam bağlamda 4 — küçük bir örnek üzerinde; gerçek bir uygulamada bu oran onlarca ile yüzlerce arasında değişebilir, ve fark büyüdükçe dilim testinin doğrulamadığı yüzey de büyüyor.

Özet

  • Dilim testi ile tam bağlam testi aynı kaptan geçer; aralarındaki fark kabın kurduğu bileşen sayısıyla ölçülür.
  • Dilim testi, sınadığı sınıfın doğrudan bağımlılığını bile bir sahte gerçekleştirimle kurabilir; buna rağmen toplam bileşen sayısı tam bağlam testinden azdır.
  • Tam bağlam testi bağımlılık zincirinin tamamını ve zincire hiç girmeyen bağımsız bileşenleri de kurar.
  • Bir kurulum açıklaması yalnız nesne kabı onu kurduğunda uygulanır; elle kurulan aynı sınıf açıklamayı hiç görmez.
  • Dilimin dışında kalan bir bileşenin açıklaması hiç çalışmasa bile dilim testinin sonucu değişmez ve test yine geçer; daha az bileşen kurmak daha az şey doğrulamak demektir.

Sonraki Adım

Bu ders hangi bileşenlerin kurulduğunu saydı; sıradaki ders hiç kurulmayan, hiç yazılmayan bir şeyi soruyor. Bir uygulamanın çalışır durumda olup olmadığını dışarıdan soran uçlar var, ve bu uçların çoğu kaynakta hiç görünmüyor — ne bir sınıf, ne bir yöntem, ne bir açıklama. Sıradaki ders bu uçların kaç tanesinin hiç yazılmadan var olduğunu ve hangi bilginin dışarıya verildiğini ölçüyor.

İ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