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-veri —
GercekServis’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.