Birim Test yazarken dikkat edilmesi gereken 2 önemli nokta

Teste maruz bırakılan sistem(ler) – (System Under Test) kavramını Martin Fowler’in Mocks Arent Stubs yazısında Bilgem Çakır ‘ın önerisiyle inceledim ve aklımda kalanları sizlerle paylaşmak istedim.

Birim testleri yazarken taklit (mock ) yapılar kullanmak faydalıdır çünkü asgari düzeyde sürtünme ile ilerlemek testlerin sürekliği açısında çok önemlidir. Martin Fowler’ın test dünyasında iki akımdan bahseder.

  • Klasik yaklaşım
  • Taklitçi (Mockist) yaklaşım

Klasik testçiler taklit (mock) yapıları kullanmazken, taklitçiler (Mockist) taklit nesneleri testlerinde sıkça kullanırlar. Her iki yaklaşımında artılar ve eksiler vardır.

Şimdi hemen kod örneği üzerinden Klasik ve Taklitçi yaklaşımı tarif etmeye çalışayım.

Kodları indirmek için : https://github.com/altuga/MocksArentStubs

 

@RunWith(JUnit4.class)
public class OrderStateTester {
    private static String ISTANBUL = "Istanbul";
    private static String ERZURUM = "Erzurum";
    private Depo depo = new DepoImpl();

    @Before
    public void setUp() throws Exception {
        depo.add(ISTANBUL, 50);
        depo.add(ERZURUM, 25);
    }

    @Test
    public void testOrderIsFilledIfEnoughInWarehouse() {
        Siparis siparis = new Siparis(ISTANBUL, 50);
        siparis.fill(depo);
        Assert.assertTrue(siparis.isFilled());
        Assert.assertEquals(0, depo.getInventory(ISTANBUL));
    }

    @Test
    public void testOrderDoesNotRemoveIfNotEnough() {
        Siparis siparis = new Siparis(ISTANBUL, 51);
        siparis.fill(depo);
        Assert.assertFalse(siparis.isFilled());
        Assert.assertEquals(50, depo.getInventory(ISTANBUL));
    }
}

Klasik yaklaşım örneğini yukarıdaki görülmektedir. Hiç bir taklit (mock) nesne kullanılmamaktadır.  İşin hikayesi ise şöyle;

  1. Öncelikle Istanbul ve Erzurum’da iki adet farklı kapasitelerde depolar oluşturuluyor
  2.  testOrderIsFilledIfEnoughInWarehouse içinde 50 adet siparişi Istanbul deposundan çekilmeye çalışılması test edililiyor. Depodaki kapasite, sipariş adet ile uyuştuğu için sipariş true yani başarılı şekilde sonuçlanmasını bekliyoruz.
  3. testOrderDoesNotRemoveIfNotEnough içinde ise 51 adet siparişi Istanbul deposundan çekilmeye çalışılıyor. Depodaki kapasite, sipariş adet ile uyuşmadığı için sipariş false yani başarılı şekilde sonuçlanmasını bekliyoruz.

Bu örnekteki baş rol oyunucusu Siparis nesnesidir yani teste maruz bırakılan sistem  Siparis sistemidir. Diğer figüran oyuncu Depo nesnesidir.  Peki Depo sistemi oluşturulması kolay bir nesne olmasaydı ne yapacaktık ? Siparis nesnesini test etmek için Depo bize ayak bağı olursa bir karşı önlemimiz var mı ?

 

@RunWith(MockitoJUnitRunner.class)
public class OrderInteractionTesterMockito  {
    private static String ISTANBUL = "Istanbul";

    @org.mockito.Mock
    Depo taklitDepo;

    @Test
    public void testFillingRemovesInventoryIfInStock() {
        //setup - data
        Siparis order = new Siparis(ISTANBUL, 50);
        when(taklitDepo.setQuantity(ISTANBUL, 50)).thenReturn(true);

        order.fill(taklitDepo);
        assertTrue(order.isFilled());
        assertEquals(0, taklitDepo.getInventory(ISTANBUL));


    }

    @Test
    public void testFillingDoesNotRemoveIfNotEnoughInStock() {
        //setup - data
        Siparis siparis = new Siparis(ISTANBUL, 51);

        when(taklitDepo.setQuantity(ISTANBUL, 50)).thenReturn(true);
        when(taklitDepo.getInventory(ISTANBUL)).thenReturn(50);

        siparis.fill(taklitDepo); // SUT - System Under Test
        assertFalse(siparis.isFilled());
        assertEquals(50, taklitDepo.getInventory(ISTANBUL));
        verify(taklitDepo).getInventory(ISTANBUL);
    }
}

Bu örnek için Mockito aracını kullandım diğer başka test araçları da kullanabilirdi. Taklitçi yaklaşım örneğini yukarıdaki görülmektedir. Baş rol oyunucusunu (Siparis) test etmek için figürana (Depo) ihtiyaç var ve bu figüran taklitçilik yoluyla oluşturulmuş.  İşin hikayesi ise şöyle;

  1. testFillingRemovesInventoryIfInStock içinde öncelikle siparişin nereden alınacağı ve adeti belirtiliyor.
  2. Daha sonra Istanbul deposunun verileri hazır hale getiyoruz yani akıllı taklitçi (Stub) olarak oluşturuluyoruz.  Daha sonra setQuantity() yordamı içerisinde adeti  50 veriyoruz ve bu durumda true değeri dönmesini kurguluyoruz.
  3. Daha sonra testin odağında bulunan Siparis nesnesinin test ediyoruz.

 

Akıllı taklitçi (Stub) ve taklitçi (Mock) kavramları arasındaki farkı anlamak çok önemlidir. Yukarıdaki testFillingRemovesInventoryIfInStock() yordamı içerisinde taklitDepo değişkenine bağlı nesne kendi içerisinde bir durum bilgisi (state) tuttuğu için akıllı taklitçi (Stub) kategorisine girer ve test süreci boyunca bu durum bilgisini korur.  Bellekte çalışan veritabanları da (In Memory database) akıllı taklitçi (Stub) kategorisine girerler.

Taklitçi yapılar (mock) etkileşimi sağlamak için vardır. İşte http://site.mockito.org/ sitesinden bir örnek :

import static org.mockito.Mockito.*;

// mock creation
List mockedList = mock(List.class);

// using mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one");
mockedList.clear();

// selective, explicit, highly readable verification
verify(mockedList).add("one");
verify(mockedList).clear();

Son olarak test yazmanın işe başlamadan önce yapılmasının büyük bir önem taşıdığını vurgulamak isterim, yani TDD – Teste yönelik geliştirme (Test driven development) içerisinde gitmek kaliteyi ve beklenmeyen hata (bug) sayısını azaltacaktır.

Teste yönelik geliştirme (TDD) tabiri  sanki işin önemini azaltıyor hissine kapılıyorum. Teste yönelik geliştirme (TDD) yerine Planlı geliştirme ismi daha uygun geliyor benim kulağıma yada İngilizcesi PDCA driven development dense daha uygun olabilir. Siz ne dersiniz ?

Eğer teste yönelik geliştirme ile işe başlamadıysa ve uygulamada beklemeyen hata (bug) sayısını artıyorsa ne yapılmalı ?  Ortak görüş kod gözden geçirme toplantılarının düzenli yapılmasıdır. Birim test yazarak beklemeyen hataların (bug) bulma ve düzeltme kararı kısa vadede çok etkili olmayacağını vurgulamak isterim.

Başarılar.

No Comments

Post a Comment

Comment
Name
Email
Website