Veysel Uğur KIZMAZ

Java Unit Test ve JUnit Kullanımı

16.06.2012Okunma Sayısı: 15682Kategori: Java
Bu makalemizde Java’da Junit Testlerin nasıl oluşturulduğunu birkaç örnek ile inceleyeceğiz.
 
Öncelikle Unit Test nedir konusu ile başlayalım.
 
Unit test, yazılımda en temel test çeşitidir. Yazılan fonksiyonların çalışıp çalışmadığının testlerini yapmak için kullanılır. Buradaki en temel noktalardan biri, oluşturulan bütün testler birbirinden bağımsız çalışmaktadır ve her testin tek bir sonucu olmalıdır (true / false).
 
Unit testlerde en büyük avantaj yazılımın geliştirilme hızıdır. Unit testlerin olmadığı bir projeyi düşünelim. Bu projede kullanıcı, bir ürün kaydı yapsın. Proje çalıştığında
  1. Kullanıcı önce bilgileriyle giriş yapıyor,
  2. İşlem yapacağı birimi seçiyor,
  3. Kayıt yapacağı kategoriyi seçiyor,
  4. Açılan formda gerekli bilgilerini yazıyor
  5. Kayıt butonuna basıyor
 
Unit testlerin olmadığı bir projede yazılım geliştirici, yazılan kayıt fonksiyonu hemen arayüz katmanına bağlandığı taktirde runtime’da oluşacak hataların tespiti için bu işlemlerin hepsini tamamlayıp kayıt butonuna bastığında hatayla karşılaşacaktır. Hatayı düzeltip tekrar çalıştıracak, aynı işlemleri tekrar yapacak ve başka bir hatayla karşılaşabilecektir. Bu, yazılımcıya projenin ilerleyen aşamalarında büyük zaman kaybı yaşatmaktadır.
 
Unit testlerin en büyük avantajı da burada kendini göstermektedir. Bir üst satırda yazdığım örneği unit testlerle gerçekleştiren bir yazılımcı, bu işlemlerin hiçbirini yapmadan kendi oluşturduğu dummy datalarla bu hataları arayüz katmanına bağlama işlemini yapmadan alacak, testlerinin tümü başarıyla çalıştıktan sonra arayüz katmanına bağlama işlemini yapacak ve zaman kaybını önleyecektir.
 
Unit test oluştururken yapılması gereken, sadece kodun çalışacağı değerlerde değil çalışmayacağı değerlerin de parametrelerde gönderilip verilen hataların düzenlenmesidir. Örneğin bir fonksiyon içinde bir yerde bölme işlemi geçiyorsa parametreden bu bölme işleminde böleni 0 yapan bir değer gönderdiğimizde hata vereceğini biliyorsak bu hatanın sonucunda sistemin nasıl bir sonuç üreteceğini de görmemiz gerekmektedir. Bunun için de bir test yazmamız gerekecektir. Classlarımız tamamlandığında testlerinin de oluşturulup classlardaki tüm fonksiyonların testlerini başarıyla tamamlamalarını sağladıktan sonra bir sonraki class’ı kodlamaya başlamak gerekmektedir.
 
Peki Unit Test’in avantajlarını hızlıca maddelemek istersek:
 
  1. Yazılan kodun her satırının başka bir kod (test kodu) tarafından otomatik olarak test edilmesini sağlar.
  2. Kodun anlaşılmasını kolaylaştırır.
  3. Daha hızlı yazılım geliştirmeyi sağlar.(Proje başlangıcında projeyi yavaşlatır gibi görünmekte fakat projenin ileri dönemlerinde ciddi bir zaman kazanımı sağlamaktadır)
  4. Koddaki hata oranını azaltır.
  5. Kodların kalitesinin artmasını sağlar.
  6. Hataların çabuk tespit edilip düzenlenmesini sağlar.
 
Şimdi Java’da JUnit ile Unit Testlerin nasıl yapıldığını inceleyelim.
 
JUnit testi için önce testini yapacağımız sınıf ve fonksiyonu oluşturalım.
 
Örnek sınıfımızda parametre olarak 2 sayı gönderiyoruz ve bu sayıları kullanarak çarpma ve bölme işlemlerini gerçekleştiriyoruz.
 
 
  1. public class Islem {
  2.     private int sayi1;
  3.     private int sayi2;
  4.     
  5.     public Islem(){
  6.         
  7.     }
  8.     public Islem(int sayi1, int sayi2){
  9.         this.sayi1 = sayi1;
  10.         this.sayi2 = sayi2;
  11.     }
  12.  
  13.     public int getSayi1() {
  14.         return sayi1;
  15.     }
  16.  
  17.     public void setSayi1(int sayi1) {
  18.         this.sayi1 = sayi1;
  19.     }
  20.  
  21.     public int getSayi2() {
  22.         return sayi2;
  23.     }
  24.  
  25.     public void setSayi2(int sayi2) {
  26.         this.sayi2 = sayi2;
  27.     }
  28.  
  29.     public int carp(){
  30.         return this.sayi1 * this.sayi2;
  31.     }
  32.     public int bol(){
  33.         return this.sayi1 / this.sayi2;
  34.     }   
  35. }
Sınıfımızın kodlamasını tamamladık. Şimdi testlerini yazmaya başlayalım.
 
Java’da JUnit test yazmak için projemize bir JUnit Test Case dosyası ekliyoruz.
 
 
Açılan ekranda testimize bir isim veriyoruz.
 
 
Test case oluşturulurken karşımıza bazı seçenekler çıkmaktadır.  (Which method stubs would you like to create?) Bu seçeneklerin ne anlama geldiklerini inceleyelim.
  1. setUpBeforeClass() : Class ilk oluşturulduğunda yapılan işlemdir.
    1. Örneğin veritabanı işlemimiz varsa ve bağlantı oluşturulması gerekiyorsa testlerin en başında bağlantıyı oluşturma fonksiyonunu buradan çağırabiliriz.
  2. tearDownAfterClass() : Testlerin çalışması tamamlandıktan sonra yapılan işlemlerdir.
    1. Örneğin oluşturduğumuz veritabanı bağlantısını bu methodda yok edebiliriz.
  3. setUp() : Her test methodunun başında çalışan methoddur.
    1. Örneğin var olan veritabanı bağlantısını açma işlemini burada gerçekleştirebiliriz.
  4. tearDown() : Her test methodu tamamlandıktan sonra çalışan methoddur.
    1. Örneğin açtığımız veritabanı bağlantısını buradan kapatabiliriz.

Bunların nasıl çalıştıklarını da görmek için hepsini işaretleyip Finish butonuna basalım.

 

 
  1. public class Test1 {
  2.  
  3.     @BeforeClass
  4.     public static void setUpBeforeClass() throws Exception {
  5.         System.out.println("beforeclass");
  6.     }
  7.  
  8.     @AfterClass
  9.     public static void tearDownAfterClass() throws Exception {
  10.         System.out.println("afterclass");
  11.     }
  12.  
  13.     @Before
  14.     public void setUp() throws Exception {
  15.         System.out.println("setup");
  16.     }
  17.  
  18.     @After
  19.     public void tearDown() throws Exception {
  20.         System.out.println("after");
  21.     }
  22.  
  23. }
Kod tarafına geçtiğimiz zaman bazı keywordler gözümüze çarpıyor :
  1. @BeforeClass
  2. @AfterClass
  3. @Before
  4. @After
 
Yukarıda maddelediğimiz setUpBeforeClass(),tearDownAfterClass(),setUp(), tearDown() methodlarının yaptığı işlemleri belirleyenler aslında method isimleri değil, başlarında aldıkları bu özelliklerdir. setUpBeforeClass() methodunun ismini istediğimiz gibi değiştirebiliriz, eğer başında @BeforeClass varsa sınıf çalışmaya başladığında çalışacak demektir.
 
Şimdi, sınıfımızda yazdığız carp() methodunu test etmek için bir test methodu oluşturalım.
 
 
  1. @Test
  2. public void testCarp_dogru_10_20(){
  3.     int sayi1 = 10;
  4.     int sayi2 = 20;
  5.     int gercekSonuc = 200;
  6.     //
  7.     Islem islem = new Islem(sayi1, sayi2);
  8.     int sonuc = islem.carp();
  9.     Assert.assertTrue("Sonucta Hata Var", sonuc == gercekSonuc);
  10. }
Öncelikle bu methodumuzun test methodu olduğunu belirtmek için başına @Test keywordünü yazıyoruz.
 
Methodu oluştururken istediğimiz ismi verebiliriz.
 
Islem sınıfının carp() methodunun testini yazacağımızı söylemiştik. Islem sınıfından bir nesne oluşturmak için 2 tane int değere ihtiyacımız vardır. Bu değerleri birer değişken ile (sayi1, sayi2) oluşturuyoruz. Bunun sebebi, testlerde gönderdiğimiz parametreleri daha iyi görebilmektir.
 
İşlem tamamlandıktan sonra bir sonuç dönmesini bekleyeceğiz. Bu test için sonucun 200 dönmesini bekliyoruz. Bunu da bir değişkende tanımlayıp en sonunda çıkan sonuçla bu sonucu karşılaştıracağız.
 
Şimdiye kadar oluşturulan değişkenler, test işleminde gönderilen ve alınan sonuçları takip etmemizi kolaylaştıracaktır.
 
Değişkenleri oluşturduktan sonra işlemlerimizi yaptık ve sonuc değişkenimize işlemimizin fonksiyondan gelen soncunu atadık.
 
Test projesinin kontrol noktasına gelelim. Assert sınıfı.
 
Testimizin çalışıp ürettiği sonuca göre doğru yanlışlığını bu sınıfın methodları ile kontrol etmekteyiz. Assert sınıfının bazı methodlarına göz atalım.
 
fail(String)
Kodun tamamın çalışmadığını kontrol eder.
assertTrue(true);
İşlemin true değer döndürdüğünü kontrol eder.
assertsEquals(message, expected, actual)
expected ve actual değerlerinin aynı olduğunu kontrol eder. Değilse message’ı gösterir.
assertsEquals(message, expected, actual, tolerance)
expected ve actual değerlerinin belirli toleransta aynı olduğunu kontrol eder. Değilse message’ı gösterir.
assertNull(message, object)
Nesnenin null olduğunu kontrol eder.
assertNotNull(message, object)
Nesnenin null olmadığını kontrol eder.
assertSame(message, expected, actual)
expected ve actual nesnelerinin aynı nesneler olduğunu kontrol eder.
assertNotSame(message, expected, actual)
expected ve actual nesnelerinin aynı nesneler olmadığını kontrol eder.
assertTrue(message, boolean condition)
condition’ın true olduğunu kontrol eder. Değilse message’ı gösterir.
 
Temel anlamda kullanılan test methodları bunlardır. Şimdi örneğimizde kullanıdığımız methoda bakalım.
 
 
  1. Assert.assertTrue("Sonucta Hata Var", sonuc == gercekSonuc);
sonuc, gercekSonuc’a eşitse test başarıyla sonuçlanacak, değilse “Sonucta Hata Var” mesajı ekranda görünecek.
 
Testimize sağ tıklayıp Run As -> JUnit Test ile testimizi çalıştıralım.
 
 
Test sonucumuz doğru olduğu için JUnit ekranında testTopla_10_20 methodu başarıyla çalışacak ve aşağıdaki ekran görünecektir. 
 
 
Kodumuzda küçük bir değişiklik yapalım ve gercekSonuc değişkenimizin değerini 100 yapalım.
 
 
  1. @Test
  2. public void testCarp_dogru_10_20(){
  3.     int sayi1 = 10;
  4.     int sayi2 = 20;
  5.     int gercekSonuc = 100;
  6.     //
  7.     Islem islem = new Islem(sayi1, sayi2);
  8.     int sonuc = islem.carp();
  9.     Assert.assertTrue("Sonucta Hata Var", sonuc == gercekSonuc);
  10. }
sonuc ve gercekSonuc değerlerimizin değerleri eşit olmadığı için JUnit ekranında “Sonucta Hata Var” mesajı gelecektir.
 
 
Şimdi örneğimize bir test methodu daha ekleyelim ve bu methodda ise sayi2 değerimize 0 atayıp bol() fonksiyonunu çalıştıralım, hata vermesini bekleyelim.
 
 
  1. @Test
  2. public void test_Bol_yanlis_10_0(){
  3.     int sayi1 = 10;
  4.     int sayi2 = 0;
  5.     int gercekSonuc = 0;
  6.     //
  7.     Islem islem = new Islem(sayi1, sayi2);
  8.     int sonuc = islem.bol ();
  9.     Assert.assertTrue("Sonucta Hata Var", sonuc == gercekSonuc);
  10. }
 
JUnit ekranında “Failture Trace” bölümünü inceleyelim. İlk satırda hatamızı söylemektedir : Sıfıra bölme hatası. Yalnız dikkat etmemiz gereken nokta, “Sonucta Hata Var” mesajı ekranda görünmedi. Bunun sebebi ise, bol() methodunun tamamının çalışmamış olmasıdır. Bir sonuç geriye döndürmediği için gercekSonuc ile karşılaştıracak bir değeri yoktur.
 
Son olarak kodumuzda küçük bir düzenleme yaparak @BeforeClass methodumuzu çalıştıralım.
 
Her test methodumuzda birer Islem nesnesi oluşturduk. Bunun yerine class oluşturulurken bu nesneyi oluşturalım ve tüm methodlarda bu global nesneyi kullanalım. Kodumuzu aşağıdaki gibi düzenleyelim.
 
 
  1. public class Test1 {
  2.  
  3.     private static Islem islem;
  4.     
  5.     @BeforeClass
  6.     public static void setUpBeforeClass() throws Exception {
  7.         islem = new Islem();
  8.     }
  9.     
  10.     @Test
  11.     public void testCarp_dogru_10_20(){
  12.         int sayi1 = 10;
  13.         int sayi2 = 20;
  14.         int gercekSonuc = 200;
  15.         //
  16.         islem.setSayi1(sayi1);
  17.         islem.setSayi2(sayi2);
  18.         //
  19.         int sonuc = islem.carp();
  20.         Assert.assertTrue("Sonucta Hata Var", sonuc == gercekSonuc);
  21.     }
  22.     
  23.     @Test
  24.     public void test_Bol_yanlis_10_0(){
  25.         int sayi1 = 10;
  26.         int sayi2 = 0;
  27.         int gercekSonuc = 0;
  28.         //
  29.         //
  30.         islem.setSayi1(sayi1);
  31.         islem.setSayi2(sayi2);
  32.         //
  33.         int sonuc = islem.bol();
  34.         Assert.assertTrue("Sonucta Hata Var", sonuc == gercekSonuc);
  35.     }
  36. }
islem nesnemizi static oluşturduk. Bunun nedeni setUpBeforeClass() methodumuzun static olmasından kaynaklanmaktadır. 
 
 
Veysel Uğur KIZMAZ
Bilgisayar Mühendisi