Uygulamalarınızı geliştirirken, kodun küçük bir kısmını test etmek amaçlı yazdığınız koda birim test denir. Birim test, geliştirmiş olduğunuz kodu test etmek için yazdığınız kod bloğudur.
Bu kod bloklarını üretim kodlarınızın bir parçası haline getirdiğinizde faydası görülecektir. Üretim kodları ile bağlantısı sağlandıktan sonra her bir fonksiyonun davranışını kontrol ederek geliştirdiğiniz kod bloklarındaki akışa göre kabul kriterlerini karşılıyor mu karşılamıyor mu bunu izleyip yorumlayabilirsiniz.
Birim test aynı zamanda kodlarınızı kullanacak diğer arkadaşlar/yazılımcılar tarafından okunduğunda onların kodları nasıl kullanması gerektiğini anlatması açısından da öğrenme (learning) testi gibi önemli bir göreve sahiptir.
Çalışma yapacak olan yazılımcı arkadaşların bu testler ile fonksiyonların nasıl çalıştığını kavramaları da hızlı olacaktır. Bir fonksiyona bir özellik eklendiğinde etki analizi yapılırken mevcut sisteme etkileri konusunda çalışma yapmasına gerek kalmadan o kod bloğu için yazılmış, bir birim test fonksiyonu çalıştırıp testin geçip geçmediğini gözlemlemesi yeterli olacaktır. Aynı zamanda uygulama geliştirme sürecinin başında birim testler geliştirildiği için oluşabilecek hataların maliyetlerini düşürür.
Biz blog yazımızda .Net’te kullanılan test framework’lerinden NUnit framework ile testler yazmaya çalışacağız.
NUnit Projesi Nasıl Oluşturulur?
Kendimize test işlemlerini yapacağımız bir arayüz uygulaması tasarlayarak başlayalım.
Örneklerimizi basit bir e-ticaret ürün seçim ve sepet işlemleri üzerinden yapmaya çalışacağız.
Daha sonrasında bu çözüm klasörü altına NUnit projemizi ekleyerek testlerimizi yazabiliriz.
Birim test yazarken üç tane aşama göreceğiz öncelikle bunların ne olduğuna kısaca değinelim. Kullanılacak aşamalar Arrange, Act ve Assert aşamalarından oluşur.
Arrange
Fonksiyon içerisinde kullanılacak olan değişkenlerin tanımlandığı, nesnelerin oluşturulduğu bölümdür.
Act
Test edilecek fonksiyonun çalıştırıldığı bölümdür.
Assert
Çalıştırılan fonksiyonun doğrulamasının yapıldığı bölümdür. Beklenen ve gerçekleşen sonuçların kıyaslandığı bölümdür. Burada birden fazla karşılaştırma yapılabilir ancak beklenen en az sayıda doğrulamanın yapılmasıdır.
Şimdi test uygulamamızda, sepetimize ürün ekleyip çıkaran fonksiyonlarımızın testlerini yazalım.
Test fonksiyonlarımızın testini başlatmak için test explorerdan “run all test” diyerek testleri başlatabiliriz.
Test Explorer, Test ya da TestCase ile işaretlediğimiz fonksiyonların test fonksiyonu olduğunu anlayarak Assert kullanımlarına göre de testin sonucunu bize sunar.
Assert sınıfı ile testlerde beklenen ve gelen değerlerin eşitliği gibi bir kontrol sonucuna göre başarılı ya da başarısız sonucu elde edebiliyoruz. Bu yöntem dışında başka durumları kontrol edebiliyor muyuz bunları da kontrol edelim isterseniz.
Uygulamamızda AssertionControlExamples fonksiyonu altında Assert sınıfının örneklerini çalıştıracağız.
Karşılaştırma Kısıtlamaları (Comparison Constraints)
Assert.That(result, Is.EqualTo(5));
Assert.That(result, Is.Not.EqualTo(5));
Is.EqualTo/Is.Not.EqualTo gerçekleşen değerin beklenen değere eşit mi, değil mi olduğunu kontrol etmemizi sağlar.
Assert.That(result, Is.GreaterThan(expected));
Assert.That(result, Is.GreaterThanOrEqualTo(expected));
GreaterThan/ GreaterThanOrEqualTo gerçekleşen ve beklenen değerlerinin daha büyük ya da eşit olma durumlarını kontrol eder.
Assert.That(result, Is.LessThan(expected));
Assert.That(result, Is.LessThanOrEqualTo(expected));
LessThan/LessThanOrEqualTobeklenen ve beklenen değerlerinin daha küçük/az ya da eşit olma durumlarını kontrol eder.
Assert.That(result, Is.InRange(0, expected));
InRange fonksiyonu gerçekleşen değerin verilen aralıkta olup olmadığını kontrol eder.
Conditional Constraints-Koşullu Kısıtlamalar
string[] fruits = new string[] { “Elma”, “Muz”, “Erik” };
Assert.That(fruits, Is.Null);
Assert.That(fruits, Is.Not.Null);
Assert.That(fruits.Length > 0, Is.True);
Assert.That(fruits.Length > 0, Is.False);
Assert.That(fruits, Is.Empty);
Compound Constraints -Bileşik Kısıtlamalar
Assert.That(result, Is.GreaterThan(4).And.LessThan(10));
Assert.That(result, Is.LessThan(1).Or.GreaterThan(4));
Assert.That(result, Is.Not.EqualTo(7));
Mantıksal operatörler ile assert sınıfında aynı anda birçok durumun kontrolünü sağlayabilir kendinize özel durumlar oluşturabilirsiniz.
Dize/Metin Kısıtlamaları (String Constraints)
const string strinResult = “ArchiTecht”;
Assert.That(strinResult, Is.EqualTo(“architecht”));
Assert.That(strinResult, Is.Not.EqualTo(“architecht”));
EqualTo/Not.EqualTo fonksiyonları string karşılaştırmayı yapar
Assert.That(strinResult, Is.EqualTo(“archiTecht”).IgnoreCase);
EqualTo fonksyonu string karşılaştırma yapar IgnoreCase ile kullandığınızda case-sensitive karşılaştırmayı devre dışı bırakmış olursunuz.
Assert.That(strinResult, Does.Contain(“arch”));
Assert.That(strinResult, Does.Not.Contain(“echt”));
Assert.That(strinResult, Does.Contain(“arch”).IgnoreCase);
Assert.That(strinResult, Does.Not.Contain(“echt”).IgnoreCase);
Does.Contain/ Does.Not.Contain fonksiyonları tanımlanan metin içerisinde Contain içerisindeki tanımlanan metni arar var mı yok mu diye. Beraberinde IgnoreCase kullandığınızda case-sensitive karşılaştırmayı devre dışı bırakmış olursunuz.
Assert.That(strinResult, Is.Empty);
Assert.That(strinResult, Is.Not.Empty);
Empty/Not.Empty fonksiyonları tanımlanan metnin boş olup olmadığını kontrol eder
Assert.That(strinResult, Does.StartWith(“arch”));
Assert.That(strinResult, Does.Not.StartWith(“echt”));
StartWith/Not.StartWith fonksiyonları gerçekleşen değerin parametredeki metin ile başlayıp ya da başlamadığını kontrol eder.
Assert.That(strinResult, Does.EndWith(“arch”));
Assert.That(strinResult, Does.Not.EndWith(“echt”));
EndWith/Not.EndWith fonksiyonları gerçekleşen değerin parametredeki metin ile bitip bitmediğini kontrol eder.
Assert.That(strinResult, Does.Match(“a*t”));
Assert.That(strinResult, Does.Not.Match(“t*a”));
Match fonksiyonları ile de regex kontrollerini sağlayabilirsiniz.
Koleksiyon Kısıtlamaları-Collection Constraints
int[] years = new int[] { 2022, 2021, 2021, 2023, 2024, 2025 };
Assert.That(years, Is.All.Not.Null);
Assert.That(years, Is.All.GreaterThan(2020));
Assert.That(years, Is.All.LessThan(2053));
Assert.That(years, Is.All.InstanceOf<Int32>());
Assert.That(years, Is.All.InstanceOf<string>());
Assert.That(years, Is.Empty);
Assert.That(years, Is.Not.Empty);
Assert.That(years, Has.Exactly(2).Items);
Has.Exactly(?).Items fonksiyonu dizi içerisinde kaç tane Item olduğunu kontrol eder.
Assert.That(years, Is.Unique);
Unique fonksiyonu tüm dizi elemanlarının benzersiz olup olmadığını kontrol eder.
Assert.That(years, Contains.Item(4));
Contains.Item fonksiyonu dizi içerisinde 4 olan bir item var mı kontrol eder.
Assert.That(years, Is.Ordered.Ascending);
Ordered.Ascending fonksiyonu dizi de Ascending bir sıralama var mı kontrolu yapar.
Assert.That(years, Is.Ordered.Descending);
var basketItems = basketHelper.BasketItems;
Assert.That(basketItems, Is.Ordered.Ascending.By(“Quantity”));
Assert.That(basketItems, Is.Ordered.Descending.By(“Quantity”));
Assert.That(basketItems, Is.Ordered.Ascending.By(“Quantity”).Then.Descending.By(“UnitPrice”));
Exceptions Constraints-İstisnalar Kısıtlamalar
BasketItem item = new BasketItem();
item.Quantity= 0;
Assert.Throws<Exception>(() => item.CalculatePrice());
Exception ex = Assert.Throws<Exception>(() => item.CalculatePrice());
Assert.That(ex.Message, Is.EqualTo(“Test için method exception döndü mü ?”));
Type / Reference Constraints-Tip / Referans Kısıtlamaları
Product productItem = new Product();
Assert.That(productItem, Is.InstanceOf<Product>());
Assert.That(productItem, Is.Not.InstanceOf<string>());
Assert.That(productItem, Is.TypeOf<Product>());
Assert.That(productItem, Is.AssignableTo<Product>());
Directory / File Constraints-Dizin / Dosya Kısıtlamaları
string path = @”C:\Users\Documents”;
Assert.That(new FileInfo(path), Does.Exist);
Assert.That(new FileInfo(path), Does.Not.Exist);
Assert.That(new DirectoryInfo(path), Does.Exist);
Assert.That(new DirectoryInfo(path), Does.Not.Exist);
Assert.That(path, Is.SamePath(@”c:\Users\images”).IgnoreCase);
Assert.That(new DirectoryInfo(path), Is.Empty);
Assert sınıfından türetilecek kısıtlamaları yukarıda vermeye çalıştım. Ayrıca testlerde kullanılan attribute örneklerini de kısa anlatımlarla özelliklerini daha iyi anlayabilmek için uygulamakta fayda olacaktır.
TestFixture
Bu özellik bir sınıf için kullanıldığında o sınıfın test yöntemleri içerdiğini gösterir.
[TestFixture(Description =“Test yapıyoruz”,Author =“Architecht”,Category =“Unit Test Örnekleri”,Ignore =“Vazgeç”,IgnoreReason =“Vazgeçme sebebi”,Reason =“Sebep 1”,TestName =“Unit Test”)]
Yukarıdaki kullanım şeklinde özelliklerini görebilirsiniz. Atamalarını yaparak kendi test sınıflarınızı özelleştirebilirsiniz. Bu attribute için bazı kısıtlamalardan bahsedebiliriz.
- Sadece bir sınıf üzerinde kullanabilirsiniz.
- TestFixture herhangi bir parametreye sahip değilse sınıf default contructor bulundurmalı.
- TestFixture parametreye sahip ise sınıfın contructorı da testfixture ile eşleşecek şekilde parametre almalıdır.
- Bir sınıf da birden fazla TestFixture kullanılabilir
- Soyut sınıflarda kullanabiliriz.
TestCase
Testcaseattribute iki amaç için kullanılır. Bir fonksiyonun test fonksiyonu olduğunu belirtmek ve o fonksiyona parametre geçirmek için kullanılır.
TestCase içinde bir fonksiyonun parametrelerini gönderebilirsiniz.
Setup ve OneTimeSetUp
[OneTimeSetUp] ile [SetUp] etiketleri arasındaki farkı bir örnekle anlatmak gerekirse iki adet test fonksiyonunuzun olduğunu düşünün.
Run All komutu vererek iki test fonksiyonunda çalıştıracaksınız. [SetUp] etiketini kullandığınızda ilk test fonksiyonu için bir webdriver nesnesi yaratılır ve bir tarayıcı penceresi açılır test fonksiyonu çalıştıktan sonra [TearDown] etiketine girerek test fonksiyonu tamamlanır ve tarayıcı penceresi kapanır. İkinci test fonksiyonu için aynı işlemler tekrarlanır.
Yani her test fonksiyonu için yeni bir tarayıcı penceresi açılmış olur. [OneTimeSetUp] etiketi kullandığınız zaman tüm test fonksiyonları tek tarayıcı da çalışır ve test bitirilir.
TearDown ve TestFixtureTearDown
TearDown attribute ise test koleksiyonu içerisindeki her bir test çalıştıktan sonra çağrılacak olan fonksiyondur. TestFixtureTearDown attribute ile tanımlanan fonksiyon ise bir test koleksiyonu içerisindeki tüm testler çalıştırıldıktan sonra çağrılacaktır.
Bu fonksiyonların amacı ise testler sonrası nesnelerin sonlandırılması, kaynakları bırakmalarını sağlamaktır. Örneğin SetUp fonksiyonu ile açtığımız bir veri tabanı bağlantı nesnesini TearDown fonksiyonu ile kapatırız.
Moq Framework Nedir?
Test edilmek istenilen sınıfların gerçek nesnelerini kullanmak yerine onları simüle etmemizi sağlayan ve böylece test süreçlerindeki maliyetleri minimize etmemizi hedefleyen bir framework’tür.
Önce projemize nuget package ile Moq framework kuralım.
Daha sonra projemizde checkout adında bir sınıf oluşturarak GoBankForPayment adında bir fonksiyon ile bankanın ödeme sayfasına gittiğimizi düşünelim. Her test çağrımında ödeme sayfasına gitmek maliyetli olacaktır. Bizlerde her test çalıştığında ödeme sayfasına gitmeden fonksiyonun bize başarılı olarak dönmesini simüle edelim.
Bir sınıfı taklit edebilmek için o sınıfın arayüzü uygulanarak kullanılmalıdır. Aksi taktirde projeniz derlenirken hata alır.
Aşağıdaki fonksiyonumuzda GoBankForPayment fonksiyonumuzda banka sayfasına request yapılıyordur. Ancak biz burada moq framework kullanarak fonksiyonumuzu simüle ederek ilgili sayfalara request yapılmasını atlatıyoruz ve fonksiyona KUVEYTTURK banka adını gönderdiğimizde her seferinde bize Architecht dönsün diyoruz ve assert sınıfında da kontrolümüzü yapıyoruz
Ayrıca fonksiyonumuzda Mock sınıfı üzerindeki Verify fonksiyonunu da şartlarımıza eklemiş olduk. Verify fonksiyonu kullanacağımız fonksiyonun kaç kez çalıştığını ya da çalışması gerektiğini söyleyen fonksiyondur. Biz örneğimizde en az bir kere çalışması durumunda testin başarılı olacağını belirttik.
Örnek kod blokları için: https://github.com/mikailkarakaya/unittest
Kaynakça:
https://dotnetpattern.com/ (Erişim : 24.07.2023)
https://docs.nunit.org/articles/nunit/writing-tests/attributes.html (Erişim : 24.07.2023)
http://www.csharpnedir.com/articles/read/?id=564&title=NUnit%20ile%20Birim%20Test (Erişim : 24.07.2023)
https://docs.educationsmediagroup.com/unit-testing-csharp/nunit/quick-glance-at-nunit (Erişim: 24.07.2023)
https://www.lambdatest.com/blog/asserts-in-nunit/ (Erişim : 24.07.2023)