Ders 07 / 14
Sahte Nesneler
Dış kaynağa uzanan aynı test üç kipte 3 ayrı sonuç verir, sahte nesneyle 1'e iner; ama depodan sapmış bir sahte de 1 ayrı sonuç verir ve beklenti onun yanında yazıldığı için geçer, gerçek kaynakla arasında 2 ayrı sonuç durur ve sözleşme sınaması sapmayı tek pakette bulur.
İçindekiler
Önceki ders düzeneğin paylaşılan durumu her testte yeniden kurarak ayrı karar sayısını ikiden bire indirdiğini ölçtü. Bunu yapabildi, çünkü kurduğu şey kendi elindeydi: bellekteki bir nesne, bir sayaç. Yeniden kurmak bir atama kadar ucuzdu ve sonucu tam olarak belirliyordu.
Testin dokunduğu her şey böyle değildir. Bir çözümleyici, kabul edilen sürümleri öğrenmek için bir paket deposuna sorar; depo bir ağın öbür ucundadır, içeriği bizim denetimimizde değildir ve aynı soruya her zaman aynı yanıtı vermez. Bu dersin sorusu şudur: böyle bir kaynağa uzanan test kaç ayrı sonuç verir, ve yerine bir sahte nesne konduğunda bu sayı neye iner?
Dış Kaynağın Üç Kipi
Ölçümdeki dış kaynak bir depo istemcisidir ve ders içinde modellenmiştir; hiçbir ürün adı geçmez, gerçek bir istek yapılmaz. İstemcinin tek yöntemi vardır: bir paketin yayımlanmış sürümlerini döndürür. Aynı çağrı üç ayrı kipte üç ayrı şey yapar.
Tam. Depo bilinen bütün sürümleri döndürür. Çözümleme ortak tanımın sonucunu verir.
Gecikmiş yansıma. Deponun bir kopyası henüz güncellenmemiştir ve en son sürümü taşımaz. İstek başarılıdır, yanıt geçerlidir, ama eksiktir — ve eksiklik yanıtın içinde görünmez.
Kesinti. Depoya erişilemez ve çağrı bir istisna fırlatır. Çözümleme hiç sonuç üretmez.
Üç kip de gerçek bir kurulumda görülür ve hiçbiri bir kusur değildir; deponun normal işleyişinin parçasıdır. Testin bakış açısından ise üçü tek bir şeydir: aynı test, üç ayrı sonuç.
Üç sayısı ölçümün seçimidir, kaynağın sınırı değil. Gerçek bir depoda kip sayısı daha büyüktür: yeni bir sürüm yayımlanır ve liste uzar, bir sürüm geri çekilir ve liste kısalır, istek sınırlanır ve yanıt gecikir. Bu dersin ölçtüğü sayının anlamı “kaynak tam üç şey yapar” değil, kaynağın kip sayısı ne olursa olsun testin sonuç sayısı ona eşit olur demektir. Test dış kaynağa bağlıysa, kaynağın her ayrı davranışı testin bir ayrı sonucudur.
Sahtenin Konabileceği Yer
Bir sahte nesnenin gerçeğin yerine geçebilmesi bir tasarım koşuluna bağlıdır: gerçeğin dışarıdan verilmiş olması. Ölçümdeki çözümleme işlevi sürüm listesini kendi içinde üretmiyor; kendisine verilen istemciden istiyor. Bu yüzden istemcinin yerine başka bir nesne koymak bir argüman değiştirmekten ibaret.
Aynı işlev depoya doğrudan kendisi sorsaydı — istemciyi kendi içinde kurup kullansaydı — sahte nesnenin konabileceği bir yer kalmazdı. O durumda testin elinde iki seçenek olurdu: gerçek depoya sormak ve üç kipe açık kalmak, ya da çalışma zamanında adı yeniden bağlayıp işlevin gördüğü nesneyi değiştirmek. İkincisi işe yarar ama testi işlevin iç gerçekleştirimine bağlar: işlev istemciyi başka bir adla kurmaya başladığında test, davranış hiç değişmeden düşer.
Bağımlılığın dışarıdan verilmesi Yazılım Kalitesi ve Test müfredatının Birim Testi ve Test Odaklı Geliştirme kursunda “test edilebilir tasarım” olarak kurulmuştu; burada tekrarlanmıyor. Kaydedilecek tek şey şu: sahte nesnenin ayrı sonuç sayısını indirebilmesi, kodun onu kabul edecek biçimde yazılmış olmasına bağlıdır.
Sahte Nesne Neyi Değiştirir
Sahte nesne (mock / stub), gerçek bir bağımlılığın yerine konan ve aynı çağrılara yanıt veren bir nesnedir. Yanıtı testin kendisi belirler; ağ yoktur, gecikme yoktur, kesinti yoktur. Bunun ölçüme etkisi doğrudandır: kaynağın kip sayısı kaç olursa olsun, sahte nesne tek bir yanıt verir ve test 1 ayrı sonuç üretir.
Sahte nesnenin türleri ve hangi durumda hangisinin kullanılacağı Yazılım Kalitesi ve Test müfredatının Birim Testi ve Test Odaklı Geliştirme kursunda kurulmuştur; yordam burada tekrarlanmıyor, atıf yapılıyor. Bu derste ölçülen tek şey yalıtımın kaç ayrı sonucu tekleştirdiğidir.
Ölçümdeki sahte nesne iki iş yapar. Birincisi, yanıtı sabitler: kendisine verilen sürüm sözlüğünü döndürür. İkincisi, aldığı çağrıları kaydeder — kaç kez ve hangi paket için çağrıldığını saklar. Bu ikinci iş, testin yalnız dönen değeri değil, yapılan çağrıyı da sınamasına izin verir; ölçümün son satırı bu kaydı yazıyor.
Yalıtımın Ödettiği
Sahte nesne testi kaynağın kiplerinden kurtarır, ama bir bedel karşılığında: test artık gerçek kaynağa dokunmuyor. Sabitlenen yanıtın gerçek deponun verdiği yanıtla aynı olduğunu söyleyen hiçbir şey kalmaz. Sahte nesne bir kez yazılır, depo ise değişmeye devam eder.
Buna sözleşme sapması denir ve ölçümün ikinci bölümü onu sayıyor. Sahtenin sürüm listesine gerçek depoda bulunmayan bir sürüm eklendiğinde test yine geçer — çünkü testin beklentisi sahtenin yanında, sahteye bakılarak yazılmıştır. Beklenti ile sahte birbirini doğrular ve ikisi birlikte gerçekten uzaklaşır.
Sapmanın iki yönü de olur ve ikisi de sessizdir. Sahte, gerçeğin artık vermediği bir yanıtı vermeye devam edebilir; ya da gerçek, sahtenin hiç öngörmediği bir yanıt vermeye başlayabilir. Birinci durumda test yeşil kalır ve kurulum kırılır; ikinci durumda test yine yeşil kalır ve kurulum, sahtenin hiç denemediği bir yolu izler.
Bu sapmayı görünür kılan düzeneğe sözleşme sınaması denir: aynı çağrılar hem sahteye hem gerçek kaynağa yapılır ve yanıtlar karşılaştırılır. Böyle bir sınama yalıtımı bozar — gerçek kaynağa dokunur, dolayısıyla kaynağın üç kipine yeniden açıktır. Bu yüzden birim testlerin yanında değil, ayrı ve seyrek koşan bir küme olarak tutulur.
Ölçümün Varsayımları
- PT19 — Depo istemcisi ders içinde modellenir; gerçek bir ağ isteği yapılmaz, gerçek paket indirilmez, hiçbir ürün adı yazılmaz.
- PT20 — Kâhin ortak tanımın deposudur: hangi paketin hangi sürümleri taşıdığını biz
yazdığımız için biliyoruz.
DEPOveBILDIRIMdeğiştirilmez. - PT21 — İstemcinin üç kipi vardır: tam, gecikmiş yansıma ve kesinti. Gecikmiş yansıma her paketin en son sürümünü düşürür; kesinti bir istisna fırlatır.
- PT22 — Çözümleme “en yeni” stratejisiyle yapılır ve sürüm listesini yalnız istemciden alır; başka hiçbir kaynağa bakmaz.
- PT23 — Kesinti sonucu “sonuç yok” olarak yazılır ve ayrı bir sonuç sayılır; hata da bir sonuçtur.
- PT24 — Sahte nesne yanıtı sabitler ve çağrıları kaydeder. Kip kavramı sahtede yoktur; üç koşumda da aynı yanıtı verir.
- PT25 — Sapmış sahte,
olcumpaketine gerçek depoda bulunmayan bir sürüm ekler. Öbür iki paket dokunulmadan kalır. - PT26 — Testin beklentisi, kullanılan sahtenin yanında ve ona bakılarak yazılır. Bu, sahte nesneyle çalışan bir takımda olağan yazım biçimidir.
- PT27 — Sözleşme sınaması aynı üç çağrıyı iki kaynağa sorar ve yanıtları paket paket karşılaştırır; sapma bulunan paket sayısını verir.
- PT28 — Süre ölçülmez. Sayılan şey ayrı sonuç, sapma bulunan paket ve sahteye giden çağrı sayısıdır.
Ölçüm
"""Sahte nesne: dis kaynagin uc yaniti ile tek yanit. Istemci ders icinde modellenir. Bolum 1 - gercek istemci uc kipte, sahte nesne tek kipte. Bolum 2 - sahte nesne depodan sapinca ne oluyor. """ DEPO = {"olcum": [(1, 0), (1, 1), (1, 2), (2, 0)], "rapor": [(0, 9), (1, 0), (1, 1)], "ortak": [(3, 0), (3, 1), (3, 2), (4, 0)]} BILDIRIM = {"olcum": ((1, 0), (2, 0)), "rapor": ((1, 0), (2, 0)), "ortak": ((3, 0), (4, 0))} class Kesinti(Exception): """Dis kaynaga erisilemedigini bildirir.""" class GercekIstemci: """Dis kaynak: ayni cagri kipe gore ayri yanit verir.""" def __init__(self, kip): self.kip = kip self.cagri = 0 def surumler(self, paket): self.cagri += 1 if self.kip == "kesinti": raise Kesinti(paket) if self.kip == "gecikmiş yansıma": return DEPO[paket][:-1] return list(DEPO[paket]) class SahteIstemci: """Yanitı sabitleyen sahte nesne; cagriyi da sayar.""" def __init__(self, kaynak=None): self.kaynak = kaynak if kaynak is not None else DEPO self.cagri = 0 self.istenen = [] def surumler(self, paket): self.cagri += 1 self.istenen.append(paket) return list(self.kaynak[paket]) def coz(istemci, strateji="en yeni"): """Ayni bildirimi cozer; surum listesini istemciden alir.""" secim = {} for paket in sorted(BILDIRIM): alt, ust = BILDIRIM[paket] adaylar = [s for s in istemci.surumler(paket) if alt <= s < ust] secim[paket] = adaylar[-1] if strateji == "en yeni" else adaylar[0] return secim def metin(secim): return " ".join(".".join(map(str, secim[p])) for p in ("olcum", "ortak", "rapor")) def dene(istemci): try: return metin(coz(istemci)) except Kesinti: return "sonuç yok" KIPLER = ("tam", "gecikmiş yansıma", "kesinti") print(f"{'koşum':<22s} {'gerçek istemci':>16s} {'sahte nesne':>16s}") gercek, sahte = [], [] for kip in KIPLER: g, s = dene(GercekIstemci(kip)), dene(SahteIstemci()) gercek.append(g) sahte.append(s) print(f"{'kip: ' + kip:<22s} {g:>16s} {s:>16s}") print(f"aynı test, gerçek kaynakla {len(set(gercek))} ayrı sonuç; " f"sahte nesneyle {len(set(sahte))}") print() KAYAN = dict(DEPO, olcum=DEPO["olcum"] + [(1, 3)]) kayan_sahte = SahteIstemci(KAYAN) kayan = dene(kayan_sahte) # beklenti sahtenin yanindan yazilir print(f"{'koşum':<31s} {'sonuç':>12s} {'beklenti':>12s} {'karar':>7s}") for ad, sonuc, beklenti in (("sahte, depoyla aynı", dene(SahteIstemci()), dene(SahteIstemci())), ("sahte, depodan sapmış", kayan, kayan), ("gerçek kaynak, sapmış beklenti", dene(GercekIstemci("tam")), kayan)): print(f"{ad:<31s} {sonuc:>12s} {beklenti:>12s} " f"{('geçer' if sonuc == beklenti else 'düşer'):>7s}") print(f"sapmış sahte ile gerçek kaynak arasında " f"{len({kayan, dene(GercekIstemci('tam'))})} ayrı sonuç var") sozlesme = [p for p in sorted(BILDIRIM) if SahteIstemci(KAYAN).surumler(p) != GercekIstemci("tam").surumler(p)] print(f"sözleşme sınaması aynı üç çağrıyı iki kaynağa sorar ve " f"{len(sozlesme)} pakette sapma bulur: {', '.join(sozlesme)}") print(f"sahte nesne {kayan_sahte.cagri} çağrı aldı, istenen paketler " f"{', '.join(kayan_sahte.istenen)}")
koşum gerçek istemci sahte nesne kip: tam 1.2 3.2 1.1 1.2 3.2 1.1 kip: gecikmiş yansıma 1.2 3.2 1.0 1.2 3.2 1.1 kip: kesinti sonuç yok 1.2 3.2 1.1 aynı test, gerçek kaynakla 3 ayrı sonuç; sahte nesneyle 1 koşum sonuç beklenti karar sahte, depoyla aynı 1.2 3.2 1.1 1.2 3.2 1.1 geçer sahte, depodan sapmış 1.3 3.2 1.1 1.3 3.2 1.1 geçer gerçek kaynak, sapmış beklenti 1.2 3.2 1.1 1.3 3.2 1.1 düşer sapmış sahte ile gerçek kaynak arasında 2 ayrı sonuç var sözleşme sınaması aynı üç çağrıyı iki kaynağa sorar ve 1 pakette sapma bulur: olcum sahte nesne 3 çağrı aldı, istenen paketler olcum, ortak, rapor
Üçten Bire
Üst tablo yalıtımın ödediğini veriyor. Gerçek istemciyle aynı test 3 ayrı sonuç
üretiyor: tam kipte 1.2 3.2 1.1, gecikmiş yansımada 1.2 3.2 1.0, kesintide sonuç yok.
Sahte nesneyle üç koşumun üçü de 1.2 3.2 1.1 veriyor — 1 ayrı sonuç.
Ortadaki satır bu üçlünün en tehlikelisidir ve ayrıca bakılmayı hak eder. Kesinti kendini
duyurur: bir istisna fırlar, koşum kırmızı olur ve nedeni ortadadır. Gecikmiş yansıma
hiçbir şey duyurmaz. İstek başarılıdır, yanıt geçerli bir listedir, çözümleme sorunsuz
tamamlanır ve yalnız rapor paketinin sürümü değişir. Böyle bir farkı yakalayan tek
şey, beklentinin tam olarak yazılmış olmasıdır — testin “bir sonuç döndü” demesi yetmez.
Sayının üçten bire inmesinin anlamı şudur: sahte nesne testi kaynağın kiplerinden kurtardı. Test artık deponun gecikmesine, kesintisine ve içeriğinin değişmesine karşı duyarsız. Kurtardığı şey budur ve azımsanacak bir şey değildir; sıra bağımlılığı gibi, kaynak kipi de kararı kodun dışından değiştiren bir etkendir.
Tekleşen Sonucun Doğruluğu
Alt tablo sahte nesnenin kurtaramadığını veriyor ve önceki dersin uyarısını başka bir yüzeyde tekrarlıyor.
İlk iki satır aynı kararı veriyor: geçer. İkisinin de sahtesi tek bir yanıt döndürüyor, ikisi de 1 ayrı sonuç üretiyor. Ama biri deponun içeriğini taşıyor, öbürü depoda olmayan bir sürüm uyduruyor. Testin geçmesinin nedeni ikisinde de aynı: beklenti, sahteye bakılarak yazıldı. Sahte ne derse beklenti onu yazar ve karşılaştırma kendiliğinden tutar.
Üçüncü satır ise ancak biri gerçek kaynağı çağırırsa var olur. Sapmış beklenti gerçek kaynağa sorulduğunda düşüyor, çünkü aralarında 2 ayrı sonuç duruyor. Bu iki sonuç takımın hiçbir koşumunda karşı karşıya gelmiyor; birim testler sahteyle koşuyor, gerçek kaynak yalnız kurulum sırasında devreye giriyor.
Sözleşme sınaması tam bu boşluğu kapatıyor: aynı üç çağrıyı iki kaynağa soruyor ve
1 pakette sapma buluyor — olcum. Sapmanın tek pakette olması ölçümün ölçeğini de
veriyor: üç paketin ikisinde sahte hâlâ doğru, biri kaymış. Sözleşme sınaması olmadan bu
oran hiç bilinmez, çünkü bilinmesi için gerçek kaynağa sorulması gerekir.
Son satır sahte nesnenin ikinci işini gösteriyor. Sahte 3 çağrı almış ve üç paketi de sormuş. Bu kayıt, dönen değerin ötesinde bir şey sınamayı sağlar: çözümleyici gerçekten her paket için deposuna sordu mu, yoksa bir kısmını önbellekten mi aldı? Dönen değer bu sorunun yanıtını taşımaz, çağrı kaydı taşır.
Çağrı kaydının kendi bedeli de vardır. Çağrı sayısını sınayan bir test, çözümleyicinin nasıl çalıştığına bağlanır: bir gün önbellek eklenir, çağrı sayısı üçten bire iner ve test düşer — oysa çözümleme aynı sonucu vermeye devam etmektedir. Dönen değeri sınayan test böyle bir değişiklikten etkilenmez. Kayıt bu yüzden ancak çağrının kendisi bir sözleşme parçasıysa sınanır: örneğin her paketin ayrı ayrı sorulması bir gereklilikse.
Üç bölümü bir arada okuduğumuzda ölçüm şunu söylüyor. Sahte nesne kaynağın değişkenliğini ortadan kaldırır ve ayrı sonuç sayısını üçten bire indirir; buna karşılık kaynağın kendisiyle uyumu hakkında hiçbir şey söylemez, çünkü gerçek kaynağa hiç sormaz. Yalıtımın tekleştirdiği şey sonuçtur, doğruluk değildir — ve tekleşen sonucun gerçekle bağını kuran tek düzenek, o bağı ayrıca ölçen bir sınamadır.
Özet
- Dış kaynağa uzanan aynı test, kaynağın üç kipinde 3 ayrı sonuç verir: tam yanıt, eksik yanıt ve hata. Üçü de kaynağın olağan işleyişindedir.
- Gecikmiş yansıma üç kipin en sessizidir: istek başarılıdır, yanıt geçerlidir ve yalnız bir paketin seçilen sürümü değişir.
- Sahte nesne yanıtı sabitleyerek sayıyı 1 ayrı sonuca indirir ve testi kaynağın kiplerinden kurtarır; ayrıca aldığı çağrıları kaydederek dönen değerin ötesinde bir sınamaya izin verir.
- Depodan sapmış bir sahte de 1 ayrı sonuç verir ve testi geçirir, çünkü beklenti sahteye bakılarak yazılmıştır; sahte ile gerçek kaynak arasında 2 ayrı sonuç durur.
- Sözleşme sınaması aynı çağrıları iki kaynağa sorar ve sapmayı 1 pakette bulur; yalıtımı bozduğu için birim testlerin yanında değil, ayrı bir küme olarak tutulur.
Sonraki Adım
Buraya kadar sınanan her şey bir test dosyasında yazılıydı: iddia da, düzenek de, sahte nesne de. Oysa bir projenin doğruluk iddiaları yalnız orada durmaz — belgelerde de durur. Bir işlevin belge dizgisinde yazan “şu çağrı şunu döndürür” cümlesi bir iddiadır ve hiçbir koşum onu sınamaz; kod değiştiğinde sessizce yanlışlanır. Sonraki ders bu iddiaları sınanabilir hale getirir ve tek bir şeyi sayar: aynı belge, aynı değişiklikten sonra kaç bayat iddiayı sese çevirir, kaçını sessiz bırakır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.