Ders 05 / 14
Test Çerçeveleri
Çerçeve dört iş yapar ve bunlardan yalnız biri kararı üretir: aynı üç durum iki yazım biçiminde 1 ayrı sonuç verirken, aynı dosya üç keşif kuralında bulunan test bakımından 3 ayrı sonuç verir ve rapor üçünde de aynı kalır.
İçindekiler
Önceki ders ortamı ve bağımlılığı tekleştirdi: yorumlayıcı sürümü sabitlendi, kilit dosyası çözümlemenin kararını yazıya geçirdi ve aynı bildirim artık tek bir sürüm kümesi veriyor. Kurulum tarafında sorulacak soru kalmadı. Ama kurulumun tekleşmesi, o kurulumun üzerinde koşan kodun da her seferinde aynı kararı verdiği anlamına gelmiyor.
Bu dersin sorusu şudur: kodun kendisi her koşumda aynı sonucu mu veriyor, ve bunu bize kim söylüyor? Söyleyen şey bir test çerçevesidir (test framework). Çerçeve bir ürün değil, bir araç sınıfıdır; bu derste hiçbir ürün adı geçmez. Çerçevenin yaptığı işler ders içinde modellenir, standart kitaplığın sunduğu çerçeve ise doğrudan kullanılır.
Çerçevenin Dört İşi
Bir test çerçevesi tek bir iş yapıyormuş gibi görünür — “testleri koşturur” — ama aslında dört ayrı iş yapar ve bunların yalnız biri sonucu belirler.
Keşif. Hangi çağrılabilirlerin test olduğuna karar verir. Bu karar bir adlandırma kuralına, bir dizin desenine ya da bir işaretlemeye dayanır ve testin içeriğine bakmaz. Kuralın kaynağı çerçevenin varsayılanı olabilir, projenin ayarı da olabilir; her iki durumda da kural testin dışındadır.
Koşum. Keşfedilen her testi çağırır, sırayı belirler, gerekirse hazırlık ve toplama adımlarını araya yerleştirir. Sıranın kendisi de bir karardır ve sonraki dersin ölçtüğü şeydir.
İddia. Testin içinde beklenen ile gerçekleşeni karşılaştırır. Python’da bunun en yalın
biçimi assert deyimidir; deyim yanlışlanınca AssertionError fırlatır ve çerçeve bu
istisnayı düşen test olarak sayar. İstisnanın akış denetimi olarak kullanılması Python
Temelleri kursunda kurulmuştu; burada aynı düzenek raporlama için kullanılıyor.
Rapor. Kaç testin koştuğunu, kaçının geçtiğini ve düşenlerin adını yazar; okuyan kişi kararı bu satırlardan alır.
Bu dört işten yalnız iddia doğruluk hakkında bir şey söyler. Keşif, koşum ve rapor yapılandırma kararlarıdır — ve bu dersin ölçtüğü şey, yapılandırma kararlarının sonuca ne yaptığıdır.
Dördü de bir arada durduğu için tek bir iş gibi görünürler. Oysa yalnız iddia projenin kaynak dosyalarında yazılıdır; keşif kuralı ve koşum düzeni çoğu kurulumda ayrı bir yapılandırma dosyasında ya da koşturma komutunun seçeneklerinde durur. Yani kararın bir parçası kodun içinde, bir parçası dışındadır — ve dışarıda duran parça değiştiğinde kod dosyalarında hiçbir iz kalmaz. Bu kursun sorusu bakımından bu ayrım tam olarak aranan şeydir: aynı kaynak, ayrı yapılandırma, kaç ayrı sonuç.
İddianın Kâhine İhtiyacı
Bir iddia iki şeyi karşılaştırır: kodun ürettiği değer ile beklenen değer. Beklenen değerin nereden geldiği testin kendi içinde görünmez; onu yazan kişi bir yerden almıştır. Bu kaynağa kâhin (oracle) denir ve kavram Yazılım Kalitesi ve Test müfredatının Kalite ve Test Temelleri kursunda kurulmuştur. Burada kuram tekrarlanmıyor; kâhinin ölçüme ne kattığı ölçülüyor.
Ortak kurgumuzda kâhin bizizdir: doğru indirim davranışını, temiz ortamı ve doğru sürüm kümesini biz yazdık. Bu dersin test durumları ise kâhinden değil, işlevin gözlenen davranışından yazılmıştır. Yani beklenen değerler, işlev bugün ne veriyorsa o yazılarak elde edildi. Böyle yazılmış bir test, işlevin davranışının değişmediğini doğrular; davranışın doğru olduğunu doğrulamaz. Aradaki farkın ne ödettiği bu konunun son dersinde ölçülecek; şimdilik şunu saptamak yeterli: bir testin geçmesi, beklenen değerin doğru olduğunu söylemez.
İddianın yazılışı da bir seçimdir. assert deyimi bir tümce yanlışlanınca AssertionError
fırlatır ve raporda yalnız “bu satırdaki deyim yanlışlandı” bilgisi kalır. Çerçevenin
karşılaştırma yöntemleri ise iki değeri de yakalar ve rapora yazar. Bu fark tanı süresini
kısaltır ama kararı değiştirmez; ölçümün üst tablosu tam olarak bunu gösteriyor.
İddiasız Test Sessizce Geçer
Modellenen koşucunun kuralı basit bir kuraldır: testi çağır, AssertionError gelmezse
geçmiş say. Bu kuralın doğrudan bir sonucu vardır — hiçbir iddia içermeyen bir test her
zaman geçer. Koşucu iddiayı saymaz, istisnayı sayar; iddia hiç yazılmamışsa fırlatılacak
istisna da yoktur.
Bu, çerçevenin bir kusuru değil, sınırıdır. Çerçeve testin gövdesine bakmaz; baksaydı,
hangi çağrının iddia sayılacağını bilmesi gerekirdi ve bu bilgi uygulamaya özgüdür.
Ölçümdeki yardimci_tutar bu sınırın somut örneğidir: bir iddia taşımaz, dolayısıyla
keşfedilseydi de geçmiş sayılırdı. Aynı şey, iddiası yanlışlıkla yorum satırına alınmış ya
da bir koşulun içinde kalıp hiç değerlendirilmeyen bir test için de geçerlidir.
Sonuç şudur: geçen bir test, en az iki ayrı nedenden geçmiş olabilir — iddia doğrulandı ya da hiç iddia yoktu. Rapor bu ikisini ayırmaz. Ayrım için bakılacak yer rapor değil, testin kendisidir; ve bunu kodun üzerinden ölçen düzenek bu konunun son dersinde kurulacak.
Aynı Üç Durum, İki Yazım Biçimi
Birim testin iki yaygın yazım biçimi vardır. Birincisinde her test bir işlevdir ve iddia
assert deyimiyle yazılır; koşucu işlevi çağırır, istisna yakalarsa testi düşmüş sayar.
İkincisinde testler bir sınıfın yöntemleridir ve iddia, çerçevenin sunduğu karşılaştırma
yöntemleriyle yazılır; standart kitaplığın unittest çerçevesi bu biçimi kullanır.
Aradaki fark bir yazım farkıdır. İkinci biçim düşen bir iddiada iki değeri de rapora yazar, birincisi yalnız deyimin yanlışlandığını söyler. Bu bir rapor ayrıntısıdır ve kararın kendisini değiştirmez — ölçüm bunu gösteriyor.
Sınıf tabanlı biçimin ikinci bir katkısı vardır: her testin öncesinde ve sonrasında çalışan kancalar tanımlanabilir. Bu kancalar tekrarı testten çıkarmanın bir yoludur ve sonraki dersin konusudur; burada yalnız üç yalın test yazıldığı için kanca kullanılmıyor ve iki biçim gerçekten aynı işi yapıyor. Karşılaştırmanın anlamlı olması için de bu gerekiyordu: iki biçim ancak aynı üç durumu aynı hazırlıkla koşturduğunda yan yana konabilir.
Keşif Kuralı Neyi Belirler
Keşif kuralı bir dosyadaki hangi çağrılabilirlerin test sayılacağını belirler. Kural
değiştiğinde dosyanın içeriği değişmez, koşan test kümesi değişir. Ölçümdeki dosya beş
çağrılabilir taşır: üçü t_ önekli, biri kontrol_ önekli, biri de yardımcı bir işlev.
Bu üç kural gerçek bir durumu modelliyor. Bir takım büyüdükçe testler birden çok kişi
tarafından yazılır ve herkes aynı adlandırma alışkanlığını taşımaz; bir kısmı test_,
bir kısmı t_, bir kısmı da kontrol_ gibi bir önek kullanır. Keşif kuralı bunlardan
yalnız birini arıyorsa öbürleri dosyada durur, gözle görünür, sürüm denetiminde
izlenir — ve hiç koşmaz.
Burada kritik olan şudur: koşmayan bir test düşmüş sayılmaz. Çerçeve onu hiç görmediği için raporda yeri yoktur. Rapor “başarısız yok” der ve bu cümle hem beş testin koştuğu durumda hem de hiçbirinin koşmadığı durumda aynıdır. Keşif kuralı bu yüzden bir ayrıntı değil, kararın bir parçasıdır.
Ölçümün Varsayımları
- PT1 — Koşucu ders içinde modellenir: her testi çağırır,
AssertionErroryakalarsa testi düşmüş sayar, başka bir şey yapmaz. Ürün adı taşıyan hiçbir çerçeve kullanılmaz. - PT2 — Sınıf tabanlı takım standart kitaplığın
unittestçerçevesiyle koşturulur; rapor akışı yutulur ve yalnız sayılar okunur. - PT3 — Üç test durumu ortak tanımın test kümesidir ve beklenen değerler işlevin gözlenen davranışından yazılmıştır, kâhinden değil.
- PT4 — İndirim işlevinin davranışı değiştirilmez; ders yalnız test ve keşif kuralı ekler.
- PT5 — Keşif kuralı bir ad önekidir ve testin gövdesine bakmaz. Üç kural denenir:
t_öneki,test_öneki, vet_ilekontrol_öneklerinin birleşimi. - PT6 — Dosyadaki
yardimci_tutarbir test değil, bir yardımcıdır; hiçbir kural onu toplamaz ve toplasaydı iddia içermediği için geçmiş sayılırdı. - PT7 — Ayrı sonuç sayısı, bir ölçüm boyunca gözlenen birbirinden farklı sonuç demetlerinin sayısıdır; koşumda süre ölçülmez.
- PT8 — Rapor yalnız düşen test sayısını taşır; koşmayan testin adı raporda yoktur ve raporu okuyan bunu göremez.
Ölçüm
"""Cerceve dort is yapar: kesif, kosum, iddia ve rapor. Kosucu ders icinde modellenir. Bolum 1 - ayni uc durum, iki yazim bicimi. Bolum 2 - ayni dosya, uc kesif kurali. """ import io import unittest def indirim(tutar, uye, kupon): """Ortak tanimin uc kararli islevi; davranisi degistirilmez.""" oran = 0 if uye: oran += 10 if kupon: oran += 15 if oran > 20: oran = 20 return tutar - tutar * oran // 100 DURUMLAR = (("yalniz_uye", (100, True, False), 90), ("yalniz_kupon", (100, False, True), 85), ("ikisi_birden", (100, True, True), 80)) def kosucu(testler): """Modellenmis kosucu: her testi cagirir, gecen ve duseni sayar.""" gecen, dusen = 0, [] for ad, islev in testler: try: islev() except AssertionError: dusen.append(ad) else: gecen += 1 return gecen, dusen def duz_takim(): def uret(arg, beklenen): def test(): assert indirim(*arg) == beklenen return test return [(ad, uret(arg, bek)) for ad, arg, bek in DURUMLAR] class SinifTakimi(unittest.TestCase): def test_yalniz_uye(self): self.assertEqual(indirim(100, True, False), 90) def test_yalniz_kupon(self): self.assertEqual(indirim(100, False, True), 85) def test_ikisi_birden(self): self.assertEqual(indirim(100, True, True), 80) def sinif_kosumu(): takim = unittest.TestLoader().loadTestsFromTestCase(SinifTakimi) sonuc = unittest.TextTestRunner(stream=io.StringIO(), verbosity=0).run(takim) return sonuc.testsRun - len(sonuc.failures) - len(sonuc.errors), len(sonuc.failures) def t_yalniz_uye(): assert indirim(100, True, False) == 90 def t_yalniz_kupon(): assert indirim(100, False, True) == 85 def t_ikisi_birden(): assert indirim(100, True, True) == 80 def kontrol_iki_yuz(): assert indirim(200, True, True) == 160 def yardimci_tutar(tutar): return tutar DOSYA = {"t_yalniz_uye": t_yalniz_uye, "t_yalniz_kupon": t_yalniz_kupon, "t_ikisi_birden": t_ikisi_birden, "kontrol_iki_yuz": kontrol_iki_yuz, "yardimci_tutar": yardimci_tutar} KURALLAR = {"t_ öneki": ("t_",), "test_ öneki": ("test_",), "t_ ya da kontrol_": ("t_", "kontrol_")} def kesif(kural): return [(ad, i) for ad, i in DOSYA.items() if ad.startswith(kural)] print(f"{'yazım biçimi':<22s} {'test':>5s} {'geçen':>6s} {'düşen':>6s}") duz_gecen, duz_dusen = kosucu(duz_takim()) print(f"{'düz işlev takımı':<22s} {len(DURUMLAR):5d} {duz_gecen:6d} {len(duz_dusen):6d}") sinif_gecen, sinif_dusen = sinif_kosumu() print(f"{'sınıf tabanlı takım':<22s} {len(DURUMLAR):5d} {sinif_gecen:6d} {sinif_dusen:6d}") print(f"aynı üç durum, iki yazım biçimi: " f"{len({(duz_gecen, len(duz_dusen)), (sinif_gecen, sinif_dusen)})} ayrı sonuç") print() print(f"{'keşif kuralı':<18s} {'bulunan':>8s} {'geçen':>6s} {'düşen':>6s} {'rapor':>16s}") bulunan, rapor = [], [] for ad, kural in KURALLAR.items(): testler = kesif(kural) gecen, dusen = kosucu(testler) bulunan.append(len(testler)) rapor.append(len(dusen)) print(f"{ad:<18s} {len(testler):8d} {gecen:6d} {len(dusen):6d} " f"{'başarısız ' + str(len(dusen)):>16s}") print(f"aynı dosya, üç keşif kuralı: bulunan test {len(set(bulunan))} ayrı sonuç, " f"rapor {len(set(rapor))} ayrı sonuç") print(f"en dar kuralda hiç koşmayan test {max(bulunan) - min(bulunan)} tane; " f"rapor bunu söylemiyor")
yazım biçimi test geçen düşen düz işlev takımı 3 3 0 sınıf tabanlı takım 3 3 0 aynı üç durum, iki yazım biçimi: 1 ayrı sonuç keşif kuralı bulunan geçen düşen rapor t_ öneki 3 3 0 başarısız 0 test_ öneki 0 0 0 başarısız 0 t_ ya da kontrol_ 4 4 0 başarısız 0 aynı dosya, üç keşif kuralı: bulunan test 3 ayrı sonuç, rapor 1 ayrı sonuç en dar kuralda hiç koşmayan test 4 tane; rapor bunu söylemiyor
Sayıların Okunması
Üstteki tablo yazım biçiminin ödettiğini veriyor: aynı üç durum, iki ayrı yazımda 1 ayrı sonuç. Düz işlev takımı da sınıf tabanlı takım da 3 testin 3’ünü geçiriyor. Yazım biçimi bir tercihtir ve kararı değiştirmez. Bu, çerçeveler arasında seçim yapmanın önemsiz olduğu anlamına gelmez — ama seçimin ödettiği şey sonuç değil, raporun ayrıntısıdır.
Alttaki tablo yapılandırmanın ödettiğini veriyor ve sayı değişiyor. Aynı dosya, üç keşif kuralı: bulunan test 3 ayrı sonuç (3, 0 ve 4), rapor ise 1 ayrı sonuç. Üç kuralın üçünde de rapor “başarısız 0” diyor.
İkinci satır bu ölçümün asıl bulgusudur. test_ önekini arayan kural bu dosyada hiçbir
test bulamıyor, sıfır test koşuyor ve raporu “başarısız 0” oluyor. Bu rapor, üç testin
geçtiği rapordan ayırt edilemez. Bir takımın yeşil olması iki ayrı durumda doğrudur:
testler koştu ve geçti, ya da hiç test koşmadı.
Üçüncü satır aynı olguyu öbür yönden gösteriyor. kontrol_iki_yuz adlı test dosyada
duruyor, iddiası doğru ve geçiyor — ama yalnız t_ önekini arayan varsayılan kural onu
hiç toplamıyor. En dar kural ile en geniş kural arasındaki fark 4 test, ve bu farkın
tamamı raporun dışında kalıyor.
Buradan çıkan kural şudur: bir test takımının kararı, testlerin kendisi kadar keşif kuralına da bağlıdır. Rapor koşan testleri sayar, koşmayanları değil; bu yüzden raporun “başarısız 0” satırı tek başına bir kanıt değildir. Kanıtın tamamı için bulunan test sayısının da okunması gerekir — ve bu sayı, ölçümün gösterdiği gibi, kodun kendisi hiç değişmeden üç ayrı değer alabilir.
İki tablo birlikte okunduğunda kursun ölçü ekseni burada ilk kez görünür hale geliyor. Aynı girdiye — aynı üç durum, aynı işlev, aynı dosya — iki ayrı soru soruldu. Birincisi “nasıl yazdın” sorusuydu ve sonucu değiştirmedi: 1 ayrı sonuç. İkincisi “neyi koşturuyorsun” sorusuydu ve sonucu değiştirdi: 3 ayrı sonuç. Yeniden üretilebilirliği tehdit eden şey birincisi değil ikincisidir, çünkü ikincisi bir dosyada değil, koşturmayı yapılandıran ayarda yazılıdır ve kodla birlikte okunmaz.
Bu ayrımın pratikteki karşılığı da açıktır. Bir takımın yeşil raporu, ancak koşan test sayısı da bilindiğinde bir bilgi taşır; sayı bilinmeden okunan yeşil rapor, hiçbir testin koşmadığı bir kurulumda da aynı görünür. Bu yüzden koşan test sayısı raporun yanında tutulur ve düşmesi, düşen test kadar dikkat çeken bir olay sayılır.
Özet
- Bir test çerçevesi dört iş yapar: keşif, koşum, iddia ve rapor. Bunlardan yalnız iddia doğruluk hakkında bir şey söyler; öbür üçü yapılandırma kararıdır.
- İddia, beklenen değeri bir kâhinden alır. Testin geçmesi, o beklenen değerin doğru olduğunu göstermez.
- Aynı üç durum düz işlev takımıyla da sınıf tabanlı takımla da 3/3 geçer; iki yazım biçimi 1 ayrı sonuç verir ve fark yalnız raporun ayrıntısındadır.
- Aynı dosya üç keşif kuralında 3, 0 ve 4 test bulur — 3 ayrı sonuç; rapor üçünde de “başarısız 0” der ve 1 ayrı sonuçta kalır.
- Hiç koşmayan test düşmüş sayılmaz; en dar kuralda 4 test raporun dışında kalır ve yeşil bir rapor bunu söylemez.
Sonraki Adım
Keşif kuralı hangi testlerin koşacağını belirledi; ama koşan testler tek tek bağımsız değil. Ortak kurgunun beş testlik takımında biri paylaşılan durumu kirletiyor, biri de kirlenmemiş bir durum varsayıyor. Böyle bir takımda testleri hangi sırayla çağırdığınız bir yapılandırma ayrıntısıdır — peki bu ayrıntı kararı değiştiriyor mu? Sonraki ders aynı takımı üç ayrı sırada koşturur, çıkan ayrı karar sayısını okur ve tekrarı testten çıkaran iki düzeneği ölçer: paylaşılan durumu her testte yeniden kuran düzenek ve aynı gövdeyi birden çok girdiyle koşturan parametreleme.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.