Flaky Test Sana Bir Şey Anlatmaya Çalışıyor
Her ekipte bir tane vardır. Salı günü kırılan, yeniden çalıştırınca geçen, ertesi hafta başka bir dalda yine kırılan test. Sonunda biri ona bir retry ekler ya da @flaky etiketiyle kimsenin bakmadığı bir karantina paketine taşır. Pipeline yeşile döner ve herkes sorunun çözüldüğünde hemfikir olur.
Çözülmedi. Sadece sesi kısıldı. Flaky test, bazen senin kurduğunu sandığın dünyadan farklı bir dünya gören testtir. O fark bir bilgidir. Test geçene kadar yeniden çalıştırmak, sürekli öttüğü için duman dedektörünün pilini çıkarmakla aynı şeydir.
Flaky Testin Ruh Hâli Yok, Nedeni Var
"Rastgele" bir kök neden değildir. Bir ekiple flaky testlerin üzerinden geçtiğimde neredeyse hepsi birkaç kümeden birine düşer:
- Zamanlama. Test bir şeyin bittiğini varsayar: bir istek, bir animasyon, bir veritabanı yazması. Çoğu zaman bitmiştir. Bazen bitmemiştir. Sabit bekleme süreleri bunu, makine testin yazıldığı makineden yavaş olana kadar gizler.
- Paylaşılan durum. İki test aynı kullanıcıya, aynı dosyaya, aynı satıra dokunur. Tek başına çalışınca ikisi de geçer. Paralel ya da farklı sırayla çalışınca çarpışırlar.
- Ortam. İstekleri kısan üçüncü parti bir sandbox, gece yarısını geçen bir saat, bir saat dilimi, bir yerel ayar, dizüstü bilgisayardan daha az belleği olan bir container.
- Ürünün kendisi. Gerçek bir race condition, eksik bir kilit, anında tutarlıymış gibi okunan eventually consistent bir veri. Burada flaky olan test değildir. Sistemdir ve test onun tek tanığıdır.
En önemli küme sonuncusudur ve retry'ın en iyi sakladığı da tam olarak odur.
Retry Neden Yanlış Varsayılan
Retry basit bir soru sorar: bu test hiç geçebiliyor mu? Sorması gereken soru ise bu sefer neden kırıldığıdır. Yeşil olana kadar yeniden çalıştırdığında şanslı çalışmayı tutar, şanssız olanı çöpe atarsın. Oysa kullanıcılarının er geç yaşayacağı tam da o şanssız çalışmadır: canlıda, yük altında, kötü bir ağda, bir cuma akşamı.
Retry ekibe sinsi bir şey de öğretir: kırmızı bir build'in bir anlamı olmayabileceğini. İnsanlar buna inandığı an hataları okumayı bırakır. Test paketi çalışmaya devam eder ama artık kimse ona güvenmez. Güvenilmeyen bir test paketi ise CI dakikası harcamanın yavaş bir yoludur.
Sinyali Okumak
Flaky testi bir sinyal olarak görmek, biraz umut etmek yerine biraz mühendislik yapmak demektir:
- Her hatayı bağlamıyla kaydet. Commit, runner, süre, çalışma sırası, zaman damgaları. Geçmişi olmayan bir flaky test dedikodudur. Geçmişi olunca bir örüntüye dönüşür.
- Bilerek yeniden üret. Testi döngüde, paralel, yavaşlatılmış bir ağla ya da daha yavaş bir işlemciyle çalıştır. İstediğin an kırabiliyorsan çözümün yarısındasın.
- Beklemek yerine bilmeyi koy. Saniye sayısını değil, bir koşulu bekle. Ürün sana ne zaman hazır olduğunu bilmenin bir yolunu vermiyorsa, bu geliştiricilerle konuşmaya değer bir test edilebilirlik açığıdır.
- Veriyi izole et. Her test ihtiyacı olanı kendisi oluşturur ve arkasını toplar. Paylaşılan fixture'lar, paylaşılan kırılma noktalarıdır.
- Karantinaya tarihle al. Bir testi ana pipeline'dan çıkarman gerekiyorsa ona bir sahip ve bir tarih ver. Çıkışı olmayan karantina bir mezarlıktır.
Geliştirme Bilgisinin Karşılığını Verdiği Yer
Anlamadığın bir race condition'ı teşhis edemezsin. Frontend'in render'ı nasıl zamanladığını, API'nin eşzamanlı yazmaları nasıl ele aldığını ve kuyruğun nasıl yeniden denediğini bilmek, "bazen kırılıyor" cümlesini "önbellek soğukken iki istek elli milisaniye arayla gelince kırılıyor" cümlesine çevirir. Bu cümle, yeniden çalıştıran bir test uzmanı ile hata bulan bir mühendis arasındaki farktır. Bu serinin tekrar tekrar söylediği şeyin ta kendisi: bir şeyi iyi test etmek için onu inşa edebilecek kadar bilmen gerekir.
Bir dahaki sefere bir test kırılıp sonra geçtiğinde, yeniden çalıştır düğmesine uzanma refleksine diren. Ne gördüğünü sor. Sandığından daha sık, gerçek bir şey görmüştür.