Unit Test ve Kodun Varlığının Testi

15 Eki

Aşağıda bulunan controller sınıfının test edileceğini düşünelim.

[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ApiControllerBase
{
    [HttpGet]
    public async Task<ActionResult<TodoItemBriefDto>> GetTodoItems([FromQuery] Query query)
    {
        return await Mediator.Send(query);
    }
}

Soru: Sınıfa ait her şeyi test etmeli miyim?

1 – TodoItemsController sınıfı ApiControllerAttribute attribute sahiptir.
2 – TodoItemsController sınıfı RouteAttribute attribute sahiptir.
3 – TodoItemsController sınıfı “GetTodoItems” isimli bir meoda sahiptir.
4 – RouteAttribute attribute “api/[controller]” diye bir parametreye sahiptir.
5 – GetTodoItems metodu ActionResult döndürür.
6 – GetTodoItems metodu Query tipinde bir parametre alır.
7 – GetTodoItems metodu TodoItemBriefDto tipinde bir sonuç verir.
8 – GetTodoItems metodu null bir Query parametresine ArgumentNullException fırlatır.
9 – GetTodoItems metodu Query parametresi ile TodoItemBriefDto tipinde bir sonuç döndürür.

Unit test olarak bu testlerin hangileri yazılmalıdır?

Eğer unit test yazıyorsak, bir unit (metod) için belli girdiler ile belli sonuçların döndürdüğünü incelememiz gerekir. Çünkü unit testte kodun varlığı ve işlevselliği test edilir.

O halde yukarıdaki maddelerde 8 ve 9 numaralı maddelerde bir unit(method) test yapılmaktadır.
Diğer maddeler ise kodun var olup olmadığını test etmek için yazılmıştır.

Unit Test metodlarında bulunması gereken özelliklerden burada bahsedilmiştir.

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.

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.

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.

Math Operations (Matematiksel) Kata .Net

19 Kas

Matematiksel işlemler katası

Bu katanın amacı, değişkenleri ve sabitleri belli olan matematiksel formülleri çözebilen bir uygulama geliştirmek.

Örnekler / Examples
————————————————-

1 + 1 => constant + constant
x + y => variable + variable
x + y -1 => variable + variable + constant
(x+y) / 2 =>(variable + variable) / constant
(x*y)-(x/3)=>(variable + variable) – (variable/3)

Microsoft .Net ortamında geliştirilmiş ve Test Driven Development kuralları uygulanmış olan bir kod katasıdır.

Kaynak kodlara github hesabımdan ulaşabilirsiniz.

Not: Bu katada geliştirilen Math Expression örneği, “The C# Programming Language” kitabındaki bir bölümden esinlenerek yapılmıştır.

Tekrar görüşmek dileğiyle.

NCrunch Visual Studio Eklentisi

6 Kas

NCrunch, kodlama esnasında birim testleri (unit tests) otomatik ve eş zamanlı olarak çalıştıran bir Visual Studio eklentisidir.

Projelerini teste dayalı geliştirenler için mükemmel bir araç diyebilirim. Performans konusunda donanımınızın yeterliliğine ve test sayınıza göre NCrunch analiz süresi değişiklik gösterebilir.

NCrunch aracı, siz kodunuzu yazarken arka planda otomatik olarak kodu testlerden geçirerek durumu size bildirir.

İsterseniz testleri otomatik olarak değil de el yordamıyla da yaptırabilirsiniz. Testlerin durumunu aşağıdaki pencereden de görebilirsiniz.

Büyük projeler düşünülerek, Visual Studio IDE performansı üzerindeki etkileri en aza indirmek için, proje mimarisindeki bağımlılıkları analiz ederek,  testleri yürütme işlemi için öncelik sırasına koyar ve asenkron süreçler, ek işlemci çekirdeği kullanır.

Bir diğer özellik ise test kapsamına girmeyen kodların belirlenmesidir.

Siyah noktayla işaretlenen satırların test edilmediğini anlayabilmekteyiz. Yeşil noktayla işaretlenen kod satırlarının testlerden başarıyla geçtiğini göstermektedir.

Testlerin çalıştırılma süresini ölçen özelliği sayesinde ise uzun sürebilecek testler işaretlenir.

NCrunch aracının noktasal işaretleme özelliğinin renkleri isteğe göre değiştirilebilmektedir.

Test Driven Development yapanlar için çok iyi bir araçtır.

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.