İçeriğe geç
academia.sh

Ders 04 / 16

Sahteleme ve Arayüz Ayrımı

Aynı StokAzalt gövdesi hem kayıtlı hem casus bir Bildirici gerçekleştirmesiyle koşturuluyor: normal bir azaltmada ikisi de aynı kararı veriyor ve casus nesnenin çağrı sayısı 1 okunuyor. Sınırlayıcı ölçüm, stoktan fazlasını azaltan bir çağrıda gerçek gerçekleştirmenin negatif kalanı reddettiğini ama casus nesnenin aynı çağrıyı sessizce kaydettiğini gösteriyor.

Yayın Güncelleme

Bu derste

  • Bildirici arayüzünün StokAzalt'ı somut bir gerçekleştirmeye değil bir sözleşmeye bağladığını kaynaktan okur
  • Aynı test gövdesini gerçek ve sahte bir gerçekleştirmeyle koşturup casus nesnenin çağrı sayısını okur
  • Gerçek gerçekleştirmede tutan bir varsayımın sahte gerçekleştirmede hiç sınanmadığını, stoktan fazla azaltma durumuyla sınırlar
İçindekiler

Önceki ders bir HTTP işleyicisinin gerçek bir bağlantı hiç kurulmadan sınanabildiğini ölçtü, ama işleyicinin kendisi hâlâ elle kurulan sabit bir depoya bakıyordu. Bu ders o depoyu, işleyicinin arkasındaki bir başka bağımlılığa — bir bildirim gönderen taraf — taşıyor ve aynı soruyu soruyor: bu bağımlılık gerçek dünyada yavaş, kararsız ya da yan etkili bir şeyse (bir dış sisteme yazmak gibi), bu bağımlılığa dokunmadan aynı mantığı nasıl sınarsınız?

Önceki derste bu soru bir bakıma zaten çözülmüştü — httptest.NewRecorder, standart kitaplığın kendi http.ResponseWriter arayüzünü gerçekleştiren, önceden yazılmış bir sahteydi. Bu ders aynı deseni, standart kitaplığın hazır bir sahtesi olmayan bir bağımlılığa uyguluyor: Bildirici’yi ve onun iki gerçekleştirmesini bu paketin kendisi yazıyor.

Sahte bağımlılık ailesi — sahte nesne, güdük, taklit nesne, casus nesne, kukla nesne — Birim Testi ve Test Odaklı Geliştirme kursunda taksonomisiyle birlikte kuruldu ve burada yeniden tanıtılmıyor; ne taksonomi yeniden anlatılıyor ne de her üye bu derste ayrı ayrı gerçekleştiriliyor. Bu dersin eklediği soru dar: sahtelemeyi mümkün kılan şey nedir ve bu şey kaynakta nerede görünüyor?

Üretim kod tabanlarında bir işlevin çağırdığı her şeyi gerçek hâliyle sınamak çoğu zaman mümkün değil: bir bildirim gönderen taraf gerçek bir dış sisteme yazabilir, bir ödeme sağlayıcısı gerçek parayı hareket ettirebilir, bir depolama katmanı gerçek bir sunucuya bağlanabilir. Bu bağımlılıkların yerine geçen bir sahte, sınamayı bu maliyetlerden kurtarıyor — ama bu kurtuluşun kendi bedeli var ve bu dersin sınırlayıcı ölçümü tam olarak bu bedeli sayıyor. Bedel görünmez kaldığı sürece bir risk taşımıyor; bu dersin amacı bedeli görünür kılmak, sahtelemeyi terk ettirmek değil.

Sahteyi Mümkün Kılan Şey Arayüz Ayrımı

envanter paketine bir işlev daha ekleniyor: StokAzalt, bir kalemin adedini azaltıyor ve kalan miktar belirli bir eşiğin altına düştüğünde bir bildirim gönderiyor — eşik bu derste sabit olarak 2 seçildi. Bildirimi gönderen tarafı StokAzalt doğrudan çağırmıyor — bir Bildirici arayüzü üzerinden çağırıyor.

// envanter.go — StokAzalt somut bir bildirim gonderen tarafa degil bir arayuze bagli
package main

import (
	"errors"
	"fmt"
)

type Kalem struct {
	Ad    string
	Adet  int
	Fiyat int
}

type Bildirici interface {
	Bildir(ad string, kalanAdet int) error
}

func StokAzalt(depo map[string]Kalem, ad string, miktar int, b Bildirici) error {
	k := depo[ad]
	k.Adet -= miktar
	depo[ad] = k
	if k.Adet < 2 {
		return b.Bildir(ad, k.Adet)
	}
	return nil
}

// kayitliBildirici gercek gerceklestirim: negatif kalani reddediyor.
type kayitliBildirici struct {
	kayitlar []string
}

func (k *kayitliBildirici) Bildir(ad string, kalanAdet int) error {
	if kalanAdet < 0 {
		return errors.New("negatif kalan bildirilemez")
	}
	k.kayitlar = append(k.kayitlar, fmt.Sprintf("%s:%d", ad, kalanAdet))
	return nil
}

// casusBildirici sahte gerceklestirim: dogrulama yapmadan cagriyi kaydediyor.
type casusBildirici struct {
	cagrilar int
}

func (c *casusBildirici) Bildir(ad string, kalanAdet int) error {
	c.cagrilar++
	return nil
}

TS10. StokAzalt’ın son parametresi Bildirici — bir yöntem imzasından ibaret bir arayüz, kayitliBildirici ya da casusBildirici gibi somut bir tip değil. Bu, kaynaktan doğrudan okunabilen bir karar: StokAzalt’ı çağıran kod, Bildir yöntemini taşıyan herhangi bir tipi geçirebiliyor ve StokAzalt’ın gövdesi hangi somut tipin geçirildiğini hiç bilmiyor. Sahtelemeyi mümkün kılan tam olarak bu: eğer StokAzalt *kayitliBildirici’yi doğrudan alsaydı, onun yerine başka bir tip geçirmenin hiçbir yolu olmazdı.

envanter paketi bu ders boyunca tek bir yeni bağımlılık kazandı ve bu bağımlılık paketin kendi içinde tanımlandı — Bildirici gerçek bir dış paketten içe aktarılan bir tip değil, StokAzalt’ın ihtiyaç duyduğu tek yöntemi tanımlayan, envanter paketinin kendi arayüzü. Bu, arayüzü tüketen tarafın (burada StokAzalt) kendi ihtiyacına göre küçük bir arayüz tanımlamasının bir örneği: arayüz, bir kitaplığın dışarıya sunduğu geniş bir sözleşme değil, çağıranın gerçekten kullandığı kadarını kapsayan dar bir sözleşme.

Bildirici arayüzü tek bir yöntem taşıyor ve bu darlık kasıtlı: arayüz ne kadar dar olursa, onu gerçekleştiren bir tip yazmak o kadar az iş gerektiriyor. kayitliBildirici ve casusBildirici ikisi de yalnız bu tek yöntemi (Bildir) tanımlayarak arayüzü sağlıyor — Go’da bir tipin bir arayüzü sağladığını ayrıca bildirmek gerekmiyor, yöntem kümesi arayüzün istediği kümeyi kapsıyorsa tip o arayüzü kendiliğinden sağlamış oluyor. Bu, casusBildirici’nin envanter paketinin geri kalanıyla hiçbir bağı olmadan, yalnız Bildir yöntemini taşıdığı için StokAzalt’a geçirilebilmesinin nedeni.

Arayüzün kendisi, gerçek ve sahte gerçekleştirmenin aynı sözleşmeyi taşıdığını garanti ediyor: ikisi de aynı imzayla (Bildir(ad string, kalanAdet int) error) çağrılabiliyor, StokAzalt’ın gövdesi hangisiyle çalıştığını ayırt etmeden aynı satırı (b.Bildir(ad, k.Adet)) çalıştırıyor. Ama bu sözleşme yalnız imzayı garanti ediyor — imzanın arkasındaki davranışı değil. İki gerçekleştirmenin aynı girdiye aynı yanıtı verip vermeyeceği, arayüzün kendisinden hiç okunamıyor; bu sorunun yanıtı aşağıdaki ölçümde ortaya çıkıyor.

Aynı Gövde, İki Gerçekleştirme

StokAzalt’ın gövdesinde Bildirici tipiyle ilgili tek bir satır var: b.Bildir(ad, k.Adet). Bu satırın hangi somut tiple çalıştığı, çağıranın StokAzalt’a hangi değeri geçirdiğine bağlı — işlevin kendisi bu kararı hiç vermiyor. Aşağıdaki ölçüm bu kararı çağıran tarafına bırakarak, aynı işlevi iki kez, iki farklı üçüncü argümanla çağırıyor.

TS11. Aynı StokAzalt çağrısı, önce gerçek gerçekleştirme (kayitliBildirici), sonra sahte gerçekleştirme (casusBildirici) ile koşturuluyor; üç adetlik bir kalemden iki adet azaltılıyor, kalan (1) eşiğin altında olduğu için bildirim tetikleniyor.

// olcum.go — ayni govde gercek ve sahte gerceklestirmeyle kosuyor
package main

import "fmt"

func main() {
	fmt.Println("-- kosum: ayni govde gercek ve sahte ile --")
	depo1 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
	gercek := &kayitliBildirici{}
	hataGercek := StokAzalt(depo1, "kalem", 2, gercek)
	fmt.Println("gercek: hata var mi:", hataGercek != nil, "kayit sayisi:", len(gercek.kayitlar))

	depo2 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
	sahte := &casusBildirici{}
	hataSahte := StokAzalt(depo2, "kalem", 2, sahte)
	fmt.Println("sahte: hata var mi:", hataSahte != nil, "casus cagri sayisi:", sahte.cagrilar)
}
-- kosum: ayni govde gercek ve sahte ile --
gercek: hata var mi: false kayit sayisi: 1
sahte: hata var mi: false casus cagri sayisi: 1

Ölçüm iki ayrı depo değişkeni kuruyor (depo1, depo2), aynı içerikle ama ayrı haritalar olarak — StokAzalt deponun kendisini değiştirdiği için, tek bir depo iki kez kullanılsaydı ikinci çağrı ilk çağrının bıraktığı değişmiş durumdan başlardı ve iki gerçekleştirmenin kararı artık aynı girdiye dayanmazdı. Bu yalıtım, önceki derslerdeki araclar.Kur’un her koşum için tamamen boş bir modül kurmasıyla aynı gerekçeye dayanıyor: bir ölçümün önceki bir ölçümün izini taşımaması.

İki koşumda da StokAzalt‘ın gövdesi bayt bayt aynı; değişen tek şey son argümana geçirilen değer. Gerçek gerçekleştirmede kayıt listesinin uzunluğu 1’e çıkıyor — bu gerçek bir yan etkinin izini tutuyor. Sahte gerçekleştirmede aynı çağrı cagrilar sayacını 1’e çıkarıyor — casus nesnenin ölçtüğü tek şey bu: kaç kez çağrıldığı, ne yaptığı değil. İki ölçüm de aynı sonuca (hata var mi: false) varıyor ve bu ölçümün bu kadarıyla sahtenin gerçeğin yerini tuttuğu söylenebilir.

casusBildirici burada özellikle casus nesne — sahte bağımlılık ailesinin, çağrıları kaydedip sonradan sorgulanmasına izin veren üyesi. Bildir çağrıldığında hiçbir iş yapmıyor, yalnız bir sayacı artırıyor; bu, StokAzalt’ın bildirim tetikleyip tetiklemediğini sınamak için yeterli, çünkü ölçümün sorusu “bildirim gönderildi mi”, “bildirim doğru içerikte mi” değil. Aynı senaryo, çağrı sayısı yerine çağrının argümanlarını da tutan bir casus ile genişletilebilirdi — casusBildirici bu derste en dar hâliyle, yalnız bir sayaçla, tutuluyor çünkü ölçümün sorduğu tek şey bu.

kayitliBildirici adının kendisi de bir seçim: bu tip yalnızca çağrıları kaydeden bir kukla değil, gerçekten karar veren bir gerçekleştirme — negatif bir kalan miktarı reddetme kararı, sahte olmayan, üretim koduna gidecek bir mantık. Bu ders onu “gerçek” diye anıyor çünkü StokAzalt’ın üretimde birlikte çalışacağı asıl gerçekleştirmenin küçük bir modelini kuruyor — kendisi de bu derste hiçbir dış sisteme yazmıyor, ama taşıdığı denetim kuralı gerçek bir dış sistemin taşıyabileceği türden.

Sınırlayıcı Ölçüm: Sahte Gerçekleştirme Daha Az Şey Sınıyor

StokAzalt’ın kendisi, adedi negatife düşürecek bir çağrıyı reddetmiyor — bu bir tasarım kararı: stoktan fazla azaltmanın nasıl ele alınacağı StokAzalt’ın değil, Bildirici’nin sorumluluğuna bırakılmış. Bu, önceki ölçümde görünmeyen bir dal; üç adetlik bir kalemden iki adet azaltıldığında kalan hep pozitif kalıyordu.

TS12. Aynı ölçüm bu kez üç adetlik bir kalemden beş adet azaltarak koşturuluyor — kalan adet negatife düşüyor (-2) ve Bildir bu negatif değerle çağrılıyor.

// sinir.go — gercek gerceklestirmede tutan bir varsayim sahtede hic sinanmiyor
package main

import "fmt"

func main() {
	fmt.Println("-- sinirlayici olcum: stoktan fazla azaltma --")
	depo3 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
	gercek2 := &kayitliBildirici{}
	hataGercek2 := StokAzalt(depo3, "kalem", 5, gercek2)
	fmt.Println("gercek: hata var mi:", hataGercek2 != nil)

	depo4 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
	sahte2 := &casusBildirici{}
	hataSahte2 := StokAzalt(depo4, "kalem", 5, sahte2)
	fmt.Println("sahte: hata var mi:", hataSahte2 != nil, "casus cagri sayisi:", sahte2.cagrilar)
}
-- sinirlayici olcum: stoktan fazla azaltma --
gercek: hata var mi: true
sahte: hata var mi: false casus cagri sayisi: 1

Çağrı sayısı iki gerçekleştirmede de aynı çıkıyor — sahte2.cagrilar yine 1, çünkü Bildir yine tam olarak bir kez çağrılıyor. Değişen şey çağrının sonucu, çağrının kendisi değil; casus nesne çağrının olup olmadığını doğru sayıyor, ama çağrının taşıdığı değerin geçerli olup olmadığına hiç bakmıyor. Bu kez iki gerçekleştirme farklı karar veriyor. kayitliBildirici, gerçek dünyada bir dış sistemin taşıyabileceği bir varsayımı kodluyor: negatif bir kalan miktar bildirilemez, çünkü böyle bir durum dış sistemin gözünde anlamsız. Bu varsayım ihlal edildiğinde gerçek gerçekleştirme bir hata döndürüyor ve StokAzalt’ın kendisi de bu hatayı olduğu gibi çağırana taşıyor. casusBildirici ise hiçbir varsayım taşımıyor — yalnız çağrıldığını kaydediyor ve her zaman başarı bildiriyor; aynı negatif değerle çağrıldığında sessizce cagriları 1 artırıp nil döndürüyor.

Bu, konunun ölçü ekseninin bu derse özgü sınırı: yalnız casusBildirici kullanan bir test takımı, StokAzalt’ın stoktan fazla azaltma durumunu doğru işlediğini hiçbir zaman sınamaz — çünkü casus nesnenin kendisi bu durumu ayırt etmiyor, her çağrıyı aynı biçimde kabul ediyor. Gerçek gerçekleştirmede tutan varsayım, sahte gerçekleştirmede hiç sınanmadan kalıyor; sahteleme testi bu yüzden gerçek testten daha az şey sınıyor, daha fazla değil. Bu bir kusur değil, sahtenin doğası: bir sahte, arkasındaki gerçek sistemin bütün kurallarını değil, yalnız yazan kişinin kodladığı kadarını taşıyor.

Bunun üretim tarafındaki karşılığı somut: yalnız casus nesneyle yazılmış bir test takımı yeşil görünse bile, gerçek dış sistem devreye girdiğinde StokAzalt bu senaryoda bir hata ile karşılaşacak — ve bu hatayı üreten kural, dış sistemin kendi belgelerinde yazılı olabilir ama sahte gerçekleştirmeye hiç taşınmamış olabilir. Bu, Test Paketi Temelleri’nde ölçülen sınırın bir başka biçimi: orada geçen bir test doğru olduğunu değil sınadığı şeyin tuttuğunu söylüyordu, burada geçen bir sahteleme testi de yalnız sahtenin kendi kurallarının tuttuğunu söylüyor — gerçek sistemin kurallarının değil. İki sınır da aynı köke iniyor: bir testin geçmesi, sınamanın kapsamı dışında kalan hiçbir şey hakkında bir iddia taşımıyor. Sahtenin gerçekleştirdiği yöntem sayısı ile gerçek gerçekleştirmenin yöntem sayısı burada aynı (ikisi de yalnız Bildir), ama davranış sayısı aynı değil: gerçek gerçekleştirme iki davranış taşıyor (kabul etme, reddetme), sahte gerçekleştirme yalnız birini (kabul etme). Arayüzün kendisi bu farkı hiç göstermiyor, çünkü arayüz yalnız imzayı zorunlu kılıyor, davranış sayısını değil.

Bu sınırın bir çıkış yolu var, ama bu dersin kapsamı dışında: sahte gerçekleştirmeyi gerçek gerçekleştirmenin varsayımlarına göre periyodik olarak sınayan ayrı bir sözleşme testi yazmak, iki tarafın davranışının birbirinden sapmadığını ayrıca doğrulayabilirdi. Bu derste böyle bir sözleşme testi yok — amaç sahtenin sınırını göstermek, o sınırı kapatan bir çözüm sunmak değil.

casusBildirici bu ölçüm için doğru seçim, çünkü sorulan soru “kaç kez çağrıldı” — sahte bağımlılık ailesinin öbür üyeleri farklı sorulara yanıt vermek için var. Bir güdük sabit bir değer döndürüp hiçbir şeyi kaydetmezdi; bir kukla nesne yalnız parametre yerini doldurur, Bildir hiç çağrılmasa bile fark etmezdi. StokAzalt’ın burada ölçülen davranışı — bildirimin tetiklenip tetiklenmediği ve kaç kez tetiklendiği — tam olarak bir casus nesnenin ölçtüğü tür bir bilgi; bu yüzden beş üyeden bu ders yalnız onu kullandı.

Özet

  • StokAzalt’ın son parametresi somut bir tip değil bir arayüz (Bildirici); bu ayrım kaynaktan doğrudan okunabiliyor ve sahtelemeyi mümkün kılan tam olarak bu — sahteyi mümkün kılan bilgiyi kaynak söylüyor.
  • Aynı StokAzalt gövdesi gerçek (kayitliBildirici) ve sahte (casusBildirici) gerçekleştirmeyle koşturuldu; normal bir azaltmada ikisi de aynı kararı verdi.
  • Casus nesnenin ölçtüğü şey yalnız çağrı sayısı; ne yaptığı değil, kaç kez çağrıldığı.
  • Sınırlayıcı ölçüm: stoktan fazla azaltan bir çağrıda gerçek gerçekleştirme negatif kalanı reddetti, sahte gerçekleştirme aynı çağrıyı sessizce kabul etti — sahteleme testi bu varsayımı hiç sınamadı.
  • Sahte bağımlılık taksonomisi Birim Testi ve Test Odaklı Geliştirme kursunda kuruldu; burada yeniden anlatılmadı, yalnız Go’nun arayüz düzeneği üzerinden ölçüldü.

Sonraki Adım

Bu derste bir arayüzün, bir bağımlılığı gerçek ve sahte gerçekleştirmeler arasında değiştirilebilir kıldığı, ama sahte gerçekleştirmenin gerçeğin bütün varsayımlarını taşımadığı ölçüldü. Bildirici tek yöntemli, dar bir arayüz olarak kaldı; kayitliBildirici kendi denetim kuralını taşıdı, casusBildirici yalnız bir çağrı sayacı tuttu — ve iki gerçekleştirme normal bir azaltmada aynı, aşırı bir azaltmada farklı karar verdi. Buraya kadarki dört ders de tek tek geçti ya da kaldı, tek tek hata verdi ya da vermedi — ama hiçbiri, bu kararların hangi sayısının koşumdan koşuma değiştiğini sormadı. Sıradaki ders bu soruyu soruyor: aynı işin iki yazımı karşılaştırıldığında, hangi sayı beş ayrı koşumda hep aynı kalıyor, hangisi kalmıyor — ve konunun bağlayıcı sayıları da orada kuruluyor.

İ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