JUnit @Rule'ları
JUnit ve Docker kullanarak entegrasyon testleri yazmayı anlattığım yazı serisinin birinci bölümüne buradan erişebilirsiniz. Serinin bu ikinci yazısında, JUnit ve JUnit’in az bilinen fakat entegrasyon testleri yazarken kullanılması gereken bir özelliğinden, @Rule‘lardan bahsedeceğim. JUnit5‘e henüz geçmediğim için örnekler JUnit4 üzerinden olacak. Daha önce de yazdığım sebeplerden dolayı, bu yazı serisinde programlama dili olarak Kotlin
kullanacağım.
Unit testlerin ortak ihtiyaçlarını kod tekrarı yapmadan (DRY) nasıl karşılarız? Bu soru 10 sene önce bana sorulmuş olsaydı cevabım inheritence olurdu. Bir tane AbstracTest
sınıfı yaratırdım. Testlerin ortak ihtiyaçlarını bu abstract sınıfta tanımlardım ve tüm unit testlerimi bu AbstractTest
sınıfından türetirdim. Fakat Object Oriented programlama dillerinin güçlü bir özelliği olan inheritence‘ı code reuse için kullanmamalıyız. Code reuse söz konusu olduğunda öncelikle composition
‘ı düşünmemiz gerekir.
Bakınız “Effective Java 3rd Edition Item 18: Favor Composition Over Inheritence”
JUnit @Rule’ları sayesinde kod tekrarı yapmadan, testlerimize ortak kullanılan özellikleri ekleyebiliriz. Rule’ların çalışmasını kontrol eden iki farklı annotation var:
@Rule
annotation’ı ile tanımlanan Rule’lar her test metodu için ayrı ayrı çalıştırılır.@ClassRule
annotation’ı ile tanımlanan Rule’lar her test class’ı için bir kere çalıştırılır.
Kendi Rule’umuzu yazmadan önce JUnit4 ile beraber gelen bazı Rule’lara göz atalım.
TemporaryFolder Rule
Unit testlerin birbirinden bağımsız
ve izole
bir şekilde çalışmaları gerekiyor. Testlerden birisinin FileSystem’de yaptığı değişikliği diğer testlerin görmemesi lazım. Bunun için TemporaryFolder
JUnit Rule’unu kullanabiliriz. Bu rule’da testler sırasında dosya oluşturmaya ve dizin oluşturmaya yardımcı metodlar bulunuyor. TemporaryFolder sınıfının public metodları ile oluşturduğumuz dosya ve dizinler testler tamamlandığında otomatik olarak silinirler.
Aşağıdaki FileMerger.mergeFilesInFolder
metodu, istediğimiz bir dizin içindeki prefix
ile başlayan dosyaları bulup, bu dosyaların içeriklerini mergedFileName
adında ve aynı dizin içinde bulunan yeni bir dosyada birleştirir.
Bu metodun istediğimiz gibi çalıştığından emin olmak için unit testlerini aşağıdaki gibi yazabiliriz.
Method @Rule’larının public
@ClassRule’larının da public static
olması gerekmektedir. tempFolder
adındaki değişkenin başındaki @JvmField
annotation’ı Kotlin compiler’ın bu değişken için getter/setter üretmemesini sağlar. Bu sayede JUnit framework’u bu değişkene erişebilir.
System Rules
Junit @Rule’larına güzel bir örnek de System Rules
paketi. Kendi Rule’larınızı yazmadan önce bu paket içindeki Rule’lara bakmanızı öneririm. Örneğin ortam (environment) değişkenlerini kullanan kodlarınız varsa, testlerinizde EnvironmentVariables
Rule’unu kullanabilirsiniz. Testler sırasında bu Rule ile değiştirdiğiniz ortam değişkenleri test sonunda tekrar eski haline güncellenir.
Kendi Rule’umuzu Yazalım
Mevcut Rule’lar işimizi görmediği zamanlarda kendi Rule’larımızı yazabiliriz. Örneğin her @Test metodunun çalışmasının kaç milisaniye sürdüğünü loglamak için aşağıdaki gibi bir Rule yazabiliriz.
TestDurationRule
‘u aşağıdaki gibi testlerimize ekleyebiliriz. @Rule olarak kullanırsak her metod için ayrı ayrı ölçüm yapılır. @ClassRule olarak kullandığımızda ise test sınıfı içindeki tüm metodların toplam çalışma süresi loglanır.
RuleChain
Test sınıfımızda birden fazla @ClassRule
varsa bu rule’ların uygulanma sırası kullandığımız JVM’deki reflection API
‘ye bağlıdır. Yani rule’ların çalışma sırası belirsizdir. Rule’ların bizim belirlediğimiz bir sırada çalışmasını istiyorsak RuleChain
kullanabiliriz.
Bir redis sunucusu ile olan entegrasyonu test ettiğimizi düşünün. Testler başlamadan önce redis sunucusunu ayağa kaldırmamız gerekir. Redis process’inin başlamış olması, sunucunun testlerde kullanılabilir hale geldiği anlamına gelmez. Varsayılan redis portundan (6379) istekleri dinliyor olması gerekir. Redis entegrasyon testlerini sağlıklı bir şekilde çalıştırmak için iki farklı @ClassRule rule yazabiliriz:
- Birinci @ClassRule’un görevi redis sunucusunu baslatmak ve test tamamlandığında kapatmak.
- İkinci @ClassRule’un görevi ise redis sunucu 6379 portundan isteklere cevap vermeye başlayana kadar beklemek ve cevap vermeye başlar başlamaz testleri çalıştırmak. Yani redis için
healthcheck
yapmak.
Aşağıdaki RedisIntegrationTest
sınıfında, redis ve redisHealthcheck adında iki tane junit rule tanımladım. RuleChain sayesinde de bu rule’ların çalışma sırasını belirliyorum.
RedisRule
bir ExternalRule olarak tanımlandı. Şimdilik içini boş bırakıyorum. Gelecek yazılarda docker container’ları ve junit rule’ları ile Redis sunucusunu entegrasyon testleri için başlatıp kapatmayı göstereceğim.
Herhangi bir ExternalResource’un (redis, oracle, kafka, başka bir microservis….) testlerde kullanıma hazır olup olmadığını kontrol etmek için kullandığım HealthCheckRule
da aşağıdaki gibi:
RedisIntegrationTest sınıfındaki testi çalıştırdığımda aşağıdaki gibi bir çıktı alırım.
JUnit test Rule’ları ile başladığımız bu yazı dizisini Docker’a nasıl bağlayacağımı merak ediyorsanız ufak bir ipucu vereyim : Testcontainers. Rule’lar sayesinde testler başlamadan önce Docker container’larını ayağa kaldırıp testlerimizde kullanacağız, testler tamamlandığında da container’ları kapatacağız. Bir sonraki yazıda buluşmak üzere.
Kodunuzu automated test’lerden mahrum etmeyin! -anonim