Unit Test metodlarında bulunması gereken özellikler

1 Şub

Test Driven Developmant uygulamalarında karşılaştığımız kalıp terimlerden biri olan birim testlerin(unit tests) sahip olması gereken bazı temel özelliklerden bahsetmeye çalışacağız.

Birim test(unit test), bir kod parçasını çağırarak o kod parçasının istenen davranışı sergileyip sergilemediğini kontrol eden başka bir kod parçasıdır. “Birim” kavramı bir metod veya bir fonksiyon olarak düşünülebilir.(osherove – The Art of Unit Testing)

Birim testlerde bulunması gereken bazı ortak özellikler vardır. Bunlar:

  • Atomik
  • Deterministik
  • Tekrarlanabilir
  • Bağımsız
  • Hız

Atomik: Birim test metodu tek bir işlevselliği test etmelidir. Birden fazla işlevi test etmek,  anlaşılabilirliği azaltacağı gibi süreçte karmaşıklığa yol açar.

Deterministik: Bir test metodu ya başarıyla testten geçer(pass) ya da geçemez(fail). Yani sonuçsuz bir durum söz konusu değildir.

Tekrarlanabilirlik: Bir kere başarıyla geçen birim test, test metodunda veya test edilen kodda bir değişiklik yapılmadığı sürece her çalıştırıldığında aynı davranışı sergiler. Bazen geçip bazen geçmeyen testler tekrarlanabilir değildir.

Bağımsız: Birim testin çalışması için herhangi bir başka teste, uygulamaya veya dış kaynağa(veri tabanı, mail sunucu v.b) bağımlı olmaması gerekir.

Hız: Birim testler olabildiğince hızlı çalışmalıdır. Bir birim test, dakikalar, saniyeler değil milisaniyeler içinde sonuçlanmalıdır.

Domain Driven Design Servisleri

11 Oca

Bir domain model için en önemli yapı taşlarından biri servislerdir. Servisler, modele ait Entity ve Value nesnelerinin yapısına aykırı davranışları kendi bünyesinde taşıması için tasarlanırlar. Böylelikle Entity ve Value nesnelerinin vazifesi olmayan işleri yüklenmesini önlemiş olurlar.

Eric Evans‘a göre iyi tanımlanmış bir servisin belli karakteristik özellikleri vardır. Bunlar:

  • Domain nesnelerinin doğal bir parçası olmayan işlemleri yönetir.
  • Domain modelin diğer elemanları açısından Interface’ler tanımlanır.
  • Servis işlemler belli bir yerde tanımlanmak zorunda değildir.

Servisler test edilebilir olması ve birbirine bağlı işlemleri belirleyici olması açısından mutlaka bir  Interface çatısı altında bulunmalıdır. Interface’ler servislerin sözleşmeleridirler.

Servisler Application, Domain, Infrastructure gibi birçok katmanda bulunabilir.

Infrastructure servisleri IEmailSernder gibi dış kaynaklarla iletişimi sağlayan servislerdir. Dış kaynaklar  dosya sistemleri, SMTP, veritabanı, SMS gibi yapılar olabilir. Domain katmanı bir bildirimin kullanıcılara nasıl iletildiği ile ilgilenmez, sadece iç süreci tamamlar ve bir olayı(Event) tetikler.

Domain servisleri, küçük parçalar arasında üst seviye işlevselliği sağlayan kooordinatör vazifesindedir.  Örneğin sipariş işlemi için OrderProcessor servisi, para transferi için FundTransferService gibi. Domain  servisleri model için çok önemli olduğundan isimleri ve kullanımları Ubiquitous Language diye adlandırılan  ve kavramsal bir ifade olan domain dilinin bir parçası olmalıdır. Anlamları ve sorumlulukları müşteri ve  domain uzmanı tarafında tutarlı ve mantıklı olmalıdır.

Application servisleri dış ortama açılan servislerdir. Dış ortamdakiler bizim Entity nesnelerimizle doğrudan  iletişime geçemez. Fakat bunları temsil eden nesnelerle iletişime geçebilir. Katmanlar arasında iletişimi  doğrudan domain nesneleri ile yaparsak diğer katmanlar domain yapımız hakkında çok fazla bilgi sahibi olurlar. Application servisleri dış ortamdan gelentalepleri mesaj şeklinde model içindeki süreçlere aktarır. Bu noktada  Messaging Pattern diye adlandırılan yeni bir kavram karşımıza çıkıyor. Messaging Pattern Application  servislerinin kuralı gibidir. Dış ortamdan taleper mesaj olarak alınır ve iç süreç tamamlandıktan sonra sonuç  dış ortama servisler aracılığı ile mesaj olarak verilir. Application servisleri herhangi biriş kuralı içermezler. İş kuralları Domain katmanı için yürütülür.

Tasarıma başlarken genellikle öncelikle Domain ve Applicaiton servislerin oluşturlması ve kullanıcılara sunulacak olan Interface tiplerinin belirlenmesi daha sonra da Test Driven Development ile dış davranışların test edilmesi uygun olacaktır. Kullanıcı bakış açısından senaryoları oluşturup test etmek bize büyük katkılar sağlar. Çünkü yazdığımız kod sonuçta bir kullanıcıya sunulacaktır.

Özet

Domain Service: Domain nesnelerinin doğal yapısına sığmayan işletme mantığını kapsar. Bunlar CRUD işlemleri değildir. CRUD işelmeri repository bünyesinde gelişir.

Application Service: Sistem dışı kullanıcılar için oluşturulur. Örneğin Web servisleri veya Web arayüzleri bu servislerle haberleşirler. Kullanıcılara sunulan CRUD işlemleri burada tanımlanabilir.

Infrastructure Service: Dış kaynaklarla yapılan iletişimler için oluşturulur. (File, SMS, SMTP, MSMQ).

Kaynak:

  • Eric Evans, Domain Driven Design Tackling Complexity in The Heart of Software
  • http://lostechies.com/jimmybogard/2008/08/21/services-in-domain-driven-design/

Private Metodlar Test Edilebilir mi?

11 Eyl

Bu yazıda, sosyal topluluklarda karşılaştığım sorulardan biri üzerinde durmak istiyorum. Private erişim belirleyicili metodların test edilmesi mümkün mü? Ya da private metodların test edilmesine gerek var mı? Bu sorular Test Driven Development(TDD) konusunu ilk incelemeye başladığım zamanlarda benim de sürekli aklıma takılırdı.

TDD yaklaşımı ile geliştirme yapmak aslında test öncelikli(test first) geliştirme yapmaktır. Amacımız önce testlerin yazılmasıdır. Testler başarılı bir şeklide sonuçlandıkça çalışan kodun ortaya çıkmasıdır.

Bir sınıfın TDD yaklaşımı ile üretilmesi demek sınıfın ilk önce farklı bir test sınıfı yardımı ile üretilmesi, ardından da üyelerinin üretilmesi demektir. Sınıf üyeleri metod, property veya field şeklinde olabilir. Bir sınıf üyesine başka bir sınıf tarafından erişilebilmesi için, bu üyenin public erişim belirleyicisine sahip olması gerektiğini biliyoruz. Bu sebeple test sırasında üretilen sınıf üyelerinin erişim belirleyicileri de public şeklinde olacaktır. Test sınıfını ve test sırasında üretilen kodu görmek için basit bir örnek gösterim üzerinden ilerleyelim.

Test Sınıfı

[TestFixture]
public class MathOperationsTests
{
    [Test]
    public void Pow_ReturnResult()
    {
         MathOperations operations = new MathOperations();

         double result = operations.Pow(3, 4);

         Assert.AreEqual(81, result);
    }
 }

Üretim Kodu

public class MathOperations
{
     public double Pow(int number, int pow)
     {
         double result = 1;

         for (int i = 0; i < pow; i++)
             result = result * number;

         return result;
     }
 }

Önce test sınıfı oluşturuldu. Daha sonra erişim belirleyicileri public olan MathOperations sınıfı ve Pow metodu oluşturuldu. Pow metodu tam sayılarda üs alama işlemini yapmak üzere basit bir şekilde tasarlandı. Şimdi Pow metodu için bütün işlemlerin tamamlandığını varsayalım ve  MathOperations sınıfında bir takım kod iyileştirmesi yapalım.

public class MathOperations
{
    public double Pow(int number, int pow)
    {
        var result = calculate(number, pow);

        return result;
    }

    private double calculate(int number, int pow)
    {
        double result = 1;

        for (int i = 0; i < pow; i++)
           result = result*number;

        return result;
    }
 }

Üretim kodunu yeniden gözden geçirerek kodumuzun çalışmasını etkilemeden basit bir değişiklik yaptık. Bu değişiklik sonucunda üs alma işlemini private erişim belirleyicili başka bir metoda yüklemiş olduk. MathOperations sınıfında test edilmesi gereken tek metod şu anda Pow metodudur. calculate metodu, sadece sınıf içerisinde kullanılacağı için private olarak belirlenmiştir. Bu sebeple calculate metodunun test edilmesine gerek yoktur. Pow metodunun test edilmesi sırasında zaten calculate metodu çalışmaktadır. Yani zaten test sürecinden geçirilmektedir.

TDD yaklaşımını ilk öğrenmeye başladığım sıralarda benim de kafamı kurcalayan bir mesele olan private metodların testleri, aslında var olan bir sınıfın test edilmesi sırasında kafa karışıklığına yol açıyor. Yani var olan bir sınıfı test ederken, sınıf üyelerinden private erişim belirleyicisine sahip olan üyelerin de test edilmesi gerektiği, kafalarda bir soru işareti yaratabilir. Var olan bir sınıfın testinden kastım, daha önceden geliştirilmiş ve her hangi bir test sürecinden geçmemiş bir sistemdir.

Sonuç

TDD yaklaşımının doğru bir şekilde uygulanması, yani test öncelikli geliştirmenin doğru bir şekilde işletilmesi, private metodların test edilmesine gerek olmadığını ortaya koymaktadır.

TDD Sizi Yavaşlatmaz

19 Ağu

TDD(Test Driven Development) yaklaşımı için ortaya atılan eleştirilerden biri kod geliştirmeyi yavaşlattığı yönündedir.  TDD yaklaşımı, yöntem olarak şelale(waterfal) tarzı yaklaşımdan çok farklıdır. Dolayısıyla şelale tarzı geliştirmeyi bırakmak demek eski alışkanlıklardan vazgeçmek demektir. Ben TDD yaklaşımını ilk uygulamaya başladığımda, kendimi sanki sağ ayakla futbol oynarken sol ayakla oynamaya alıştıran oyuncu gibi hissetim. Çünkü alışkanlığımın tersi bir durum söz konusuydu. Önce kodu yazıp sonra test etmeye alışmışken, önce testin yazılıp sonra kodun üretildiği bir ortamda buldum kendimi.

Eski yöntemde yazdığım kodu test etmek için kullanıcı arayüzü (form, console) hazırlamam gerekirdi. Bu da hayli bir zaman kaybı demekti. Test edilecek sınıfa ait üyeleri test etmek için kullanıcı arayüz hazırlamak zahmetli ve uzun süren bir işlemdir. Test arayüzünü hazırladıktan sonra, ilgili forma kritik test parametrelerini elle tekrar tekrar girip denemek gerekir. Örneğin matematiksel bir fonksiyonu hesaplayan bir metodu test etmek için önce parametrenin sıfırdan küçük, sonra sıfır, sonra sıfırdan büyük olması durumunu sürekli test etmek gerekebilir. Metodun özellikleri arttıkça test sayısı da artar. Arada bir hata alırız ve hatayı düzelttikten sonra testleri tekrar yapmamız gerekir. Test senaryoları arttıkça, elle yapılan işlem sayısı da doğru orantılı olarak artar.

Testlerin elle yapılmasının gereksiz yere zaman harcadığını fark eden yazılımcılar, test sürecinin otomatik işletilmesi gerektiğine karar vermiştir. Tek bir tuşa basarak yazılımın bütün testlerini çalıştırıp kısa bir süre içinde geliştiriciye sonucu veren araçlar geliştirilmiştir. Bu araçlar sayesinde testler hızlı bir şekilde yapılmaktadır.

TDD yaklaşımı testlerin otomatik bir şekilde çalıştırılması anlamına gelmez. Çalışan koddan önce testin yazılması anlamına gelir. Test öncelikli(Test First) yaklaşım olarak da adlandırılır. TDD yaklaşımında kodu yazan kişi, kodunu kullanıcı gözü ile görerek geliştirmeye çalışır. Bu yazdığım API veya Framework’ün kullanıcısı olsam nasıl bir sınıf ve metod yazardım diye düşündürür geliştiriciyi. Bu geliştiriciyi yavaşlatmaz. Kodlama yapan kişinin koduna olan güvenini arttırır. Kişi geliştirdiği koda hükmetmeyi öğrenir. Testler başarıyla gerçekleştiğinde kişi güvenilir bir kod ürettiğini bilir.

TDD yaklaşımında uzmanlar hızdan ziyade kaliteli ürün geliştirmenin önemli olduğuna vurgu yapmaktadır. Yani müşteri sizin hangi yöntemlerle geliştirme yaptığınıza değil ortada düzgün çalışan bir uygulamanızın olup olmadığına bakar. Puanınızı buna göre alırsınız. Kötü uygulamalar demek hata oranı yüksek, kullanımı zor ve zaman kaybettiren uygulamalar demektir. Kötü uygulamaların üzerine yeni özelliklerin eklenmesi veya mevcut özelliklerinin değiştirilmesi zordur. Test edilmeden hızlı bir şekilde geliştirip teslim edilen uygulamaların bakımı ve ilerletilmesi sonraki aşamalarda zaman kaybettirici olacaktır. Değişimin zamanında yapılamaması da müşteri memnuniyetini önemli ölçüde azaltacaktır. Hiçbir müşteri binlerce satır boş koda para harcamak istemez. Binlerce satır kod yazmış olmak kişiyi iyi bir geliştirici yapmaz. (Ancak bazı kariyer ilanlarında aranan programcı kriterleri arasında “10000 bin satır kod yazmış olmak” şeklinde komik maddelere rastlarız)

TDD ile geliştirme yaparken çalıştırdığınız testlerin sonucunu almak çok uzun sürüyorsa bu durum, kodunuzu direkt olarak bir veritabanına veya bir dosya sistemine veya bir ağa bağımlı olmasından kaynaklanıyordur. Testlerin hızlı çalışması açısından kodların bu tür ortamlardan izole edilmesine dikkat edilmelidir. Test ortamında bu şekilde hız kazanmak mümkündür. Gerçek veritabanı ile dakikalarca sürecek testleri izole edilmiş ortamlarda saniyeler içinde gerçekleştirmek mümkündür.

TDD yaklaşımı genel olarak uygulamanın geliştirme sırasında ve ömrü boyunca devamlılığının sağlanabilmesi açısından size hız katar. Bir  çok firmanın sonraki versiyonlarını çıkaramadığı için çöpe giden uygulamaları vardır. Bir kurumda çalışan bir tanıdığımın anısıyla sözümü bitirmek istiyorum. Kurumuna yazılım yapan firmadan, mevcut yazılıma yeni bir özellik eklemesini istemiş. Firmanın verdiği cevap ise şu olmuş:  “Sen şimdi bir binanın aradaki bir katını kır ve araya bir kat ekle diyorsun, bu çok zor”.

Renk Tabanlı Unit Test Süreci ve Örnek Uygulama

25 Oca

Renklerle ilgili işlemler yapmak, component’lerin renk tabanlı özelliklerini değiştirmek, yazılım geliştirme sürecinde uyguladığımız basit operasyonlardır. Bu kez reklerle ilgili işlemlerin unit test mantığı kullanılarak nasıl gerçekleştirilebileceği incelemeye çalışacağız. Aslında yapacağımız işlem basit olarak bir ana renge ait değişik renk tonlarının oluşturulması şeklinde ilerleyecek. Örneğin “kırmızı” renge ait “N” adet renk tonunu unit test ile oluşturmaya çalışacağız. Daha sonra oluşturulan bu renk tonlarını bir WPF uygulaması içerisinde uygulayacağız. Senaryomuzu basitçe sunduktan sonra işleyişe geçebiliriz. Ortaya çıkan ürün aşağıdaki gibi olacaktır.

colorUnitTest2

Sürecimiz önce testlerin yazılıp daha sonra geçen testlerden kod üretimi şeklinde gelişmektedir. Bunun için öncelikle Visual Studio üzerinde “ColorManager” isimli bir proje başlatarak ilerleyelim.

colorUnitTest1

Devamında “ColorManager.Tests” adında bir proje daha ekleyerek test sürecinin işletileceği bir proje başlatıyoruz.

Bu uygulama için kullanılan Unit Test aracı, Nunit test aracıdır. Nuget Package Manager üzerinden eklenebilen bir referanstır.

İlk testlerimizi aşağıdaki geibi geliştirmeye başlayabiliriz.

[Test]
public void Create_WithClassSize1_ReturnsOneColorInList()
{
      var inputColor = Colors.Red;
      var inputSize = 1;

      ColorToneCreator creator = new ColorToneCreator();

      var result = creator.Create(inputColor, inputSize);

      Assert.AreEqual(1, result.Count);
}

İlk testimizde inputSize olarak yani kaç çeşit renk tonu oluşturulacağını 1 olarak belirleyip, bize dönen listede 1 adet renk tonu oluştuğunu idda ediyoruz.

[Test]
public void Create_WithClassSize1_ReturnsSameColorInList()
{
    var inputColor = Colors.Red;
    var inputSize = 1;

    ColorToneCreator creator = new ColorToneCreator();

    var result = creator.Create(inputColor, inputSize);

    Color color = result[0];

    Assert.AreEqual(255, color.R);
    Assert.AreEqual(0, color.G);
    Assert.AreEqual(0, color.B);

}

İkinci testimizde ise yine 1 çeşit renk tonuna karşılık oluşan rengin RGB değerlerinin ne olacağını idda ediyoruz. Kırmızı rengi gönderdiğimiz için R=255, G=0, B=0 olarak bize geri dönecektir.

[Test]
public void Create_WithClassSize2_ReturnsColorsInList()
{
     var inputColor = Colors.Red;
     var inputSize = 2;

     ColorToneCreator creator = new ColorToneCreator();

     var result = creator.Create(inputColor, inputSize);

     Color color = result[0];

     Assert.AreEqual(255, color.R);
     Assert.AreEqual(0, color.G);
     Assert.AreEqual(0, color.B);

     Color color2 = result[1];

     Assert.AreEqual(255, color2.R);
     Assert.AreEqual(127, color2.G);
     Assert.AreEqual(127, color2.B);
}

Üçüncü testimizde ise kırmızı renge ait iki farklı ton seçeneğini test ediyoruz. Ortaya çıkan iki renkten birincisi için RGB değerleri R=255, G=0, B=0 şeklinde iken ikincisi için R=255, G=127, B=127 şeklinde olacaktır. En yüksek değer 255 olduğundan eşit dağılım olması açısından 255 değeri renk tonu sayısına bölünerek değerler hesaplanmaktadır.

Buraya kadar gelişen test sürecinden üretilen kod ise şu şeklide olacaktır.

public class ColorToneCreator
{
     public List<Color> Create(Color inputColor, int inputSize)
     {
          int rgbFactor = 0;
          int incFactor = 255 / inputSize;

          List<Color> tones = new List<Color>();

          for (int i = 0; i < inputSize; i++)
          {
             byte r = (byte)(inputColor.R + rgbFactor > 255 ? 255 : inputColor.R + rgbFactor);
             byte g = (byte)(inputColor.G + rgbFactor > 255 ? 255 : inputColor.G + rgbFactor);
             byte b = (byte)(inputColor.B + rgbFactor > 255 ? 255 : inputColor.B + rgbFactor);

             tones.Add(Color.FromArgb(255, r, g, b));
             rgbFactor += incFactor;
          }

          return tones;
     }

}

Test sürecinden gelişen üretim kodu bu şeklide oluştuğuna göre artık geriye sadece bu kodu bir uygulama içerisinde kullanmak kallıyor.

Bu kodun kullanılacağı uygulamayı ise WPF uygulaması olarak seçiyoruz. Solution penceremize ColorManagerGUI adında yeni bir WPF projesi ekleyerek devam edebiliriz.

colorUnitTest3

Yeni solution penceresi yukarıdaki şeklinde olacaktır. WPF uygulaması MVVM deseni ile geliştirilmiştir. MainWindow.xaml view penceresinin DataContext’i için MainWindowModel şeklinde bir model sınıfı oluşturulmuştur. Bu sayede kod tarafı ile xaml tarafı birbirinden ayrılmış oldu.

Görüntü katmanında gelişen olaylar, form tasarımı ve bir butona tıklayarak parametrelerin, ColorManager tipine gönderilmesi şeklinde gelişmektedir. Aynı şekilde dönen rek tonları da formda bir ListBox içerisine yerleştirilmektedir.

Uygulamanın kaynak kodlarını ekte sunulmaktadır. Bu yüzden görüntü katmanını uzun uzun anlatmaya gerek duymadım. Zaten burada asıl vurgulanmak istenen, renk tabanlı bir uygulamanın test güdümlü olarak nasıl geliştirildiğidir.

Anafikir:

Bu örnek uygulamada renk tabanlı test güdümlü bir süreç işletilmiştir. Bu yöntemden yola çıkılarak görüntü işleme uygulamaları da test güdümlü olarak gelişitirilebilir. Bellek üzerine döşenmiş bir Bitmap nesnesi üzerinden de RGB tabanlı olarak test güdümlü geliştirmeler yapmak mümkün hale gelmektedir.

Örnek Uygulama:

Kodlara buradan ulaşabilrisiniz.

Testin Gücü

2 Oca

Bu yazıda, Test Driven Development (TDD) ile proje geliştirmenin faydalarından bahsetmeye çalışacağım.

Aşağıdaki resimde bir web projesine ait test sonuçlarını görmekteyiz. Bu test sonuçlarına göre 310 adet test başarılı bir şeklide sonuca ulaşmış(passed) durumda. Testlerin toplam süresi ise sadece 9 saniye.

TestExplorer

Hazırladığım bu proje bir Asp.Net MVC projesidir ve tamamen test edilmeye uygun ve gelişime açık bir şekilde yazılmıştır. Yani  Open Closed prensibine uyulmuştur.

Bu şeklide proje hazırlamanın bize ne gibi faydası olabilir?

Projemizi hiç çalıştırmadan ve web arayüzü oluşturmadan, proje iskeletinin nasıl çalıştığını görebilmekteyiz. Örneğin elimizde hiç web formu bile yokken bir ürün ekleme sınıfının POST ile gelen ürünleri veritabanına ekleyip ekleyemediğini anlayabilmekteyiz. Ekleme, silme güncelleme işlemlerini yapan controller sınıflarını ve action metodlarını, view sayfalarına ihtiyaç duymadan test edebilmekteyiz. Bu sayede iş katmanını tasarım katmanından rahat bir şekilde ayırabilmekteyiz.

Bir diğer önemli özellik ise mocking diye adlandırılan taklit edilmiş nesnelerle çalışabilmektir. Gerçek verilerin veritabanından alınıp test edilmesi biraz uzun bir süreç olabilir. Veritabanınız uzakta olabilir. Ya da test makinesinden erişime kapalı olabilir. Bu gibi durumlarda, veritabanı tablolarını, kendi oluşturduğumuz nesnelerle temsil ederek çalışmak zorunda kalırız. Kendi hazırladığımız nesneleri, veritabanından geliyormuş gibi controller sınıflarına gönderebiliriz. Bu sayede çok hızlı bir sonuç alabiliriz. Test sırasında yüzlerce metodun veritabanı bağlantısını açıp, veri alıp sonra bağlantı kapattığını düşünecek olursak, test süresinin dakikalar alabilieceğini öngörebiliriz. Oysa yukarıdaki test sonucunda taklit (mock) nesneler kullanarak test sonucunun saniyelere indiğini görebilmekteyiz.

CodeCoverage2

TDD yaklaşımı sayesinde hiç teste girmemiş kodumuz olup olmadığını görebilmekteyiz. Hatta bir metodun içerisindeki bir sınamanın bile testten geçip geçmediğini anlayabilmekteyiz. Yukarıdaki resimde Controller sınıflarına ait kod kapsam(code coerage) bilgileri yer almaktadır. Amaç %100 kod kapsamını yakalamaktır. Eğer yakalayamıyorsak, kodlarımızda ya teste uymayan bir mimari vardır ya da testten geçirmeyi unuttuğumuz testler vardır. Yukarıdaki tabloda Not Covered (%Blocks) sütununun sıfırlanırken, Covered(%Blocks) sütunun 100’e çıkarıldığını görüyoruz. Hedefe tam anlamıyla ulaşmış bulunuyoruz.

Umarım TDD adına faydalı bir yazı olmuştur. Bir sonraki yazıda görüşmek dileğiyle.

FizzBuzz Kata NCrunch ve NUnit tools

11 Kas

FizzBuzz Kata

FizzBuzz uygulaması, girilen parametrelerden

3 ile bölünebilen sayılar için sonuç Fizz
5 ile bölünebilen sayılar için sonuç Buzz
3 ve 5 ile bölünebilen sayılar için sonuç FizzBuzz
Ne 3 ile ne de 5 ile bölünebilen sayılar için sayının kendisini veren bir birim test pratiğidir.

Unit Test Assert List

1- Result_ForDivisibleByThree_IsFizz
2- Result_ForDivisibleByFive_IsBuzz
3- Result_ForDivisibleByBothThreeAndFive_IsFizzBuzz
4- Result_ForDivisibleNeitherThreeNorFive

Yukarıda birim testlerin listesi çıkarılmıştır ve bu listeye göre testler icra edilmektedir.

NOT: Makinemdeki donanım (işlemci) yetersizliğinden dolayı hem görüntü kaydı hemde Visual Studio’nun çalışması sırasında NCrunch aracı eş zamanlı derlemelerde biraz yavaş kaldı. Çünkü video kaydı başlar başlamaz işlemci %100’lere dayandı.

Tekrar görüşmek dileğiyle.

Open Closed Principle OCP – Tasarım Prensibi

20 Ağu

İyi kodun üretilmesi, bazı kurallara dikkat ederek mümkün olabilmektedir. Bu kurallarda biri de kodun sürekli gelişebileceğinin göz önünde bulundurulmasıdır. Sonuçta biz bir inşaat mühendisi değil, yazılımcıyız. İnşaat mühendisinin yaptığı yapıyı değiştirmesi neredeyse imkansızdır. Ancak yazılım mühendisinin üretimini her zaman geliştirme ve değiştirme imkanı vardır. Ancak bazen öyle kodlar yazarız ki kodumuzu, içerisine beton dökülmüş gibi bir hale sokar, kodu tekrar düzenleme ve yenileme işlemini imkansızlaştırırız.

Kodlama sırasında birçok değişiklik, uygulamalara yeni özellikler eklerken ortaya çıkmaktadır. Kodda yapılandırma işleminin, kodumuzu en az seviyede etkilemesini amaçlamalıyız. Kodun en az seviyede etkilenmesini sağlamak için kodu üretirken bazı durumları göze alarak hareket etmemiz gerekir. Örneğin mevcut kod zaten bir test işleminden geçerek üretilmiş olabilir. Yeni eklemelerle mevcut test durumlarını yeşilden kırmızıya düşürmemek gerekir. Özetle kodumuz geliştirilmeye açık (Open), değiştirilmeye kapalı(Closed) olmalıdır.

Bu tasarım ilkesini bir örnek üzerinden anlatmaya çalışalım. Öncelikle, open closed tasarım ilkesinin kullanılması için bir sebebimizin olması gerekir. Yani durduk yere bu prensip ortaya çıkmış değil. Hatalı bir çözümün sonucunda ortaya çıkmış olmalı.


public class FileGenerator
{
    private readonly FileBase baseFile;
    public FileGenerator(FileBase bBase)
    {
          baseFile = bBase;
    }

    public string Create()
    {
      if (baseFile is PDF)
      {
         var item = baseFile as PDF;
         return item.Create();
      }

      if (baseFile is WORD)
      {
         var item = baseFile as WORD;
         return item.Create();
      }

      return null;
    }
}

public class FileBase
{
}

public class PDF: FileBase
{
   public string Create()
   {
       return "PDF File";
   }
}

public class WORD : FileBase
{
     public string Create()
     {
        return "WORD File";
     }
}

Bu örneğimizde FileBase tipindeki bir temel tipten türetilmiş WORD ve PDF tipleri oluşturulmuştur. Bir tane de dosya oluşturma işlemini gerçekleştirecek olan FileGenerator tipi oluşturulmuştur. FileGenerator sınıfının Create metodu, base tipine göre dosya üretimi yapmaktadır. Buraya kadar hesaplanan  düzenek çalışır vaziyettedir. Ancak bu düzenekte bazı sorunlar vardır. Bunlar:

  • FileGenerator tipi, dosya tipleriyle (WORD, PDF) birebir ilgilenmekte. Yani nesnelere direk eriştiğinden sıkı bağlılık sözkonusu.
  • Yeni bir dosya tipi eklendiğinde FileGenerator sınıfının Create metodunda değişiklik yapılmak zorunda. Oysa değişikliklere kapalı bir sistem olmalı. Ne kadar dosya tipi oluşturulursa oluşturulsun, FileGenerator sııfı etkilenmemeli. Yani gelişime açık sistem olmalıdır.
  • FileGenerator tipine ait create metodunun testleri her yeni ekleme

[Test]
public void FileGenerator_Generates_PDF()
{
    var file = new FileGenerator(new PDF());
    var created = file.Create();
    Assert.AreEqual("PDF file",created);
}

Yani yukarıdaki tes metodu, WORD için, PDF için çalışırken, FileBase tipinden türetilmiş EXCEL şeklinde yeni bir tip için çalışmayacaktır. Create metodunda değişiklik isteyecektir.

Bu gibi dezavantajları göz önünde bulundurarak yeni bir yapı tasarlayalım.


public class FileGenerator
{
    private readonly IFileBase fileBase;

    public FileGenerator(IFileBase fileBase)
    {
         this.fileBase = fileBase;
    }

    public string Create()
    {
         return fileBase.Create();
    }
}

public interface IFileBase
{
     string Create();
}

public class PDF : IFileBase
{
    public string Create()
    {
        return "PDF file";
    }
}

public class WORD : IFileBase
{
    public string Create()
    {
        return "WORD file";
    }
}

Bu tasarımda, FileGenerator ile dosya tipleri arasında IFileBase interface ile bir esneklik sağlanmıştır. Bu sayede FileGeerator tipinin sadece temel tip olan IFileBase tipinden haberi vardır. WORD, PDF gibi tipleri tanımaz. Bu yüzden yeni eklemeler(örn. EXCEL) FileGenerator sınıfında hiçbir etki yapmayacaktır.Yani değişikliğe kapalı (Cosed) şartını sağlamış olduk. İstediğimiz kadar yeni dosya tipi üretebiliriz. Böylece genişlemeye  de açık (Open) olma şartını sağlamış olduk.

Artık birim(metod) testler de değişikliklere dayanıklı hale gelmiş oldu. Yeni bir tip üretip testten geçirebiliriz.

Sürekli değişikliklere rahat cevap verebiliriz ve müşterilerden gelen yeni dosya tiplerine karşı hazırlıklıyız.

Tekrar görüşmek ümidiyle…

Visual Studio code coverage özelliğini açmak

12 Tem

Visual Studio 2010 üzerinde geliştirdiğimiz Test Driven uygulamalrın ne kadarının test kapsamında olduğunu anlamak için code coverage aracından faydalanırız. Tabi bu özellik Visual Studio default test aracıyla çalışırken geçerli. Piyasadaki diğer test araçlarını kullanıyorsanız onlara göre ayar yapmalısınız.

Code coverage özelliğini açmak için şu adımları izlemeliyiz:

  1. Solutution Explorer penceresindeki [Local].testsettings dosyasına çift tıklayın.
  2. Açılan “Test Settings” dialog penceresinde, “Data and Diagnostics”  menüsünü seçin.
  3. Açılan listede, “Code Coverage” [Resim 1] seçerek ‘”Configure” butonuna tıklayın.
  4. “Code Coverage Detail” [Resim2] penceresinden kod kapsamını merak ettiğiniz assembly yi seçin.
  5. Testinizi çalıştırın ve “Test Tools” toolbar içerisindeki “Code Coverage Results” seçeneğinden sonucu inceleyin.
Resim-1
Resim-2

Test Driven Development’a doğru

18 Haz

Programlamaya başlayan herkes, önce hiçbir kurala dikkat etmeden “Run” tuşuna basarak istediği sonucu görmek ister. Bu doğaldır çünkü öğrenmeye yeni başlayan bir programcı adayının tüm işleyiş hakkında detaylı bir bilgisi yoktur. Zaman ilerledikçe fonksiyon, metod, struct, class gibi yapıları öğrenmeye başlar. Öğrendiği yapıları severek uygulamr ve “Oh be ne rahatlık varmış” der. Artık uzun satırlar halinde yazıdığı karmaşık kodları sınıflara, metodlara bölerek yazmaya başlamıştır.

İşi biraz daha ilerleten programcımız daha önce hiç duymadığı bir kavram olan nesneye dayalı programlamayı (Object Oriented Programming) karşısında görür. Neyneya dayalı programlamanın temeli olan Encapsulation(Kapsam), Inheritence(Miraslama) ve Polimorphism(Çok biçimlilik) kavramlarını öğrenir ve “Meğer ben daha önceleri kod yazmıyormuşum.” der. Nesneye yönelik programlama, geliştirilen kodun dış dünya ile izolasyonunu sağlar ve kodda bazı kısıtlamalar yapmaya olanak sunar. Bu sayede yazılımcının geliştirdiği kodu kullanan diğer geliştiriciler, kendilerini ilgilendirmeyen mahrem kodları görmezler ve kafa karışıklığı yaşamazlar. Ayrıca görülmemesini istediğimiz algoritmalar varsa private erişim belirleyiciler ile dış dünyadan gizleyebilirler. Kısaca nesneye dayalı programlama, geliştirilen API üzerinde bazı kısıtlamalar yapmaya ve API içerisinde gerekli durumlarda miraslama ile hiyerarşik yapının kurulmasına olanak sağlanmış olur. Artık programcı, ikinci adım olan nesneye dayalı programlamayı da öğrenmiştir ve uygulamaya başlamıştır.

Bir süre sonra programcı, Test Driven Development(TDD) kavramıyla karşılaşır. Programcının kodlama geçmişine aykırı, kodlama mantığını ve kodlama biçimini kökten değiştirecek yeni bir akımdır bu. Alışılagelmiş şelale(Waterflow) tarzı kod geliştirmeyi reddeden, önce testin yazılmasını gerektiren ve testten başarıyla geçen konun düzenlenerek üretime katılmasını sağlayan bir yöntemdir. Programcı, TDD mantığını öğrendiğinde, ya onu çok sever ya da ondan nefret eder. Ortası yoktur bu işin. Çünkü kodun bir kısmı test edilebilir, bir kısmı teste uymayan şekilde yazıldığında çelişkiler yumağı oluşmaya başlar.

Genelde TDD iyi öğrenilmediği taktirde, saçma sapan bir iş olduğu düşünülerek terk edilir. Ama işin aslı öyle değildir. Sanılanın aksine TDD, çok gerekli ve yazılımın başarıya ulaşması için önemli bir yöntemdir.   Koda olan güveni arttırır. Başarılı sonuç almaya daha yatkın bir yöntemdir. Geleneksel yazılım yöntemleriyle başlatılan projelerin %50’den azının başarıya ulaştığı bir ortamda, denemeye değer bir yöntem olduğu açıktır.

Geleneksel yöntemlerle başarıya ulaşan projelerin sayısının az olmasının sebebi nedir? Sebeplerin en başında, ürünün sürekli gelişen bir yapıda olduğunun göz ardı edilmesi gelmektedir. Gelişim varsa değişimde vardır. Sürekli değişimin kaçınılmaz olduğu bir projede her şeyi önceden kestirilmeye çalışımak, sonu belli olmayan bir maceraya atılmak gibidir. Hal böyle olunca, yöntemde bir sorun olduğu ortaya çıkmaktadır.

Test edilmeyen kodların birbirini nasıl etkilediği, davranışlarının nasıl olduğu bilinmediğinden, zamanla kod kırlmaları yaşanmaya başlayar. Aslında geleneksel yöntemlerde de test işlemi yapılmaktadır. Ancak bu test işlemi, özel bir test geliştirme aracıyla değil yazılımcının geliştirdiği arayüzlerle v.s yapılmaktadır. TDD yönteminde ise kodların davranışları önce bir test aracından geçirilir, üretilen kodlar kullanıma hazır bir şeklide uygulama ortamına sunulmaktadır.

Bu arada test driven development ile geliştirme yapanlar, önce test yazıp sonra kod üretimi yapak zorunda da değildir. Kodu yazdıntan sonra da test sınıflarını yazanlar var. Ancak bu yöntem TDD geleneğine uygun bir yöntem değildir

Özetle test driven development, geleneksel yöntemlerin dışında bir tekniktir. Bu tekniğe alışmak biraz zordur. Çünkü bu yöntem sağ elimizle yapmaya alıştığımız işleri, sol elimizle yapmaya zorlar gibidir. Önce kodu yazıp sonra ara yüzümüzde test etmek yerine, önce testi yazıp sonra testten geçen kodları çalışan kod olarak kullanırız.

Bu yöntem çevik(agile) süreçlerin de önem kazandığı günümüzde çoğu yazılımcının gözdesi haline gelmiştir. Büyük yazılım şirketlerinin de vazgeliçmez yöntemidir.