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.

Test Edilebilir Tasarımın Düzenlenmesi (Refactoring)

28 Nis

Refactoring işlemi kodun işlevini değiştirilmeden daha okunabilir hale getirmek amacıyla yeniden düzenlenmesi işlemidir. Özellikler test güdümlü programlamada (Test Driven Development) gerekli olan gevşek bağlılık (loosely coupled) prensibinin uygulanabilmesi için bazı kurallara uymak durumunda kalabiliyoruz.

Özellikle sıkı bağlılığın(tightly coupled) yok edilmesi için uygulamak gereken bazı özellikler vardır. Bunlar:

  • Uygulama kodunu bir interface ile temsil etmek
  • Test edilen sınıfa soyut nesneleri aktararak soyut nesneler üzerinden işlemleri yapmak.
  • Soyut Interface nesnesini constructor seviyesine almak.
  • Soyut interface nesnesini bir property olarak almak.
  • Soyut interface nesnesini metod çağırma işleminden önce almak.

Constructor seviysinde nesne almak (Constructor Injection)

Bu senaryoda, constructor yardımıyla interface nesnesini kabul edeceğiz. Daha sonra alınan bu nesne kullanılmak üzere private erişim belirleyicili bir metoda aktarılır.


public class ReportSender{

       private IReporter reporter;

       public ReportSender(IReporter reporter){
              this.reporter = reporter;
       }

       // Diğer üyeler
       //....
       //..
       //.
 }

Problemler

Test edilen sınıf eğer birden fazla soyut nesne alıyorsa constructor sayısı veya bir constructorun aldığı parametre sayısı artacak. Bu da kodun düzenini ve okunabilirliğini bozacaktır.

Bu sorunu çözmek için kullanılacak yöntemlerden biri, gerekli tüm başlangıç değerlerinin bir sınıf içerisinde başlatılmasıdır (parameter object refactoring).

Bir diğer çözüm ise bağımlılıkların ters çevrilmesidir(Inversion of Controls IoC). Bu işlemi yapan hazır paketler bulunmaktadır. Örneğin Spring.NET, Castle Windsor, Ninject gibi. Bu konteynır yapılar sayesinde bir interface nesnesine karşılık gelecek somut nesne belirlenir ve o interface görüldüğünde somut nesnesi bağlanmaktadır.

Ne zaman kullanılmalı

Constructor seviyesinde nesneler alıp başlatmak istediğinizde, IoC konteynırlar nesne oluşturma sırasında hız kazandırmaktadır. Öte yandan constructor parametreler, API’nizi kullananlara, parametrenin opsiyonel olmadığını göstermektedir.

Property seviyesinde nesne almak(Property Injection)

Bu senaryoda, esnek bağı sağlayacak olan, bir property olacaktır. Dependency injection olarakta bilinen bu teknik bir önceki yönteme benzemektedir. Constructor injection gibi bu yöntem de API üzerinde gerekli bağımlılıkları göstermektedir.


public class ReportSender{

       public  IReporter Reporter {get; set;};

       // Diğer üyeler
       //....
       //..
       //.
}

Ne zaman kullanılmalıdır

Bu teknik, bağımlılığın isteğe bağlı olduğu durumlarda kullanılır. Bağlılığın opsiyonel bırakılmak istenen durumlarda tercih edilebilir.

Metod çığırılmadan önce nesne almak

Bu teknikte ise nesneyi almak için property veya constructor yerine factory sınıfları kullanılır. Bu sayede soyut nesne, sınıf içerisine aktarılmış olur.


public class ReportSender{

       private IReporter reporter;

       public ReportSender(IReporter reporter){
              this.reporter = FactoryClass.CreateReportInstence();
       }

       // Diğer üyeler
       //....
       //..
       //.
 }

Ne zaman kullanılmalıdır

Bu teknik, sınıf girdilerini(input) kontrol etmek istendiğinde kullanılabilir. Ancak test ortamında, bağımlılıkları test edmek istediğinizde kötü sonuç verecektir. Çünkü test ortamında sınıf içine mock object aktarıp davranışları test etmek istendiğinde başarısız oluruz.

Asp.net MVC JsonResult Tipli Metodlar İçin Unit Test

24 Nis

ASP.NET MVC mimarisi üzerinde test güdümlü çalışırken karşılaşacağımız test tiplerinden biri de JsonResult tipindeki action metodların testidir. Test tipinde veri döndüren metodların testleri kolay olabilmektedir. Ancak JsonResult tipindeki metodlar veriyi JSON tipine dönüştürerek sunduğu için, metod tarafından bize sunulan veriler text şeklinde olmayacaktır.

Normalde önce test metodu yazılarak gidilmelidir ancak burada test kodu geliştirmeyi konu almadığımız için, yazılmış metodu test ederek ilerlemeye çalışalım.

Test edilecek metod şu şekilde belirlenebilir.

public JsonResult GetMenu()
{
     var menu = new List<Menu>
     {
         new Menu{ MenuID=1, MenuName="Hamburger, Cola, Cips"},
         new Menu{ MenuID=2, MenuName="Cheeseburger, Cola, Cips"},
         new Menu{ MenuID=3, MenuName="Pizza, Cola, Cips"}
     };

     return Json(menu,JsonRequestBehavior.AllowGet);
}

Yemek menüsünü JSON formatında sunan bir metod.

Bu metodun test edilmesi sırasında aslında bazı sorulara yanıt arıyoruz olacağız. Bir başka deyişle önce test metodunun yazıldığı senaryolarda iddalar doğrulanarak gidilir. Bizim senaryomusda ise GetMenu metodunun:

  • Tipinin JsonResult olduğu iddası
  • Boş veri döndürmediği iddası
  • Dönen verinin formatının ne olacağının iddası

ortaya atılarak doğrulanmaya çalışılmıştır.


[TestMethod]
public void GetMenu_ShouldReturn_JSON()
{
    // Arrange
    HomeController controller = new HomeController();

    // Act
    var result = controller.GetMenu() as JsonResult;

    // Assert
    Assert.IsInstanceOfType(result, typeof(JsonResult));
    Assert.IsNotNull(result.Data);
}

İlk iki iddamızı bu metod sayesinde dğrulayabiliriz. Bu test başarıyla geçecektir. Ancak metodun döndürdüğü verinin ne olduğunu veya string şeklinde alınıp şuna eşittir denilebilmesi için serileştirilme işleminden geçirilmelidir. Çünkü result.Data nesnesi object tipindedir, ve içeriği okunamaz durumdadır. Serileştirilme işlemi sonrasında işlenebilir bir somut veri elde edilebilir.

     Assert.AreEqual("", result.Data.ToString());

Şeklinde bir idda hata döndürecektir. Test Fail duruma düşecektir.

Aşağıdaki lekilde serileştirme işlemini gerçekleştirdiğimizde bu tür sorunlarla karşılaşmayacağız.

[TestMethod]
public void GetMenu_ShouldReturn_JSON()
{
     // Arrange
     HomeController controller = new HomeController();

     // Act
     var result = controller.GetMenu() as JsonResult;

     var serializer = new JavaScriptSerializer();
     var output = serializer.Serialize(result.Data);

     // Assert
     Assert.IsInstanceOfType(result, typeof(JsonResult));
     Assert.IsNotNull(result.Data);
     Assert.AreEqual(@"[{""MenuID"":1,""MenuName"":""Hamburger, Cola, Cips""},
                        {""MenuID"":2,""MenuName"":""Cheeseburger, Cola, Cips""},
                        {""MenuID"":3,""MenuName"":""Pizza, Cola, Cips""}]",
                        output);
}

Tekrar görüşmek dileğiyle…

ASP.NET MVC Controller Test Ortamında UpdateModel Davranışları

4 Mar

Bu yazıda ASP.NET MVC ortamında test sınıflarımı hazırlarken karşılaştığım küçük bir ayrıntıyı analtmak istiyorum.  Bu noktadan itibaren kullanacağım örnek ASP.NET MVC3 ortamında yürütülen Nerddinner projesindeki Edit action metod testi olacaktır. Neden Edit action metodunu test ediyorum? Bunun sebebi Edit işlemi sırasında Controller temel sınıfının helper metodları olan UpdateModel veta TryUpdateModel metodlarının özel bir durumunun olması.

UpdateModel helper metodu, form ortamında bir bilgi güncellemesi yapıldığında, formdan  post yöntemi ile gelen değerleri ilgili nesneye bağlamak için kullanılır. Aşağıdaki örnekte görüldüğü üzere FormCollection içierisinden gelen form bilgileri, UpdateModel(dinner) ile dinner nesnesini güncellemektedir.


[HttpPost, Authorize]
public ActionResult Edit(int id, FormCollection collection) {

       Dinner dinner = dinnerRepository.Find(id);

       if (!dinner.IsHostedBy(User.Identity.Name))
            return View("InvalidOwner");

       try {
            UpdateModel(dinner);
            dinnerRepository.Save();
            return RedirectToAction("Details", new { id=dinner.DinnerID });
       }
       catch {
            return View(dinner);
       }
}

O halde bu noktadan sonra şunu anlıyoruz ki test ortamında da UpdateModel metodunu kullanan metodların testi için FormCollction nesnesi kullanmalıyız. Bu güncelleme işlemi nasıl yapılmaktadır? UpdateModel tarafından  Controller sınıfının “ValueProvider” özelliğine bir FormCollection nesnesi atanarak yapılmaktadır.

[TestMethod]
public void EditAction_Should_Redirect_When_Update_Successful() {

      // Arrange
      var controller = CreateDinnersControllerAs("SomeUser");
      int id = 1;

      FormCollection formValues = new FormCollection() {
         { "Dinner.Title", "Another value" },
         { "Dinner.Description", "Another description" }
      };

      controller.ValueProvider = formValues.ToValueProvider();

      // Act
      var result = controller.Edit(id, formValues) as RedirectToRouteResult;

      // Assert
      Assert.AreEqual("Details", result.RouteValues["Action"]);
      Assert.AreEqual(id, result.RouteValues["id"]);

}

Dikkat: FormCollection kullanılmayan test metodları UpdateModel metodunun çalıştırılması(invoke) sırasında NullReferenceException hatası verecektir.

Soru: Eğer Edit action metoduna parametre olarak FormCollection değilde nesnenin kendisi atılsaydı veya bir ViewModel patterne sahip bir nesne gönderilirse ne oladak?

DinnerFormViewModel adında bir nesne ve bu nesne içinde de Dinner tipinde Dinner adında bir property olduğunu varsayalım.


[HttpPost, Authorize]
public ActionResult Edit(int id, DinnerFormViewModel model) {

       Dinner dinner = dinnerRepository.Find(id);
       if (!dinner.IsHostedBy(User.Identity.Name))
            return View("InvalidOwner");

       try {
            UpdateModel(dinner, "Dinner");
            dinnerRepository.Save();
            return RedirectToAction("Details", new { id=dinner.DinnerID });
       }
       catch {
            return View(dinner);
       }
}

Bu durumda UpdateModel metodunun, prefix(ön ek) belirlenebilen versiyonunu kullanıyoruz. Yani DinnerFormViewMolel içerisindeki “Dinner” ile başlayan nesneler “ValueProvider” Property’sine bağlanacak.

Codeproject Makale-1 Test Driven Developmnet

31 Oca

Merhaba,

Dünyanın çok çeşitli yerlerinden geliştiricilerin makalelerini yayınladığı uluslarası bir konumda olan makale sitesi codeproject artık benim yazdığım bir makaleye de ev sahipliği yapıyor. Değişik bir tecrübe olması açısından ve hep tüketen konumda olmamak adına bir makale üretip yayınlamayı düşündüm ve bu düşüncemi gerçekleştirdim.

Sitede junior developer seviyeden senior developer seviyesine hatta team leader seviyesine kadar ulaşmış kişiler makale yayınlamakta.  Üye sayısının çok fazla olması sebebiyle (ki en son 8 milyon civarıydı, şu an kaçtır bilemiyorum) yazılan makalelere anında yorumlar gelmesi gerçekten çok hoş bir durum. Gelen yorumlardaki değişik fikirler, eleştiriler gelişiminize şüphesiz katkı sağlıyor.

Yazdığım makalenin konusu Test Driven Development ile geliştirilen bir Factorial uygulaması.

Uygulamanın akışı baştan sona detaylarıyla anlatılmakta ve örnek uygulama kodlarıyla birlikte verilmektedir.

Ortam benim hoşuma gitti :). Herkese tavsiye ederim. Makaleye buradan ulaşabilirsiniz.

Herkese kolay gelsin tekrar görüşmek dileğiyle…