Mockito – ilk Adımlar

Mockito adından da anlaşılacağı üzerine nesnelerinizi mock’lamanıza yarayan bir kütüphane. Peki nedir mock? Neden mock’lamaya ihtiyaç duyuyoruz? Ya da Mock’lama bize ne artı getiriyor? İşte bu yazıda ufak ufak bunları konuşacağız. Ufaktan diğer test terimlerine değineceğiz.

Mock’lama Nedir?

Terimlere çok takılıp kalmak istemiyorum. Ama neyi neden yaptığımızı ve bize neler sağladığını öğrenmemiz önemli. Mock tamamen beklentilerimiz ile ilgili. Unit testlerimizi yazarken ağırlıklı olarak kodun belirli bir bölümüne odaklanıyor. Örneğin eğer servis katmanımızı test ediyorsak, verinin nereden ya da nasıl geldiği bizim pek umurumuzda olmuyor. Aynı şekilde MVC tasarım şablonunda, Controller’ımızı test ediyorsak sınıfımızın servis üzerinden ne şekilde konuştuğu da bizi pek ilgilendirmiyor. Tabi’ki bu kısımları da test etmemiz gerekiyor fakat onları ayrı unit testler de zaten test ediyoruz. İşte bu gibi durumlarda, ilgili katmanı mock’luyoruz. Mocklarken yaptığımız aslında basitçe ne mockladığımız methodun hangi tür çağırıldığında ne şekilde davranmasını beklediğimizi belirtmektir. Bunu yapan bir çok kütüphane olmasına karşın benim bu tutorial üzerinden anlatacağım Mockito olacak.

Mock’lama genelde Stub oluşturma ile karıştırılmaktadır. İkisi de aynı sonucu üretmek için kullanılabilir ama asıl ürettikleri çözüm tamamen birbirinden farklıdır. Stub daha çok sadece çalışılması istenilen alan için üretilen nesnelerdir. Yeri geldiğinde bir servis sınıfı için stub oluşturulabileceği gibi yeri geldiğinde sadece modellerimizde Stub’lanabilir. Bu örnek boyunca stub’larda oluşturacağız. Stublarımız üzerinden Mock’ladığımız nesnelerin davranışlarını belirleyeceğiz.

Projenin Yaratılması

Projemiz basit bir java konsol uygulaması olacak. Aslında projemiz herhangibir şekilde olabilir. Ister web olsun iseter konsol uygulaması olsun bizi ilgilendiren kısmı testleri olacak. Gidip ağır bir servis katmanı üzerinden çalışmayacağız.Projemizi her zaman olduğu gibi maven kullanarak geliştireceğiz. Fakat geliştirme ortamımız olarak eclipse yerine Intellij Idea kullanacağız. Eclipse’i hala iş yerimde kullanmaya devam etsem de intellij’e alışmasının çok kolay olduğunu farkettim. Özellikle Maven ve Spring desteği inanılmaz güzel. Aynı şekilde eclipse’e göre Content Assist’i çok daha başarılı. Eclipse’te JSF geliştirirken yer yer xhtml sayfalarında content assistin beni delirttiği oluyordu. Fakat daha intellij’de böyle bir problem yaşamadım.

Neyse projenin yaratılmasına geri dönelim. Amacımız Mockito kullanılarak nasıl Mock’lama yapılacağı olduğundan, servis katmanımızı yazmak yerine sadece Interface olarak belirleyeceğiz. Sonrasında Controller sınıfımız bu servis sınıfını kullanarak yazacağız. Controller sınıfımızı bitirdikten sonra testlerimizi yazacağız ve işte asıl burada Mockito kullanmaya başlayacağız.

Maven

Projeden kısaca bahsettiğimize göre Maven ile projeyi oluşturmaya başlayabiliriz. Maven ile, maven-archetype-quickstart  archetpye’ını kullanarak projemizi oluşturuyoruz. Benim örneği yaparken kullandığım diğer bilgiler aşağıdaki gibidir. Eskiden projeleri oluştururken önce maven ile projeyi oluşturup sonra eclipse’e eklerdim. Fakat şimdi bunu yapmama gerek yok. Intellij’in maven özelliği çok başarılı.

  • Group Id: com.bahadirakin
  • Artifact Id: test-with-mockito

Projemiz oluştuğuna göre, projemizin bağımlılılarını ekleyebilir ve nasıl build olacağını belirleyebiliriz. Bunun içintest-with-mockito klasörünün altındaki pom.xml dosyasını aşağıdaki şekilde değiştiriyoruz.

pom.xml

Burada önemli olan kısım mockito bağımlılıkları. Gördüğünüz üzere mockito projeye eklenmesi açısından oldukça basit. Birazdan göreceğiz kullanması da aynı şekilde çok basit. Projemizin servis katmanını mock’layacağımız için spring olsun diğer orta katman mimarilerini olsun projemize dahil etmemize gerek yok.

Servis, Model ve Controller

Projemizi oluşturduğumuza göre, modelimizi, servis katmanımızı ve bu servis katmanımızı kullanan Controller sınıfımızı oluşturabiliriz. Burada şunu belirtmeliyim, anlaşılması kolay olsun diye böyle bir yapı seçtim. Normalde çok katmanlı bir uygulama geliştiriyorsanız buna benzer bir çok pattern ile zaten karşılaşmışsınızdır.

Mockito’ya ağırlık vermek için mantığımızı olabildiğince basit tutmaya çalıştım. Buradaki metod tanımlamalarına ve yaklaşımlara lütfen takılmayın. Elimden geldiğince ufak bir örnekle, mockitonun çoğu özelliğini kapsamaya çalışacağım. Bu sebepten herkesin alışık olduğu bir örnek yapacağım. Basit bir login örneği yapacağım. Örnekte servis katmanımız Authentication kısmını sağlarken, Controller katmanımız, authentication işleminin başarılı olup olmamasına göre sayfanın yönlendirmesinden sorumlu olacak. Bunun için ise bir User sınıfı tanımlayacağım ve işlemlerimiz bu user modeli üzerinden devam edecek.

Öncelikle projenin src/main/java klasörüne aşağıdaki paketleri ve sınıfları ekliyoruz. Sınıfların içeriğini daha sonradan vereceğim.

  • com.bahadirakin.controllers: Projemizin tek controller sınıfı bu pakette yer alacak. LoginController sınıfını bu pakette oluşturuyoruz.
  • com.bahadirakin.entitites: Projemizin entitylerini bu pakette yaratıyoruz. Basitlik katması açısından veritabanı ya da bir ORM katmanı yazmayacağız. Ama örnekleri daha iyi kavramak adına sizin yazmamanız için bir neden yok. Buraya bu örneğimizin tek modelini, User sınıfını koyuyoruz.
  • com.bahadirakin.services: Projemizin servis katmanını belirler. Eğer ORM katmanımız olsaydı, bu paketteki servisler ORM katmanı ile konuşacaktı. Aynı şekilde transaction yönetimi de yine bu katmanda yapılacaktır. Fakat biz bir tane servis tanımlayacağız. Hatta servisin implementasyonunu yazmayacağız bile sadece tanımlayıp bırakacağız. Bu sebepten buraya IUserService interface’ini oluşturuyoruz.
  • com.bahadirakin.exceptions: Örneğimiz sırasında kullanacağımız exception’lar burada yer alacak. Normal bir projede farklı exception’lar için farklı paketleriniz olabilir. Örneğin controller’lar ile ilgili exception’lar başka pakette, servislerle ilgili exception’lar başka pakette olabilir. Örneğimiz boyunca yine tek bir exception kullanacağız. Bu sebepten buraya UserNotFoundException sınıfını oluşturuyoruz.

Paketlerimizi ve içerisindeki sınıfları oluşturduğumuza göre şimdi sınıflarımızın içeriğini doldurmaya geçebiliriz.

Model

Projemizin tek modelinin içeriği aşağıdaki gibi olmalıdır.

 

Exception

Projemizin tek exception’ı UserNotFoundException‘ın içeriği aşağıdaki gibi olmalıdır.

Service

UserService interface’inin içeriği aşağıdaki gibi olmalıdır.

 Controller

Sıra esas test etmek istediğimiz sınıfı yazmaya geldi. LoginController sınıfımız aşağıdaki gibi olmalıdır.

Şimdi LoginController sınıfını biraz inceleyelim.

  • LoginController doğrudan IUserService sınıfı ile ilişkili. Hatta IUserService implementasyonu sağlamadan, LoginController sınıfını oluşturamıyoruz bile. Eğer spring ile yönetilen bir ortamımız olsaydı burada ConstructorInjection yapmamız gerekebilirdi.
  • LoginController içerisindeki authenticate methodu, User nesnesi içerisinde authentication işlemi yapıyor. Eğer user bulunduysa anasayfaya (homePage), bulanamadıysa hata sayfasına (errorPage) yönleniyor. Bunun yanı sıra farklı hatalar için farklı mesajlar üretiliyor.

Mockito Ile Testlerin Yazılması

Tutorial’ımızın başında testlerimizi Mockito ile yazacağımızı belirtmiştik. Öncelikle test paketlerimizi ve sınıflarımızı oluşturalım. Bunun için projeye öncelikle src/test/java klasörünü ekliyoruz. Daha sonra com.bahadirakin.controllers paketini bu klasöre ekliyoruz. Burada olabildiğince bir pattern üzerinden gitmeye çalışıyorum. Bunun Mockito ile bir alakası yok. Sadece test converage kısmında bana kolaylık sağlıyor. Paketimizi oluşturduktan sonra içerisinde LoginController sınıfını test edecek sınıfı yani LoginControllerTest sınıfını oluşturuyoruz.

LoginControllerTest.java

Burada dikkat edilmesi gereken kısımlar aşağıdaki gibidir.

  • @Mock annotasyonunu mock’lamak istediğimiz, sınıfta kullanıyoruz. Böylelikle Mockito bizim için bu sınıftan türemiş başka bir sınıf oluşturuyor. Testimizi çalıştırmadan Mock’lanan sınıfından neler beklediğimizi belirteceğiz.
  • @InjectMock annotasyonu mocklanan nesnenin hangi sınıfı oluştururken kullanacağını belirtmek için kullanıyoruz. Bu annotasyonu kullanmak zorunda değilsiniz. Testinizin setup phase’inde kendiniz oluşturup Mock nesnelerinizi manuel olarakta set edebilirisiniz.
  • @Before annotasyonu bildiğiniz üzere Junit4’te her testten önce çalışmasını istediğniz methodu belirler. Biz her testten önce mock nesnelerimizin yenilenmesini istiyoruz. Çünkü her testte farklı bir davranış bekleyebiliriz. Kimiznde exception bekleriz kiminde doğru sonuç dönmesini bekleriz.
  • MocitoAnnotaions.initMocks metodu adından da anlayacağınız üzere, Mockito annotasyonlarının çalışması için kullanılıyor

Genel olarak sınıfın detaylarına girdik. Şimdi de biraz test metodumuzu ineleyelim.

  • İlk testimiz başarılı olan bir authentication işlemi üzerine olacak.
  • İlk başta test sırasında kullanacağımız User stub nesnemizi yarattık. Normal uygulamada böyle bir nesne büyük ihtimal olmayacak. Ama normal bir nesne olabilecek durumda.
  • Mock’lama işleminin beklentiler üzerine kurulu olduğunu belirtmiştik. Bu sebepten IUserService sınıfımızın nerede ne zaman ne şekilde davranacağını belirtiyoruz. Eğer stub nesnemiz gelirse, IUserService içerisindeki authentication methodu true yani başarılı dönüş yapacak.
  • LoginController sınıfımızı ilk yaratırken söylemiştik. Eğer authentication başarılı (true) olursa, ana sayfaya yönlendirme yapılacaktı. Bu sebepten LoginController sınıfımızın redirect edeceği sayfayı kontrol ediyor.
  • Son olarak Mock’ladığımız nesnenin hangi metodunun kaç kere çağrıldığını kontrol ediyoruz. Böylelikle başarılı cevabının doğru nesneden geldiğine emin oluyoruz.

Bu şekilde testimizi çalıştırdığımızda başarılı sonuç alacağız.

Aynı şekilde Test Coverage bilgisine bakacak olursak, LoginController içerisindeki Methodların %100’ünü test ettiğmizi göreceğiz. Zaten tek bir methodumuz oluduğu için bu beklendik bir durum. Fakat authentication methodunun tamamını kontrol etmedik. Daha kontrol etmemiz gereken iki durum daha var. Birincisi username veritabanında kayıtlı fakat şifresi yanlış, ikincisi ise kullanıcı veritabanında da kayıtlı değil. Bu iki durumu daha kontrol etmediğimiz için Test Coverage içerisindeki Line Coverage değerimiz daha %60. Şimdi LoginControllerTest sınıfı içerisine yeni bir test adımı daha ekleyip bu değerimizi yükseltmeye bakalım.

Burada yine aynı işlemi yaptık sadece beklentimizi değiştirdik. Az önce aynı şifreyle login beklentisinde bulunurken şimdi login olamama beklentisinde bulunuyoruz. Böylelikle kullanıcı login olamadığında doğru sayfaya redirect edildiğini test etmiş oluyoruz. Fakat bu test Line Coverage bilgimizi sadece %70 yaptı. Gördüğünüz üzere bir yerden sonra Line Coverage’ı arttırmak daha zorlaşıyor.

Şimdi ise son testimizi ekleyelim, normalde büyük ihtimal güvenlik nedeniyle böyle bir bilgi dönmek istemezsiniz. Ama bu tutorial kapsamında eğitici olacağını düşünüyorum.

Evet burada yine beklentimizi değiştirdik. Az önce true ya da false beklerken şimdi Exception fırlatmasını bekliyoruz. Hemen ardından exception fırlattığında doğru redirect işlemlerini yaptığımıza emin oluyoruz.

Son

Şimdi Test Coverage’ımızı ölçtüğümüzde, LoginController sınıfında Line Coverage bilgisinin %100’e ulaştığını görüyoruz. Hiç getter/setter metodumuz olmadığından bu sayıya rahatlıkla ulaşabildik. Ama getter/setter metodlarımız da olsaydı bir kaç test daha yazmamız ya da fazladan Assertion yapmamız gerekebilirdi. Tabi mockito’nun tüm özelliği bu kadar değil, başka bir ton daha özelliğe sahip ama umarım sizin için güzel bir ilk adım olmuştur.

Kaynak kodlarına aşağıdaki adresten erişebilirsiniz.

End Of Line