Ders 07 / 12
Parametreli Testler
Tek bir gövde, bildirilen bir veri kümesi üzerinde üç kez çağrılıyor ve her çağrı kendi satırının başarılı ya da düştüğünü kaydediyor. Kurulum döngünün dışına taşınıp bir Depo örneği satırlar arasında paylaşıldığında ise hangi satırın geçtiği veri kümesinin sırasına bağlı hâle geliyor, aynı üç satır ters sırayla farklı bir satırı geçirtiyor.
İçindekiler
Önceki derste tek bir test gövdesi üç ayrı sınıf yoluna karşı koşturuldu, ama her koşumda aynı üç sayı kullanıldı. Bu derste gövde sabit kalıyor, değişen taraf veri: aynı kontrolü üç ayrı girdiyle çalıştırmak için gövdeyi üç kez yazmak yerine, veri kümesini bir bildirim olarak yazıp gövdeyi bir kez kuruyoruz. Soru şu: bir gövde, N satırlık bir veri kümesine karşı çağrıldığında kaç ayrı sonuç üretiyor, kaçı düşüyor, ve düşen satır hangisi olduğunu nereden biliyoruz?
Bu sorunun ikinci yarısı ilk bakışta ayrı görünüyor ama değil: aynı gövdeyi N kez çalıştırmak, N koşumun birbirinden bağımsız olduğunu varsayar. Gövde bir kez yazıldığı için, bir satırın kurulumu bir sonraki satırın kurulumuyla aynı koddan geçer — ve o kodun paylaşılan bir değişkene dokunup dokunmadığı, veri kümesinin kendisinde hiç görünmez. Bu ders önce doğru kurulmuş bir gövdeyle üç bağımsız sonucu sayıyor, sonra aynı veriyi bozuk bir kurulumla koşturup varsayımın ne zaman çöktüğünü gösteriyor.
Veri kümesinin kendisi burada üç satırla sınırlı tutuluyor, ama sayının kendisi önemli değil — önemli olan, N ne olursa olsun aynı gövdenin N kez çağrılabilmesi ve her çağrının kendi sonucunu üretebilmesidir. Üç satır, elle izlenebilecek en küçük sayı olduğu için seçildi; aynı kurulum hatası yüz satırlık bir veri kümesinde de aynı biçimde ortaya çıkar, yalnız izlemesi güçleşir.
Veri Kümesi Bildirimde Durur, Gövde Bir Kez Yazılır
- TS11. Veri kümesi burada bir
recorddizisi olarak bildirilir (Veri[]). Gerçek çerçevelerin veri kaynağını bir dosyadan, bir ek açıklamadan ya da bir yöntem referansından okuma mekanizması burada kurulmaz — veri doğrudan kaynak kodda durur. - TS12. Gövde burada veri dizisinde gezinen bir döngüdür; aynı üç satır aynı sırayla, her biri kendi girdisiyle bir kez çağrılır. Çağrı sayısı veri satırı sayısına eşittir.
Aşağıdaki VERI dizisi üç satır taşıyor; her satır bir başlangıç miktarı, bir azaltma ve
beklenen sonucu bir arada tutuyor. bagimsizKosum gövdesi bu üç satırı sırayla işliyor ve
her satır için kendi Depo örneğini kuruyor — kurulum satırın içinde, döngünün her
turunda yeniden çalışıyor. Aşağıda aynı veri kümesi, kurulumun döngü dışına taşındığı
bozuk bir sürümle de koşturuluyor; ikisi arasındaki fark bu dersin asıl konusu.
// Veriler.java — tek govde, veri kumesince cogaltilan kosum, ve paylasilan durumun sirali bagimliligi
import java.util.*;
public class Veriler {
static class Depo {
private int miktar;
Depo(int baslangic) { miktar = baslangic; }
void azalt(int adet) { miktar -= adet; }
int miktar() { return miktar; }
}
record Veri(int baslangic, int azalt, int beklenen) {}
static final Veri[] VERI = {
new Veri(10, 3, 7),
new Veri(5, 2, 3),
new Veri(8, 8, 0),
};
// -- govde bir kez yazilir, N koşumun her biri kendi Depo'sunu kurar --------------------
static List<String> bagimsizKosum(Veri[] veri) {
List<String> gunluk = new ArrayList<>();
int dusen = 0;
for (int i = 0; i < veri.length; i++) {
Veri v = veri[i];
Depo d = new Depo(v.baslangic());
d.azalt(v.azalt());
boolean gecti = d.miktar() == v.beklenen();
if (!gecti) dusen++;
gunluk.add("satir " + i + ": " + (gecti ? "gecti" : "dustu, gelen=" + d.miktar()));
}
gunluk.add("dusen sayisi: " + dusen);
return gunluk;
}
// -- kurulum yanlislikla dongunun disina alinirsa Depo koşumlar arasinda paylasilir -----
static List<String> paylasilanKosum(Veri[] veri) {
List<String> gunluk = new ArrayList<>();
int dusen = 0;
Depo d = new Depo(veri[0].baslangic());
for (int i = 0; i < veri.length; i++) {
Veri v = veri[i];
d.azalt(v.azalt());
boolean gecti = d.miktar() == v.beklenen();
if (!gecti) dusen++;
gunluk.add("satir " + i + ": " + (gecti ? "gecti" : "dustu, gelen=" + d.miktar()));
}
gunluk.add("dusen sayisi: " + dusen);
return gunluk;
}
public static void main(String[] args) {
System.out.println("-- bagimsiz kosum: her satir kendi Depo'sunu kuruyor --");
bagimsizKosum(VERI).forEach(System.out::println);
System.out.println("-- paylasilan kosum, veri sirasi orijinal --");
paylasilanKosum(VERI).forEach(System.out::println);
Veri[] tersVeri = { VERI[2], VERI[1], VERI[0] };
System.out.println("-- paylasilan kosum, ayni veri, sira tersine cevrildi --");
paylasilanKosum(tersVeri).forEach(System.out::println);
}
}
-- bagimsiz kosum: her satir kendi Depo'sunu kuruyor -- satir 0: gecti satir 1: gecti satir 2: gecti dusen sayisi: 0 -- paylasilan kosum, veri sirasi orijinal -- satir 0: gecti satir 1: dustu, gelen=5 satir 2: dustu, gelen=-3 dusen sayisi: 2 -- paylasilan kosum, ayni veri, sira tersine cevrildi -- satir 0: gecti satir 1: dustu, gelen=-2 satir 2: dustu, gelen=-5 dusen sayisi: 2
bagimsizKosum üç satırın üçünü de geçiriyor. Bu beklenen sonuç, çünkü her satır kendi
Depo’sunu sıfırdan kuruyor — üçü de birbirinden habersiz, birbirinin durumuna dokunmuyor.
Gövde tek bir yerde yazıldı (döngünün gövdesi), ama üç kez çalıştı ve üç ayrı sonuç üretti.
Başarısızlık sayısı (dusen sayisi) ve her satırın kendi durumu (satir i: gecti/dustu)
ayrı ayrı günlüğe geçiyor — bir satır düştüğünde hangi satır olduğu ve dönen değer kayıtta
kalıyor, yalnız toplam bir sayı değil.
Bu, önceki dersteki “bir iddia” fikrinden farklı bir sayma biçimi. Orada bir test bir sonuç
üretiyordu; burada bir gövde N kez çağrılıyor ve N ayrı iddia doğuruyor. Üç satırlık bir veri
kümesi üç ayrı iddia demektir, ve üçünün de bağımsız değerlendirilmesi gerekir — biri
düşerken diğer ikisinin geçmesi hâlâ anlamlı bir sonuçtur, çünkü her satır kendi girdisini
kendi başına sınıyor. Gövdenin bir kez yazılması burada bir kısaltmadır, kaynak metindeki
tekrarı azaltır; ama derlenmiş ya da koşan programda üç ayrı çağrı olduğu gerçeğini
değiştirmez. VERI dizisi ve bagimsizKosum/paylasilanKosum yöntemleri de önceki dersin
kurduğu ayrımın dışında değil — bunların hiçbiri üretim kodunda yazılı değil, ikisi de
yalnız test sınıf yolunda durur.
Paylaşılan Kurulum, Sıraya Bağlı Sonuç
- TS13. Başarısızlığın hangi satırdan geldiği burada bir liste elemanı olarak biriktirilir. Gerçek çerçevelerin ürettiği rapor biçimi (dosya adı, satır numarası, yığıt izi) burada kurulmaz; kayıt yalnız satır indeksi ve gelen değerdir.
- TS14. Paylaşılan durum burada döngünün dışına taşınan tek bir
Depoörneğidir. Kurulumun döngünün neresinde çalıştığı — her turda mı, bir kez mi — satırların birbirinden bağımsız olup olmadığını belirler. - TS15. Sıra bağımlılığı burada aynı veri kümesinin ters çevrilmiş bir kopyasıyla gösterilir. Gerçek bir test takımında satırların (ya da testlerin) çalışma sırası çoğunlukla belgelenmez ve koşumdan koşuma değişebilir; bu ders yalnız iki olası sırayı örnekliyor.
paylasilanKosum aynı üç satırı, aynı iddiayı taşıyor — tek fark, Depo’nun döngünün
dışında, yalnız bir kez kurulmasıdır. Bu, kurulumun her satır için yeniden çalışması
gerekirken yanlışlıkla yalnız ilk satır için çalıştığı bir hatayı modelliyor: d nesnesi
ilk satırın başlangıç değerinden kuruluyor, sonra her satırın azalt çağrısı aynı
nesne üzerinde birikiyor. Orijinal sırada ilk satır geçiyor — çünkü paylaşılan Depo tam
o satırın beklediği başlangıç değeriyle kuruldu — ama ikinci ve üçüncü satır artık kendi
beklediği başlangıç durumunu görmüyor, önceki satırların bıraktığı durumu görüyor ve
ikisi de düşüyor.
Sayıları takip etmek farkı netleştiriyor. Paylaşılan Depo, VERI[0].baslangic() olan
10 ile kuruluyor. Birinci satır azalt(3) çağırıyor, miktar 7 oluyor, beklenen de 7 —
geçiyor. İkinci satır kendi başlangıcı olan 5’i hiç görmüyor; onun yerine birincinin
bıraktığı 7’yi görüyor ve azalt(2) çağırınca miktar 5 oluyor, beklenen 3 olduğu için
düşüyor. Üçüncü satır da kendi başlangıcı olan 8’i değil, ikincinin bıraktığı 5’i görüyor;
azalt(8) çağırınca miktar -3’e düşüyor, beklenen 0’dan uzak. Her satırın kendi verisi
doğru, kendi hesabı doğru — yanlış olan, hangi durumdan başladıklarıdır.
Asıl gösterim son iki koşumda. Aynı üç veri satırı, yalnız sırası tersine çevrilerek tekrar koşturuluyor. Sonuç yine “bir satır geçti, ikisi düştü” biçiminde — ama geçen satır değişti. Orijinal sırada geçen satır, verinin ilk satırıydı (10 → 7); tersine çevrilmiş sırada geçen satır, o sırada ilk gelen satırdır (8 → 0). Veri kümesinin kendisi değişmedi, satırların hiçbirinin içeriği düzenlenmedi — yalnız hangi satırın önce işlendiği değişti, ve bu tek değişiklik hangi satırın geçtiğini baştan aşağı değiştirdi. Bir satırın geçip geçmemesi artık o satırın kendi verisine değil, konumuna bağlı.
Tersine çevrilmiş sırada aynı izleme yapılabilir. Paylaşılan Depo bu kez 8 ile kuruluyor
— tersine çevrilmiş dizinin ilk elemanı olan üçüncü satırın başlangıcı. O satır azalt(8)
çağırıyor, miktar 0 oluyor, beklenen de 0 — geçiyor. Sırada ikinci satır olarak gelen
ikinci veri satırı kendi başlangıcı olan 5’i değil, önceki adımın bıraktığı 0’ı görüyor;
azalt(2) çağırınca miktar -2 oluyor, beklenen 3’ten uzak, düşüyor. Son olarak gelen
birinci veri satırı kendi başlangıcı olan 10’u değil, -2’yi görüyor; azalt(3) çağırınca
miktar -5 oluyor, beklenen 7’den uzak, düşüyor. İki koşumda da yalnızca ilk işlenen
satır geçiyor — çünkü paylaşılan Depo yalnız o satırın beklediği başlangıç değeriyle
kuruluyor — ve geriye kalanlar önceki satırların bıraktığı durumu devralıp düşüyor. Hangi
satırın “ilk işlenen” olduğu ise veri dizisinin sırasına bağlı, satırın kendi içeriğine
değil.
Bu, M21/K02 Birim Testi ve Test Odaklı Geliştirme kursunda kurulan kararsız test kavramının somut bir kaynağıdır: testler arasında (ya da burada olduğu gibi, aynı testin satırları arasında) paylaşılan durum, sonucu çalışma sırasına bağlı kılar. Kavramın kendisi orada kuruldu; bu derste yeni olan, kaynağın nerede durduğudur — kurulumun döngü içinde mi dışında mı çalıştığı, bildirilen veri kümesinin kendisinde görünmeyen bir karardır. İki koşumun günlüğüne bakarak bu kararı geri çıkarmak mümkün değildir; yalnız veri sırası değiştirilip yeniden koşturularak görünür hâle gelir.
Bu yüzden gerçek test çerçevelerinin çoğu, satırların ya da testlerin çalışma sırasını bilinçli olarak karıştırır: iki ayrı koşumun her seferinde aynı sırayı vermesi, paylaşılan durumdan doğan bir hatayı gizli tutar. Sıra her koşumda sabit kalırsa, bu ders göstermiş olduğu ikinci koşumdaki gibi bir sapma hiç ortaya çıkmayabilir — takım “geçti” der, ama yalnız çünkü sıra hep aynıdır. Sıra karıştırıldığında ise aynı hatalı kurulum bazen bir satırı, bazen başka bir satırı geçirir ve toplam başarısızlık sayısı koşumdan koşuma değişir görünebilir. Bu değişkenlik, hatanın belirtisidir — testin kendisinde değil, testler arasında paylaşılan bir durumda aranması gerektiğinin işaretidir.
Buradan çıkan kural şudur: parametreleme bir gövdeyi çoğaltır, ama çoğalan koşumların
birbirinden bağımsız olduğunu garanti etmez. Bağımsızlığı garanti eden şey, her koşumun
kendi kurulumunu kendi başına çalıştırmasıdır — bagimsizKosum’da olduğu gibi. Kurulum bir
kez çalışıp N koşum arasında paylaşıldığında, veri kümesi ne kadar doğru yazılmış olursa
olsun, sonuç veri kümesinin sırasına bağlı hâle gelir. Bir satırın geçmesi artık o
satırın doğru olduğunu değil, o satırın konumunun uygun düştüğünü gösterir.
Hatanın kökeni bir aliaslama sorunudur: iki ayrı satırın iki ayrı Depo beklediği yerde,
tek bir Depo referansı ikisi arasında paylaşılıyor. Kaynak metinde bu paylaşım açıkça
görünmüyor — d adı döngünün her turunda aynı satırda geçiyor, değişen yalnız hangi
Veri kaydının işlendiği. Değişkenin kapsamı (döngünün dışı) ile niyet edilen ömrü
(tek bir satır) burada birbirinden ayrılıyor, ve bu ayrılığı kaynak metinden görmek
mümkün değil; yalnız birden çok satırla, birden çok sırayla koşturarak görülüyor. Bu
gözlem, kursun ölçü sorusunu test verisi düzeyine indiriyor: bir kurulumun “hangi kapsamda”
çalıştığı da, tıpkı bir tipin hangi sınıf yolunda durduğu gibi, kaynak metinde açıkça
yazılmayan ama sonucu belirleyen bir karardır.
Kurulumu döngü dışına almanın bir bedeli olmadığını düşünmek de yanıltıcı olurdu — tam
tersi, o taşımanın kendisi genellikle bir kazanç aramanın sonucudur. bagimsizKosum her
satır için yeni bir Depo kuruyor; bu, N satırlık bir veri kümesinde N ayrı kurulum çağrısı
demektir. Kurulum ağır bir işse — gerçek bir bağlantı açmak, büyük bir yapı kurmak — bu
tekrar maliyetli görünebilir ve kurulumu bir kez yapıp paylaşmak cazip gelir. Bu ders o
cazibenin bedelini gösteriyor: paylaşılan kurulumun kazandırdığı hız, satırlar arasındaki
bağımsızlığı kaybettirdiği için ödenmiş oluyor. İki özelliği aynı anda istemek — hem
paylaşılan hem bağımsız bir kurulum — çelişkilidir; hangisinin öncelikli olduğu, gövdeyi
yazan kişinin kararındadır ve bu ders yalnız o kararın sonucunu ölçüyor.
Bu seçimin doğru yanıtı yoktur, yalnız bilinçli ve bilinçsiz biçimleri vardır. Bir gövde
kurulumu bilinçli olarak paylaşıyorsa — örneğin satırların ortak, değişmeyen bir kaynağı
okuduğu, hiçbirinin yazmadığı durumlarda — paylaşım bir kusur değildir. Kusur, paylaşımın
yazma içeren bir kurulumda, farkında olunmadan gerçekleşmesindedir. Bu dersin
paylasilanKosum yöntemi ikinci durumu modelliyor: kurulum döngü dışına taşınırken hangi
satırın hangi durumu göreceği hiç düşünülmemiş, yalnız tekrarı azaltmak hedeflenmiştir.
Özet
- Veri kümesi bir bildirim olarak yazıldı, gövde bir kez kuruldu ve veri sayısınca — üç kez — çağrıldı; her çağrı kendi satırının sonucunu kaydetti.
- Bağımsız kurulumla üç satırın üçü de geçti; başarısızlık sayısı ve hangi satırdan geldiği ayrı ayrı günlüğe geçti.
- Kurulum döngünün dışına taşınıp tek bir
Deposatırlar arasında paylaşıldığında iki satır düştü, ama hangi satırın geçtiği veri sırasına bağlı hâle geldi. - Aynı üç satır ters sırayla koşturulduğunda geçen satır değişti; veri içeriği aynı kaldı, yalnız işlenme sırası değişti.
- Paylaşılan durumun sonucu sıraya bağlı kılması, M21/K02’de kurulan kararsız test kavramının bu derste somutlaşan bir kaynağıdır.
Sonraki Adım
Bu derste paylaşılan olan şey bir test gövdesinin kendi kurduğu bir nesneydi — sınanan
kodun dışına, testin kendi düzenine ait bir hataydı. Sınanan kodun kendisi (Depo) hiç
değişmedi; değişen yalnız test tarafının kurulumuydu. Sıradaki ders paylaşılan durumu
sınanan kodun dışına taşıyor: birim testinde sahtelenen bir bağımlılık, tümleşim
kurulumunda gerçek gerçekleştirimiyle yerine konuyor ve aynı test gövdesi artık daha büyük
bir sınıf yoluna karşı koşuyor. Kusurun nereye taşındığı — birimde sessiz kalan bir sapma,
tümleşimde nasıl bir hataya dönüşüyor — sıradaki dersin sorusu.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.