Kafayı yiyecektim arkadaş. Her git push'tan sonra, o güzelim Jenkins pipeline'ımın "test" aşamasında kırmızı ışık yanıyor. Build failed. Neden? Meğerse takımın yazdığı yüzlerce unit test içinde, RANDOM bir şekilde fail olan bir tane varmış. Bazen geçiyor, bazen geçmiyor. Bot da her seferinde build'i kırıyor tabii. Şaka gibi ama, CI/CD'nin amacı güvenilirlik değil miydi? Benim pipeline'ım rastgelelik üzerine kurulu bir kumarhaneye döndü.
İlk başta "kesin ortam değişkeni" dedim. Sonra "cache problemi" dedim. StackOverflow'da bile doğru düzgün bir çözüm bulamadım. Logları didik didik ettim. Fail olan test, bir tarih fonksiyonunu test ediyordu. Meğerse içinde DateTime.Now kullanıyormuş! Test, gece 23:59:59'da çalıştırıldığında başka, gündüz çalıştırıldığında başka sonuç veriyordu. Rastgelelik de tam olarak buydu işte. Sistem saati, testin kaderini belirliyordu.
C#:
// İşte o lanet olası satır:
var result = CalculateSomething(DateTime.Now);
// Test, 'result' için sabit bir değer assert ediyordu!
Çözüm belliydi aslında. Unit testler deterministik olmalı, yani aynı koşullarda hep aynı sonucu vermeli. Dış dünyayla (sistem saati, dosya sistemi, network) konuşan her şeyi izole etmek gerekiyor. Hemen kolları sıvadım ve Moq kütüphanesiyle bir zaman sağlayıcısı (ITimeProvider) interface'i yazdım. Testler artık gerçek zamana değil, benim sağladığım sabit bir "sahte" zamana bağımlı oldu.
C#:
// Production kodu artık şöyle:
var result = CalculateSomething(_timeProvider.Now);
// Testte ise:
var mockTimeProvider = new Mock<ITimeProvider>();
mockTimeProvider.Setup(t => t.Now).Returns(new DateTime(2023, 10, 29, 14, 30, 0));
// Artık test her zaman aynı tarihi görecek!
Bu değişiklikten sonra pipeline'ım yeniden huzura kavuştu. Bot, artık keyfine göre değil, kurallara göre çalışıyor.
Bu olay bana unit test yazmanın felsefesini bir kere daha hatırlattı. Dış bağımlılıkları her zaman mock'lamak gerekiyor. Siz de böyle "flaky" (istikrarsız) testler yüzünden build'leriniz kırıldı mı? Rastgele fail olan testlerle baş etmek için başka hangi yöntemleri kullanıyorsunuz? Belki test retry mekanizması kurmak? Yorumlara bekliyorum!