Tag: birim-testi

  • Hangi birim?

    Hangi birim?

    Birim testleri ile ne amaçladığımıza geçmiş bir yazıda bakmış ve Unit Testing Principles, Practices, and Patterns kitabından aşağıdaki tanım ile bitirmiştim:

    Bir geliştiricinin, yaptığı geliştirmenin beklediği gibi çalıştığından emin olmak amacıyla yazdığı aşağıdaki özelliklere sahip test türüdür:

    • Otomatik çalıştırılabilir
    • Kodun küçük bir parçasını (birim olarak da bilinir) doğrular
    • Hızlı çalışır
    • İzole çalışır

    Bu tanım her ne kadar açık görünse de birim kavramını iki farklı şekilde yorumlamak mümkün – klasik bakış ve taklitçi (mockist) bakış.

    Taklitçi (mockist) bakış birim kavramını izole edilmiş bir kod parçası – tek bir fonksiyon, bir sınıf, bir tip – olarak yorumlarken; klasik bakış birimi kod değil bir sistem davranışı olarak görüyor. Gelin iki yoruma biraz daha detaylı bakalım.

    Taklitçi (Mockist) okul

    Londra okulu olarak da bilinen bu grup; test esnasında birimin izole olması konusunda katıdır. Bu gruba göre bir birimin bütün bağımlılıkları test dublörleri ile değiştirilmeli ve birim bütün dış etkenlerden bağımsız şekilde test edilmelidir. Bu test tarzını Martin Fowler münzevi (solitary) testler olarak isimlendiriyor.

    Aşağıda Unit Testing Principles, Practices, and Patterns kitabından bir örnek var. İki detaya dikkatinizi çekmek istiyorum:

    • Store sınıfı yerine ikame olarak Mock<IStore> kullanılıyor
    • Doğrulama storeMock.Verify ile ikamenin üzerinde çağrı kontrolü ile yapılıyor
    public class Mockist
    {
        [Fact]
        public void Purchase_succeeds_when_enough_inventory()
        {
            // Arrange
            var storeMock = new Mock<IStore>();
            storeMock
                .Setup(x => x.HasEnoughInventory(Product.Shampoo, 5))
                .Returns(true);
            var customer = new Customer();
    
            // Act
            bool success = customer.Purchase(
                storeMock.Object, Product.Shampoo, 5);
    
            // Assert
            Assert.True(success);
            storeMock.Verify(
                x => x.RemoveInventory(Product.Shampoo, 5),
                Times.Once);
        }
    
        [Fact]
        public void Purchase_fails_when_not_enough_inventory()
        {
            // Arrange
            var storeMock = new Mock<IStore>();
            storeMock
                .Setup(x => x.HasEnoughInventory(Product.Shampoo, 5))
                .Returns(false);
            var customer = new Customer();
    
            // Act
            bool success = customer.Purchase(
                storeMock.Object, Product.Shampoo, 5);
    
            // Assert
            Assert.False(success);
            storeMock.Verify(
                x => x.RemoveInventory(Product.Shampoo, 5),
                Times.Never);
        }
    }

    Klasik okul (Detroit okulu)

    Bu bakış açısı birimi bir sistem davranışı olarak gördüğünden test yaklaşımları da sosyal (sociable) testler yazmak yönünde. Sosyal testler; kodu izole etmek yerine bir davranışı bütün dahili parçaları kullanarak sistemin sınırlarına kadar test etmeye çalışır.

    Bu bakış açısında test dublörü kullanımı sınırlıdır. Sistem sınırlarında; veritabanı, dosya sistemi gibi dış sistemler ikameleri ile değiştirilir.

    Aşağıda aynı testlerin sosyal versiyonunu bulabilirsiniz. Burada;

    • Taklitçi yaklaşımdan farklı olarak Store sınıfı olduğu gibi kullanılır
    • Doğrulama Store sınıfının üzerinde değer kontrolü ile yapılır
    public class Classical
    {
        [Fact]
        public void Purchase_succeeds_when_enough_inventory()
        {
            // Arrange
            var store = new Store();
            store.AddInventory(Product.Shampoo, 10);
            var customer = new Customer();
    
            // Act
            bool success = customer.Purchase(store, Product.Shampoo, 5);
    
            // Assert
            Assert.True(success);
            Assert.Equal(5, store.GetInventory(Product.Shampoo));
        }
    
        [Fact]
        public void Purchase_fails_when_not_enough_inventory()
        {
            // Arrange
            var store = new Store();
            store.AddInventory(Product.Shampoo, 10);
            var customer = new Customer();
    
            // Act
            bool success = customer.Purchase(store, Product.Shampoo, 15);
    
            // Assert
            Assert.False(success);
            Assert.Equal(10, store.GetInventory(Product.Shampoo));
        }
    }
    
    public enum Product
    {
        Shampoo,
        Book
    }

    Birkaç cümle ile iki yaklaşımı karşılaştıralım;

    • Taklitçi yaklaşımda testlerin başarısız olması durumunda problemi tespit etmek kolaydır çünkü kısıtlı bir kod test edilmiştir. Klasik yaklaşımda Store sınıfında yaptığımız bir hata alakalı alakasız çoğu testin başarısız olmasına sebep olacaktır.
    • Taklitçi yaklaşımın görece daha hızlı çalıştığı söylenebilir ama taklit için kullandığınız kütüphanelerin performansı belirleyici olacaktır.
    • Klasik yaklaşım disiplin ve bilgi birimi gerektirir. Taklitçi yaklaşım görece daha kolay anlatılır ve uygulanır – her sınıf için bir test sınıfı
    • Taklitçi yaklaşım koda çok bağımlıdır. Kodda yapılan değişiklikler testleri de değiştirmeye sebep olur. Klasik yaklaşım ise davranışa yani çözülen probleme bağımlı olduğundan anca problem tanımı değişirse testlerde değişiklik gerektirir.

    Ben bu son maddeyi çok kritik buluyorum. Uygulamanın kolay olmasından ötürü taklitçi yaklaşım çok yaygın ve bu yaklaşımda birim testleri faydadan çok kod ile beraber bakım gerektiren maliyetlere dönüşüyor. Büyük taklit(mock) kütüphanelerinin kullanıldığı okuması zor karmaşık yapılar haline geliyor.

    Eğer siz taklitçi gruptansanız birim testlerini ne amaçla yazdığımızı tekrar değerlendirin ve klasik yaklaşıma bir şans verin derim.

  • Birim testleri ne işe yarar?

    Birim testleri ne işe yarar?

    Kötü yazılmış bir kodun bizi nasıl yavaşlattığını anlatmama gerek yok sanırım. Ama şu sebepten ama bu sebepten hepimiz sonradan düzeltirim yalanına sığınarak özensiz kodlar yazdık. Dönüp düzeltme zamanı geldiğinde ise olanı bozmak korkusuyla öylece bıraktık. Ama bıldır yediğimiz hurmalar bizi durma noktasına getirdi.

    Bu kötü gidişatı durdurmanın, tersine çevirmenin yolu etrafı temizlemekten geçiyor. Peki bir şeyleri bozma korkusunu nasıl yeneceğiz? Üzerinde çalıştığımız sistemi uçtan uca test edebilseydik, bir şekilde yaptığımız her değişiklikten sonra sistemi bozmadığımızın geri bildirimini hızlıca alabilseydik? Onlarca else altına bir else de biz eklemez; gerekli düzeltmeleri yapar, mutfağı temiz bırakırdık. İyi bir test setinin varlığı bize kodu temiz ve kaliteli tutmak için gerekli olan güven ortamını veriyor. Ve nihayetinde kaliteyi arttırarak değişiklik yapmanın maliyetini kontrol altında tutmamızı sağlıyor.

    Geleneksel usülde testler, yazılım geliştirme bittikten sonra işi test olan insanlar tarafından yapılır. Bu yöntem kaliteyi denetleyen, ölçen bir yaklaşım. Biz ise kaliteyi ürüne yedirmek, onun bir parçası haline getirmek istiyoruz. Geliştirme sonrası, başkaları tarafından yapılan testlerin verdiği geri bildirim bizim amacımız için çok geç gelen bir geri bildirim. Bu aşamada çoktan bir şeyleri bozma korkusuna yenik düşmüş; bulunan her hatayı bir yama ile düzeltme eğiliminde olacağız.

    Bize lazım olan yazılım geliştirme döngüsünde çok daha erken koşabileceğimiz bir test türü – birim testleri. Birim testlerinin tanımına gelin Unit Testing Principles, Practices, and Patterns kitabından bakalım:

    Bir geliştiricinin, yaptığı geliştirmenin beklediği gibi çalıştığından emin olmak amacıyla yazdığı aşağıdaki özelliklere sahip test türüdür:

    • Otomatik çalıştırılabilir
    • Kodun küçük bir parçasını (birim olarak da bilinir) doğrular
    • Hızlı çalışır
    • İzole çalışır

    Bu tanımı irdelemeyi ileriye bırakıp, bu yazıyı testlerin önemi hakkında sevdiğim bir tespit ile bitireyim:

    Testleri kullanarak canlı koda ulaşmak kolaydır, ancak bunun tersi zor olacaktır. Bu durum testler hakkında bize şaşırtıcı bir şey söyler: Testler, bir sistemdeki en önemli bileşendir. Canlı koddan daha önemlidirler.

    Robert C. Martin