Java ile Unit Test Yazmak (Birim Test)

Yasin Memiç
16 min readMay 17, 2020

--

Başlıklar:

Unit Test Nedir?

Unit Test Yazma Kuralları Nelerdir?

Java Unit Test Framework’leri

JUnit Yaşam Döngüsü

Assertions Kullanımı- Metotlar

JUnitParams Nedir?

Hatalar nasıl test edilir?

Testleri grup halinde çalıştırmak

Ignore Özelliği Nedir?

Hamcrest

AssertJ Kütüphanesi

Obje Tutan Listeler Üzerinde Test

AssertJ ile File Testi

AssertJ ile Exception Testi

Kendi Assert Sınıfımızı Yazalım:

Mock Nedir?

Java’da Mock Kütüphaneleri şunlardır:

Mockito ile davranış kontrol etme

Spy nesneleri nedir?

Annotation Desteği

Unit Test Nedir?

Yazılımın en küçük parçasının davranışlarını kontrol etmeye birim test denir. En küçük parçadan kasıt, bir sınıf veya o sınıfın herhangi bir metodu olabilir. Her parça birbirinden bağımsız, izole bir şekilde test edilir. Birim testler yazılımcı tarafından yazılır. Faydalarını şöyle maddeleyebiliriz:

1- Daha hızlı yazılım geliştirmeyi sağlar.

2- Çok daha az hata içeren bir koda sahip oluruz.

3- Kodun çalıştırılabilir örnek dokümanını oluşturmuş oluruz. (Teste bakarak kodun ne işin yaptığı, daha kolay anlaşılır)

4- Daha iyi tasarıma sahip, kod kalitesi yüksek bir çalışma gerçekleştiririz.

5- Hataların daha kolay bulunup düzeltilmesini sağlarız.

6- Kullanıcı bakış açısından yazılan, daha kolay anlaşılabilen bir koda sahip oluruz.

7- Basit, gereksiz kompleks yapı içermeyen koda sahip oluruz.

8- Kodu yeniden düzenlemek daha kolaydır.

9- Birim testler tüm hataları ortaya çıkarmaz yani kullanım amacı hata bulmak değildir. Çünkü her parça izole şekilde test edilir. Dolayısıyla entegrasyon test aşamasında(birden çok birim test vb. içeren bir parçanın baştan sona test edilmesi) ne gibi bir problemle karşılaşacağımız bilinemez. Yani herhangi bir metodun birim testini geçmesiyle, 10 tane metodun birbiriyle entegre şekilde dosdoğru çalışacağı garanti edilemez.

Unit Test Yazma Kuralları Nelerdir?

1- Her birim test metodunun içinde, tek bir metot test edilmeli.

2- Her birim testin, kendine ait bir senaryosu olmalı. Birbiri ardına senaryolar yazdığımız durumda, ilk test patlarsa diğerlerine hiç geçilmeyecektir. Her senaryo için yeni test metodu yazılmalı.

3- Her birim testin içeriği, belli adımlara göre ilerlemelidir. Bunun için //Given → //When // → //Then standardını takip etmek uygundur. Given aşamasında elimizde olmasını istediğimiz, kullanacağımız değişkenleri oluştururuz. Yani test edeceğimiz nesneyi yapılandırırız. When aşamasında, Given aşamasında elde edilen değerlerle şu işlemi yapmak istiyorum deriz. Ve son olarak Then aşamasında beklediğimiz sonucu belirtiriz.

4- Test metot ismi, test edilen senaryoyu yansıtabilmeli. Bunun için birçok yöntem var. Bunları ezbere bilmek zorunda değiliz, sadece standartları gösterebilmek adına paylaşıyorum. En çok tercih edilen isimlendirmeler şöyle:

1-) testÖzellikİsmi → Örn: testPozitifSayılarıToplayıncaPozitifÇıkar

2-) özellikİsmi → Örn: pozitifSayılarıToplayıncaPozitifÇıkar

3-) should_BeklenenDavranış_When_Koşul → Örn: should_PozitifSayıÇıkar_When_PozitifSayıVerilirse

4-) when_Koşul_Expect_BeklenenDavranış → Örn: when_PozitifSayıVerilirse_Expect_PozitifSayıÇıkar

5-)given_ÖnHazırlık_When_Koşul_Then_BeklenenDavranış→Örn: given_BirHesapMakinesi_When_PozitifSayıVerilirse_Then_SonuçPozitifSayıdır

6-) method_Senaryo_Sonuç → Örn: topla_PozitifSayılar_Pozitif

7-) method_Sonuç_Senaryo →Örn: topla_Pozitif_PozitifSayılar

5- Test edilen her parça, diğerlerinden bağımsız olmalı. Yani başka bir parçaya ihtiyaç duymadan testini gerçekleştirebilmelidir. Bunun için mock veya stub kullanılabilir. Örneğin bir service sınıfında repository sınıfını kullanarak işlem yapıyorsak; service sınıfının testinde, repository nesnesine de bağımlılık duyulacaktır. Bu istenmeyen bir durumdur.

Mock: Bir çeşit proxy veya sahte bir nesne olarak düşünülebilir. Orijinal nesne gibi davranır. İçi boştur ve isteğimiz doğrultusunda hareket eder. Kullanmak için, Mockito.mock(ClassAdı.class) metodu kullanılır. Stub kullanımından daha çok tercih edilir.

Stub: Orijinal classı extend ederek, sadece test etmek amacıyla oluşturabileceğimiz sınıflara verilen isimdir. Çok tercih edilen bir yöntem değildir. Çünkü ekstradan sınıf ve kod ekleme zahmeti gerekiyor.

6- Her test metodu, diğer test metotlarından bağımsız şekilde çalışabilmelidir. Bir test metodundan, diğer bir test metodu çağrılmamalıdır.

7- Yukarıdaki tüm maddelere ek, sonuç bir madde olarak şu söylenebilir: Birim test tamamen development sürecini ve sonrasını kolaylaştırmak için vardır. Eğer bir birim test; çalışma zamanı, kod karmaşıklığı, anlaşılamazlık, farklı durumlarda farklı sonuçlar verme gibi problemler yaratıyorsa, yazılan test kesinlikle problemlidir.

Java Unit Test Framework’leri

JUnit

Hamcrest

AssertJ

Mockito

JUnit Yaşam Döngüsü

JUnit, her test sınıfını belirli aşamalarla test eder. Bu aşamalar şunlardır:

@BeforeClass: Test başladıktan sonra ilk olarak bu anotasyon ile işaretlenen metot çağrılmaktadır. Bir sınıfta ne kadar metot olursa olsun bu anotasyonla işaretlenen metot, her sınıf için sadece bir kere çağrılır. Bu anotasyonun icra edilmesinden sonra, @Test anotasyonuyla işaretli metot artık çalışabilir durumdadır. Bu anotasyonun kullanıldığı metot, static tanımlanmalıdır aksi durumda test hata verecektir.

@Before: @Test anotasyonundan önce icra edilir. Bu notasyonla işaretlenen metot, tüm test metotları için başlangıçta bir kere çalıştırılır. Yani hangi test metodunu test edersek edelim, bu anotasyonla işaretli metot bir kere çalışacaktır. Bir sınıfta 10 tane test metodum varsa ve hepsini tek seferde çalıştırıyorsak, bu metot 10 kere çağrılıp kullanılacaktır.

@Test: Asıl test etmek istenen metot, bu anotasyonla işaretli olan metottur.

@After:@Test anotasyonundan sonra icra edilir. Bu anotasyonla işaretlenen metot, tüm test metotlarının çalışmasının ardından bir kere çalıştırılır. @Before anotasyonunun tam tersidir.

@AfterClass: Tüm test metotlarının çalışıp görevlerini bitirdikten sonra, çalışması istenen metot bu anotasyonla işaretlenir. Yani en sonda bir kere çalıştırılması istenen metot olacaktır. Bu anotasyonun kullanıldığı metot, static tanımlanmalıdır aksi durumda test hata verecektir.

Assertions Kullanımı-Metotlar

Assertion’lar sonucun doğru olup olmadığını test etmek için kullanılır. Metotları şöyledir:

assertEquals(expected, actual): Herhangi bir nesne için beklenen değer ve gerçekte var olan değerin eşitliğine bakılır. Bu metotta önemli olan nokta, eğer metot içine verilen nesnede equals metodu override edilmemişse, referans eşitliği kontrolü yapar! Yani değerler eşit bile olsa iki nesnenin referansı farklı olduğu için test sonucu failed olacaktır. Bu duruma düşmemek için assertEquals() metoduna vereceğimiz nesnede equals() metodu override edilmelidir.

assertSame(expected, actual): İşlev olarak assertEquals() metoduyla aynıdır. Bu sınıfın farkı, nesnede equals() metodu override edilmiş bile olsa, iki nesnenin referans eşitliğine bakar. Eğer nesne tam anlamıyla aynı nesneyse, aynı referans adreslerine sahipse test passed olacaktır.

assertNull(object): Verilen objenin null olup olmadığına bakar, null ise test passed(başarılı test) olacaktır.

assertNotNull(object): Verilen objenin null olup olmadığına bakar, null değilse test passed(başarılı test) olacaktır.

assertTrue(condition): İçine verilen şartın doğruluğunu test eder. Verilen condition true ise test passed(başarılı test) olacaktır.

assertFalse(condition): İçine verilen şartın doğruluğunu test eder. Verilen condition false ise test passed(başarılı test) olacaktır.

assertArrayEquals(array1, array2): Verilen iki arrayın değer olarak eşitliğine bakar. Referans kontrolü yapmaksızın sadece değerlere bakarak bir sonuç döner. Diziler aynı değere sahipse test passed olacaktır.

Tüm bu metotlar için, birinci parametre olarak hata durumunda dönmek istediğimiz mesajı “String” tipte verebiliriz. Örneğin assertSame() metodu için kullanırsak: assertSame(“These objects are not same!”, expected, actual);

JUnitParams Nedir?

JUnitParams sayesinde herhangi bir classın herhangi bir metodunu birden çok kez farklı parametrelerle test edebiliriz. Bunun için öncelikle kaynağı belirteyim ve devamında örneklendireyim: https://github.com/Pragmatists/JUnitParams

AssertTest.java

Örnekte, CustomerRepository nesnesini gerçek bir nesne olarak (new CustomerRepository()), CustomerInformationService nesnesini mock olarak yarattık. Test metodunda @Parameters anotasyonu sayesinde birden çok kaydı, assertEquals() ile test edebildik. @Parameters içinde verilen değerlerin, testWithParameters() metodunun parametreleriyle örtüştüğünden emin olmalıyız.

Ek olarak, parametreler verilirken ben “|” şeklinde id, name, lastname alanlarını ayırdım. Bu “,” ile de sağlanabilir.

Başlığın başında paylaştığım kaynakta, bu kütüphanenin birçok örneği mevcuttur.

Hatalar nasıl test edilir?

1-) Test metodunda try-catch kullanarak, catch bloğunda assertion’ları yazabiliriz.

2-) @Test anotasyonunun expected parametresi ile beklediğimiz hatanın tipini verebiliriz. Bu yöntemin dezavantajı, hata mesajını kontrol edemiyor oluşumuzdur.

3-) @Rule anotasyonu aracılığıyla ExpectedException tipinde custom bir throw değişkeni oluşturabiliriz. Devamında test metodu içinde expect, expectMessage gibi özelliklerini kullanarak, beklenen hata tipi ve mesajını belirtebiliriyoruz. Sonradan doğrulama işlemi, JUnit tarafından gerçekleştirilir.

4-) catch-expection, JUnit içinde olmayan farklı bir kütüphanedir. Tek satırla, fırlatılan hatayı yakalamaya, onu yönetebilmeye olanak sağlar. Kaynak şu adreste: https://code.google.com/archive/p/catch-exception/

Dört durumu da örneklemeye çalışayım:

Önce CustomerInformationService içinde, throw edebileceğimiz bir metot oluşturup, MailServerException sınıfını throw ediyorum.

CustomerInformationService.java

Throw edilen MailServerException sınıfı, basitçe aşağıdaki gibi oluşturuldu.

MailServerException.java

Şimdi test sınıfını oluşturup, 4 şekilde de test edelim.

imports
ExceptionTest.java
Result

Ek olarak belirteyim, test adımlarından sonuncusu olan catch-exception yöntemi için şu bağımlılıklar eklenmeli:

<dependency>
<groupId>com.googlecode.catch-exception</groupId>
<artifactId>catch-exception</artifactId>
<version>1.2.0</version>
<scope>test</scope> <!-- test scope to use it only in tests -->
</dependency>
<dependency>
<groupId>com.googlecode.catch-exception</groupId>
<artifactId>catch-throwable</artifactId>
<version>1.2.0</version>
<scope>test</scope> <!-- test scope to use it only in tests -->
</dependency>

Ve son önemli nokta, mockito-core bağımlılığınız 1.8.1, 1.8.2, 1.8.3, 1.8.4, 1.8.5, 1.9.0-rc1, 1.9.0, 1.9.5-rc1, 1.9.5. versiyonlarından biri olmalı yoksa hata alacaksınız.

Testleri grup halinde çalıştırmak

Bu gereksinim için, Suite kullanılabilir. Bunun için şöyle bir kullanım var:

SuiteTest.java

@Suite.SuiteClasses anotasyonuna, süslü parantez içinde test etmek istediğimiz sınıfları yazıyoruz. Sınıfın içine herhangi bir tanımlama yapmamıza gerek yok. Çalıştırdığımızda tüm sınıflar için birim testlerin doğru şekilde koşulduğu görülüyor (testWithParameters isimli testte, son değeri hata alabildiğimizi görmek adına yanlış verdim):

Result
Result

Ignore Özelliği Nedir?

Önceden yazdığımız, fakat o an kullanmak istemediğimiz bir test sınıfını silmek yerine, @Ignore anotasyonuyla işaretleyerek onun işleme alınmamasını sağlayabiliriz.

Örneklemek adına iki adet test metodu ekliyorum.

IgnoreTest.java

İki test de çalışıyor:

Result

Şimdi @Ignore anotasyonuyla ikinci testin çalıştırılmamasını sağlayalım.

IgnoreTest.java
Result

Ek olarak, @Ignore anotasyonuna, message da verilebilir. Bu tamamen bilgilendirme amaçlı, kodu sonradan görebilecek bir developer için kullanım kolaylığı sağlaması amacıyladır. @Ignore(“This method will work later”)

Hamcrest

Hamcrest, eşleştirme kütüphanesidir. Birçok dil için desteği vardır. Assert eşleştirmelerinin daha anlaşılır olmasını sağlıyor. Genellikle assertThat() ile birlikte kullanılır.

Örneklendirelim:

imports
HamcrestTest.java

testBasicMatchers() metodunda,

is(equalTo(“firstText”)) kısmı hamcrest kütüphanesine aittir. is() okumayı kolaylaştırmak için kullanılıyor. Sadece equalTo() metodunu kullansak da çalışacaktı. Dolayısıyla bu satır, textFirst değişkeni için sahip olunan değerin “firstText” Stringi olup olmadığına bakıyor.

assertThat(textFirst, is(equalTo("firstText")));

Devam edelim, aşağıdaki satır da, adından anlaşılacağı üzere NotNull kontrolü yapmakta. textFirst değişkeni null olmadığı için test passed olacaktır.

assertThat(textFirst, is(notNullValue()));

Sonraki satırda, textFirst değişkeninin “st” Stringini içerip içermediği kontrol ediliyor. İçerdiği için, test passed oluyor.

assertThat(textFirst, containsString("st"));

Ve son olarak aşağıdaki satırda, anyOf() ile “fir” ve “rst” Stringlerinin bulunup bulunmadığını kontrol ediyoruz. anyOf() içinde herhangi bir parametre TRUE olursa işlem TRUE olur, Yani OR mantığıyla çalışmaktadır.

assertThat(textFirst, anyOf(containsString("fir"), containsString("rst")));

testOnLists() metodunda,

cities isminde ve defaultta 3 değer atanarak oluşturulan listede, hamcrest’in hasItem() metodu kullanılarak, parametre olarak verdiğimiz değerin listede bulunup bulunmadığı kontrol edilebilir.

Benzer şekilde hasItems() metodunda her iki değerin varlığı kontrol edilir. hasItems() birden fazla parametre olan durumlada kullanılır.

Üçüncü assertThat için, allOf() ile listede “Istanbul”, “Izmir” değerlerinin var olması, not() ile “Bursa” değerinin var olmaması sağlanıyor. Dolayısıyla bu şart sağlandığı için test passed olacaktır. allOf(…) için, verilen eşleştirmelerin hepsi doğru olmalı. AND mantığıyla çalışır.

Son olarak, either().or() ile ya “Istanbul”, “Izmir” değerleri listede var olsun, ya da “Bursa” değeri var olmasın gibi bir tanım yapıyoruz. Veya, Veya şeklinde çalışır. Ya o olsun, ya bu olsun…

AssertJ Kütüphanesi

Açık kaynak ve sık kullanılan assertJ kütüphanesi; daha okunaklı, efektif assertThat’ler yazmayı sağlar. Akıcı bir arayüze sahiptir. Çok geniş eşleştirme kütüphanesi vardır. Ek olarak kendi assert’imizi yapma imkanı sunmaktadır.

Kaynak:http://joel-costigliola.github.io/assertj/

Önce assertj dependency’ini pom.xml’e ekleyerek başlayabiliriz:

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<!-- use 2.9.1 for Java 7 projects -->
<version>3.16.1</version>
<scope>test</scope>
</dependency>

String tipinde text isimli bir değişken oluşturup, bunun üzerinde assertThat() kullanarak test yazabiliyoruz.

assertThat(), şu şekilde assertj’den import edilmeli:

import static org.assertj.core.api.Assertions.assertThat;
testStrings() method

describedAs() metoduyla hata durumunda göstermek istediğimiz bilgilendirmeyi String olarak belirtiyoruz.

Görüldüğü gibi birçok durum, yüksek okunabilirlik ile belirtilebiliyor.

Liste üzerinde örnek:

İlk assert, cities isimli arraylist için oluşturuldu. assertThat() ile bir önceki String işlemlerine benzer şekilde testleri gerçekleştirebiliyoruz. containsExactly() metodu ile, arraylist’te bulunan değerlerin birebir aynı sırada var olması beklenir. Sıralar veya değerler farklıysa test failed olacaktır. doesNotHaveDuplicates() metodu ile, bir kaydın birden çok kez olup olmadığını test edebiliriz. Aynı değerde birden çok kayıt varsa test failed olacaktır.

İkinci assert ise, assertThat() ile birlikte have() kullanımı adına örnektir. have() içine condition belirteceğimiz metot adını yazıyoruz.

Metot içinde matches() metodunu override ederek hangi durumda true veya false döneceğini belirliyoruz. Test, cities2 isimli listeyi assertThat() ile kontrol eder. Her eleman için, citiesBelongsTurkey listesine bakarak mevcutta varsa true, yoksa false şeklinde sonuç döner.

Obje Tutan Listeler Üzerinde Test

CustomerListTest.java

Bu örnekte, Item adında static bir class oluşturup özellik olarak id ve name özelliği verdik. Lombok’un @Data anotasyonu sayesinde getter-setter, equalsAndHashCode() vb metotlar otomatik olarak uygulanmış oldu. Yine Lombok’un @AllArgsContructor anotasyonu ile, tüm değişkenleri parametre olarak alan bir constructor oluşturmuş olduk.

exracting(alan_isimleri) şeklinde kullanılabiliyor. Böylece her Item nesnesi için, Item içinde bulunan id veya name alanını isteğe göre ele alabiliyoruz.

İlk assert’te basitçe listede bulunan elemanlar için “name” field’ına bakarak, “test-name-1”, “test-name-2” değerlerini içerip içermediğini kontrol ediyoruz.

İkinci assert’te name ile beraber id alanını da ele alarak, bu iki field’a göre test gerçekleşmesini istiyoruz. Birden çok field’ı belirtmemiz gereken durumda, parametre verebilmek için koddaki gibi tuple() kullanmak gerekiyor.

Üçüncü assert’te de, listedeki şu tipe sahip, şu isimdeki alanı getir ve içinde tam olarak eksiksiz bir şekilde 1,2,3,4,5 id’leri var mı kontrol et demiş olduk.

AssertJ ile File Testi

AssertJ ile, file için nasıl birim testler oluşturabileceğimizi görelim. Önce test/resources altında file.txt oluşturup içine bir şeyler yazıyorum.

file.txt

Devamında test sınıfını da şöyle yazabiliriz:

Yeni bir file nesnesi oluşturup assertThat() içine verdiğimizde koda göre sırayla; dosya mevcut mu, okunabilir mi, yazılabilir mi, uzantısı şu mu, ismi şu mu gibi testleri bizim için yapar.

İkinci assert’te ise içeriğiyle ilgilenerek, “Lorem” String’i ile başlayıp başlamadığını test etmiş olduk.

AssertJ ile Exception Testi

Hatalar da benzer şekilde test edilebilir.

testException() method

hasMessage() ile Exception’un sahip olduğu hata mesajı, isInstanceOf() ile ne tür bir hata olduğu, hasMessageContaining() ile de hata mesajında spesifik bir kelimenin varlığı test edilebilir.

Kendi Assert Sınıfımızı Yazalım:

Şöyle bir senaryo olacak: Müşteri kaydeden, kaydederken hoşgeldiniz mesajı veren ve hoşgeldin hediyesi sunan bir uygulama düşünelim. Bunu çok temel şekilde yazarak mantığı anlamaya çalışalım..

Gift.java

Customer sınıfında, List<Gift> tipinde liste ve customer’a ait diğer özellikleri oluşturuyoruz.

Customer.java

handleNewCustomer(), yeni müşteri için kayıt isteği geldiğinde önce giveGifts() metoduna giderek ilgili customer nesnesinin giftsList’ine eklemeler yapacak, devamında informationService aracılığıyla mail gönderecek ve son olarak database’e kaydedip, o nesneyi dönecek.

CustomerService.java
CustomerRepository.java
CustomerInformationService.java

CustomerInformationService içinde, custom olarak yazdığımız MailServerException, aşağıdaki gibidir:

Şimdi test senaryomuz ile devam edelim, öncelikle assertJ olmadan, JUnit ile yazmaya çalışırsak şöyle oluyor:

CusttomAssertTest.java

Yapılan işlemler: @Before anotasyonuna sahip setUp() metoduyla, bu metodun her test metodundan önce çalışmasını istediğimizi belirtmiş olduk. Bu metot, Generate seçeneklerinde de mevcut, kolayca ekleyebiliriz.

setUp() metodu içinde, service sınıfımızın ihtiyaç duyduğu repository ve informationservice sınıflarını mock ile yaratıyoruz. Önce assertJ olmadan, işlerin nasıl zahmetli olduğunu görebilmek adına testCustomAssertJUnit() metodunu yazdım.

testCustomAssertJUnit() içinde, verify ile customerRepository.save() metodunun ve customerInformationService.sendMailToCustomer() metotlarının çağrılıp çağrılmadığını doğruluyoruz. Devamında assertEquals ile “beklenen-gerçekte olan” kıyaslaması yaparak testi sonlandırıyoruz.

Şimdi assertJ ile devam edelim:

CustomAssertTest.java

Yukarda görüldüğü üzere, assertThatCustomer() şeklinde custom bir assert metodu yazmış olduk. Bu metodu 40. satırda görüyoruz. İncelediğimizde, aslında yaptığı tek işin CustomerAssert nesnesi dönmek olduğunu anlıyoruz. CustomerAssert sınıfına, customer nesnesini, return etmesini istediğimiz assert tipini, repository ve informationService nesnelerini veriyoruz. (repository ve informationService nesnelerini, isSaved() ve hasReceivedInformation() kontrollerini yapabilmek adına verdik.)

CustomerAssert sınıfı şöyledir:

Kendi Assert sınıfımızı yazmak için öncelikle AbstractAssert<CustomAssertAdı, Domain Class> şeklinde extends işlemi yapmamız gerekiyor.

Böylece sınıfın bize sunduğu actual nesnesi ile sınıfa gelen customer’a erişebileceğiz.

Görülüyor ki aslında yapılan iş, assertThat()’in var olan özelliklerini kullanarak kendi ihtiyaçlarımız doğrultusunda yeni assertler yazmak. Metotların her biri CustomerAssert dönmeli!

Mock Nedir?

Gerçek sınıfı birebir taklit eden nesnedir.

İstediğimiz gibi hareket etmesini sağlayabiliriz.

Gerçek — Mock nesneler birlikte çalışabilir. Buna Spy denir.

Neden Mock kullanmamız gerekiyor?

Birim test, sadece bir birimi test ettiği için test edilen parçanın ihtiyaç duyduğı ek bağımlılıkları test etmemek adına mock kullanılır.

Bağımlılıkların hiçbir yan etkisi olmamalıdır. Bu da mock ile sağlanır.

İstenilen herhangi bir senaryo için, bağımlılıkları istediğimiz şekilde yönetebilmek için de yine mock kullanılır.

Gerçek nesneleri kullanmak, ortaya beklenmedik durumlar çıkarabilir.

Gerçek nesneler yavaştır, dolayısıyla test yavaş geri bildirim yapar.

Gerçek nesneler tekrar edilebilir değildir. Yani her tekrarda aynı sonuç çıkmayabilir.

Gerçek nesneleri yapılandırma zor ve karmaşık olacaktır. Bu da ek bir zahmettir.

Java’da Mock Kütüphaneleri şunlardır:

Mockito

JMock

PowerMock

EasyMock

Mockito ile test yazmaya başlayalım:

Bunun için önce DummyCustomerService adında bir interface oluşturarak başlıyorum. İçinde add, delete, update, get metotları var.

DummyCustomerService.java

Bir metodun belirli bir parametreyle çağrılıp çağrılmadını test etme:

Bunun için verify() metodu şöyle kullanılır. verify(kontrolEdilecekSınıf).metodu(“parametresi”)

testBehavior() method

Bir metodun kaç kere çağrıldığını kontrol etme:

Bunun için, verify metodu times() parametresiyle kullanılır. times() içinde o metodun kaç kere çağrıldığını belirtiriz. Ayrıca metodun hangi parametreyle çağrıldığı da eğer girildiyse, önemlidir. never() ile metodun hiç çağrılmamış olması beklenir. anyString(), herhangi bir String değeri anlamına gelir yani verilen parametrenin bizim için önemsiz olduğu durumdur. Ve son olarak atLeast() metoduyla, addCustomer() metodunun “test-customer-1” parametresiyle en az 2 kere çağrılmasını beklediğimizi bildirmiş oluruz.

testHowManyTimesCalled() method

Metot çağrılma sıralarını kontrol etme:

Bunun için, InOrder interface’i kullanılır. Test ettiğimiz metotların çağrılma sıralarına göre, inOrder.verify() metotlarını kullanmamız gerekiyor. Test ettiğimiz metodun, verdiğimiz parametrelerle hangi sırada çağrıldığını görmek için kullanırız. Bu özellik, metot çalışma sıralarının önemli olduğu durumlarda kullanılır.

testCheckOrderTests() method

Sadece istenen metotların çağrıldığının kontrolü:

Sadece verdiğimiz metotların çağrılması, başka metotların çağrılmaması gereken durumlarda verifyNoMoreInteractions() metodu kullanılır. Örnekte örneğin customerService.updateCustomer(“…”) şeklinde bir şey yazsaydık test failed olurdu.

testNoOtherChecks() method

Hiçbir çağrılma durumunun olmaması istenen senaryolarda kontrol:

Bunun için, verifyZeroInteractions() kullanılır. Yorum satırı olan yerler yeniden aktif edilirse, customerService için metot çağrıldığı için test failed olacaktır.

testNeverCallMockObject() method

Mockito ile davranış kontrol etme

Bunun için when().then() ve doReturn(), doThrow() doNothing() vb. gibi do ile başlayan metotlar kullanılabilir. when().then() kullanılabilmesi için, when içine verilen metodun bir obje dönüyor olması gerekir. Eğer spesifik olarak void tipteki bir metodun davranışını kontrol etmek istiyorsak do.. ile başlayan doNothing(), doReturn() vb. metotlar kullanılabilir.

when().then() ile başlayalım:

DummyCustomerService.java

Örnek için, String dönen getCustomer() metodunu kullanacağım.

FirstPhaseTest.java

DummyCustomerService nesnesini mock ile oluşturduktan sonra şu satırda,

when(dummyCustomerService.getCustomer("test-customerName-1")).thenReturn("Customer-1");

dummyCustomerService içindeki getCustomer() metodunun “test-customer-name-1” parametresiyle çağrıldığı durumda, “Customer-1” cevabını dönmesini istediğimizi belirtiyoruz. assertThat içinde de eşitlik kontrolünü “Customer-1” Stringi ile yaptığımız için, test passed oluyor. Yani artık getCustomer(“test-customer-name-1”) ile çağrıldığında “Customer-1” return ediliyor.

İkinci testte, thenThrow() ile, parametre olarak verdiğimiz Exception’u fırlatıyoruz. getCustomer metodu “test-customer-name-1” parametresiyle çağrılırsa, RuntimeException fırlatmasını istiyoruz.

Şimdi void tipteki metotlar için nasıl davranış bildireceğimizi görelim:

checking mock object with startsWith “do” methods()

doReturn() kullanılarak, when().then() ‘de olduğu gibi bir kontrol yapabiliriz. Eğer fonksiyon void dönüyorsa, doNothing() metoduyla hiçbir şey yapılmamasını sağlayabiliriz.

İkinci metotta bulunan doThrow() metoduyla da istenen Exception fırlatılabilir.

Spy nesneleri nedir?

Spy özelliği ile, bir nesneyi bazı durumlarda gerçek nesne gibi, bazı durumlarda mock nesneler gibi kullanabiliyoruz.

Şu metot için gerçek nesneyi çağır, şu metot için mock nesneyi kullan diyebiliyoruz.

Spy kullanabilmek için gerçek nesneye ihtiyaç vardır.

Önceki örneklerde kullandığım DummyCustomerService interface’ini implemente eden bir class oluşturuyorum.

Test sınıfında spy() aracılığıyla oluşturduğum dummyCustomerService() nesnesinin addCustomer() metodunu çağrıyorum ve ekrana “addCustomer method worked” basıyor. Yani bu, gerçek nesneyi kullandığı anlamına geliyor.

doNothing().when(dummyCustomerService).addCustomer("test-customerName-1");

Satırını eklediğimizde, hiçbir tepki vermeyerek asıl nesneyi kullanmamış olacak.

doNothing() metodunda farklı, gerçekte farklı parametre çağırdığımızda spy nesnesi, defaultta orijinal nesneyi kullanmaktadır:

addCustomer() metoduna “test-customerName-2” parametresini verdiğimiz için, orijinal nesneyi kullanarak ekrana bilgi bastı.

Bir metot, sadece şu parametreyle çağrılırsa gerçek nesneyi kullan diyebiliriz:

doCallRealMethod() ile deleteCustomer metodu için sadece “test-customerName-3” parametresiyle çağrıldığında gerçek nesneyi kullanmasını sağladık.

Annotation Desteği

Normalde bir test sınıfında, ihtiyaç duyulan service, repository vb. sınıfları @Before anotasyonuyla işaretlenmiş setUp() metodunda yapılandırabiliyoruz:

AnnotationTest.java with @Before annotation and setUp() method

Hatırlarsak, @Before anotasyonuyla işaretli metotlar, o sınıf içindeki tüm testlerden önce bir kere çalıştırılıyordu.

Şimdi bunu @Mock anotasyonuyla yapalım:

AnnotationTest.java with @Mock annotation

İki metot da aynı işi yapmaktadır. Anotasyonlu kullanım için @RunWith ile, hangi runner ile çalıştıracağımızı belirtmemiz gerekiyor. Aksi takdirde çalışmaz.

@Spy kullanımına bakalım:

AnnotationTest.java with @Spy annotation

Hatırlarsak Spy, gerçek nesne ile çalışıyordu. Bunun için DummyCustomerServiceImpl sınıfı gibi, yeni instance’ı yaratılabilen bir nesne kullanılmalı.

Örneği genişleterek, daha önceden kullanılan customer senaryolarını da dahil edelim. Önce bunu setUp() metoduyla yapalım.

AnnotationTest.java

Şimdi anotasyonlar yardımıyla tekrar yazalım:

@InjectMocks anotasyonu ile, bu sınıftaki tüm @Mock ve @Spy nesnelerinin customerService nesnesine set edilmesini sağlamış oluyoruz. Bu işlemi setUp() metodunda, constructor inject olarak halletmiştik.

Yazıyı oluştururken kullanılan kaynak:

https://www.udemy.com/course/unit-test-yazma-sanati/

--

--