Test piramidi tersine döndüğünde ne olur
Altta birkaç birim testi, tepede yüzlerce uçtan uca test. Bu şeklin ekibe maliyeti CI süresinde değil, bir test kırıldığında hatanın nerede olduğunu bulma süresinde ortaya çıkıyor.
Test piramidi eski bir şema: altta çok sayıda hızlı birim testi, ortada daha az entegrasyon testi, tepede birkaç uçtan uca test. Pratikte sık gördüğüm şey bunun tersi. Altta neredeyse hiçbir şey yok, tepede yüzlerce tarayıcı testi var. Şemanın adı külah oluyor, ama asıl mesele isim değil, bu şeklin ekibe ne yaptığı.
Ters piramit önce zamanı yer
Uçtan uca test bir süreç ağı çalıştırır: tarayıcı, uygulama sunucusu, veritabanı, çoğu zaman bir kuyruk. Tek bir iddiayı doğrulamak için hepsinin ayakta olması gerekir. Birim testi aynı iddiayı bir fonksiyon çağrısıyla doğrular.
Aradaki fark milisaniye ile saniye arasında. Yüz test bu farkla çarpıldığında CI süresi dakikalardan on dakikalara çıkar. On dakikalık CI, geliştiricinin pull request açtıktan sonra başka işe geçmesi demektir. Başka işe geçmek, testin sonucunu on beş dakika sonra görmek demektir. Bu noktadan sonra test bir geri bildirim aracı olmaktan çıkar, bir onay kapısına dönüşür.
Sonra teşhis gücünü yer
Asıl kayıp burada. Kırmızıya dönen bir uçtan uca test size şunu söyler: "sepete ekle akışı çalışmıyor." Hangi katmanda çalışmadığını söylemez.
// e2e: kırıldığında hangi katmanın bozulduğu belirsiz
test('indirimli ürün sepete doğru fiyatla ekleniyor', async ({ page }) => {
await page.goto('/urun/42')
await page.click('[data-test=sepete-ekle]')
await expect(page.locator('[data-test=sepet-toplam]')).toHaveText('₺180,00')
})
Bu test kırıldığında suçlu şunlardan biri olabilir: indirim hesabı, para birimi biçimlendirmesi, sepet durumu yönetimi, 42 numaralı ürünün veritabanındaki kaydı, ya da data-test niteliğinin bir yeniden adlandırmada kaybolması. Beşi de aynı kırmızıyı üretir.
Aynı iddianın alt katmandaki karşılığı ise tek bir şeyi söyler:
test('yüzde 10 indirim kuruş bazında aşağı yuvarlanır', () => {
expect(indirimliFiyat(20000, 0.1)).toBe(18000)
})
test('kuruş, virgüllü ve iki basamaklı biçimlenir', () => {
expect(biçimlendir(18000)).toBe('₺180,00')
})
İki testten biri kırıldığında hata nerede olduğunu söylemiş oluyor. Uçtan uca test bunu hiçbir zaman yapamaz, çünkü tanım gereği bütünü ölçer.
Ve en sonda güveni yer
Ters piramitli projelerin ortak semptomu, kırmızı testin tekrar çalıştırılması. "Bazen kırılıyor, yeniden çalıştırınca geçiyor."
Bu davranış bir kez normalleştiğinde test paketi bilgi taşımayı bırakır. Gerçek bir gerilemenin ürettiği kırmızı ile ağ zaman aşımının ürettiği kırmızı ayırt edilemez hale gelir, ve ekip ikisini de aynı refleksle karşılar. Kararsız testlerin oranı belli bir noktayı geçtikten sonra paket, çalıştırma maliyeti olan ama karar üretmeyen bir eklentiye dönüşür.
Kararsızlığın kaynağı çoğunlukla testin kendisi değil, testin bağlı olduğu şeylerin sayısı. Bir test on bileşene bağlıysa, her bileşenin küçük bir kararsızlığı testin toplam kararsızlığında birikir.
Orta kat neden boş kalıyor
Ters piramitte dikkat çeken şey sadece tabanın zayıflığı değil, ortanın hiç olmaması. Entegrasyon testi yazmak birim testinden daha fazla kurulum ister ama uçtan uca testten çok daha azını: gerçek veritabanı, gerçek sorgu, sahte HTTP katmanı yeter. Tarayıcıya gerek yok.
Orta katın boş kalmasının pratik bir sebebi var. Uçtan uca test yazmak ilk günde daha kolaydır, çünkü uygulamayı zaten çalıştırıyorsunuzdur ve tıklama sırasını kaydetmek düşünmeyi gerektirmez. Entegrasyon testi ise "bu modülün sınırı nerede" sorusunu cevaplamayı gerektirir. O soruya cevap vermemenin maliyeti ilk hafta görünmez, altıncı ayda CI süresinde görünür.
Sorgu katmanı bunun en net örneği. Yanlış birleştirme yüzünden fazladan satır döndüren bir sorgu, birim testiyle yakalanmaz çünkü veritabanı sahtelenmiştir; uçtan uca testle yakalanır ama size "liste sayfası bozuk" der. Gerçek veritabanına karşı çalışan tek bir entegrasyon testi ise doğrudan sorguyu gösterir.
Piramidi düzeltmek testi taşımakla olmuyor
Yaygın refleks, mevcut uçtan uca testleri birim testine "çevirmeye" çalışmak. Bu çoğunlukla yürümez, çünkü kod o testlerin yazıldığı şekilde yazılmıştır: iş kuralı denetleyicinin içindedir, hesap şablonun içindedir, karar veritabanı sorgusunun içindedir. Alt katmanda test edilebilecek bir birim yoktur.
Yürüyen sıra şu:
- İş kuralını dışarı çıkar. Fiyat hesabı, yetki kararı, durum geçişi — bunlar HTTP'den ve veritabanından bağımsız saf fonksiyonlar olabiliyorsa olmalı. Test edilebilirlik bir test tekniği değil, bir tasarım sonucu.
- Sınırları sözleşmeyle test et. Servisler arası çağrılarda uçtan uca test yerine sözleşme testi kullan: çağıran tarafın beklediği şekil ile çağrılan tarafın ürettiği şekil ayrı ayrı doğrulanır, ikisi aynı anda ayağa kalkmak zorunda kalmaz.
- Uçtan uca testi akış sayısıyla sınırla. Kaç akış? Ürünün para kazandığı ya da veri kaybettirebileceği akışlar. Kayıt olma, ödeme, silme. Bu liste genelde bir elin parmaklarını geçmiyor.
- Kararsız testi karantinaya al, silme. Kararsız bir test bir bilgi taşıyor: sistemde deterministik olmayan bir şey var. Silmek o bilgiyi de siliyor. Ayrı bir işe alıp kaynağını bulmak, testi susturmaktan farklı.
Ölçülecek tek sayı
Kapsam yüzdesi bu konuda kötü bir gösterge; ters piramitli bir projede uçtan uca testler kapsamı yüksek gösterir çünkü kodun her yerinden geçerler. Geçtikleri yerin doğru çalıştığını iddia etmezler, sadece patlamadığını gösterirler.
Daha işe yarar bir sayı var: bir testin kırılmasından hatanın yerinin bulunmasına kadar geçen süre. Bunu bir ay boyunca kabaca kaydedin. Ters piramitte bu süre saatlerle ölçülür ve testin kendisiyle değil, testin altındaki katman sayısıyla orantılıdır. Piramit düzeldikçe süre dakikalara iner.
Testin amacı geçmek değil, kırıldığında nereye bakacağınızı söylemek. Bu ölçüye göre iyi olan bir paket, kapsam yüzdesi düşük olsa bile daha iyi bir pakettir.
- test
- CI
- kod kalitesi
- mimari