.NET’te Birim Testler Nasıl Yazılır?

recep orhan
6 min readNov 7, 2021

Yazılımın farklı senaryolarda nasıl davranacağının ve doğru sonuçları üretip, üretmediğinin test edilmesi gerekmektedir. Bu testler yapılmadıysa yazılım ürün haline geldikten ve son kullanıcı ile buluştuktan sonra birçok hata ile karşılaşılır, sürekli yama yapma ihtiyacı ortaya çıkar. Önceden test yazmayıp süreden kazanıldığı düşünülse de sonradan bakım maliyeti olarak geri dönüşü olur.

Test Tipleri

Geliştirilen yazılımın istenilen sonucu vermesi, farklı girdilerde, farklı kullanıcı davranışlarına göre tepkileri, ağır yük altında cevap verebilirliği gibi birçok konunun bu durumlarla karşılaşmadan önce ortamların oluşturularak test edilmesi gerekmektedir. Bu durumlar için farklı test tipleri geliştirilmiştir.

Birim Testleri (Unit tests)

Birim testleri yazılımın en küçük iş parçacıklarının metotların test edilmesidir. Metoda verilen parametreye karşılık istenilen sonucun alınmasını test eder. Yazılım geliştiricinin sorumluluğundadır.

Entegrasyon Testleri (Integration tests)

Birim testlerinden farklı olarak entegrasyon testleri birden çok modülü kapsayacak şekilde, birbirleriyle olan iletişimin doğruluğunu test eder.

Yük Testleri (Load tests)

Yazılımın belirli sayıdaki isteklere taleplere cevap verebilirliğini test eder.

Birim Test Araçları

.NET’te test amaçlı kullanılan farklı araç ve kütüphaneler vardır. Bunlar;

xUnit: Ücretsiz, açık kaynak bir test aracıdır. ReSharper, CodeRush, TestDriven.NET, Xamarin ile çalışır.

NUnit: .NetFoundation tarafından tüm .NET dilleri için geliştirilmiş unit-testing framework’üdür.

MSTest: Tüm .NET dilleri için Microsoft tarafından geliştirilen test framework’üdür.

Neden Birim Testleri Yazılmalı?

Bir developer’ın özellikle üzerinde yoğunlaşması gereken konu birim testlerdir.

1.Kısa Sürede Uygulanabilir

Fonksiyonel test uygulamayı çalıştırıp sayfaları açıp, butonlara tıklanarak yapılan testtir. Genellikle birim test uygulanmayan yazılım geliştirme yöntemlerinde fonksiyonel test yapılır. Bu yöntem çok maliyetlidir ve uzun zaman alır. Örneğin bir .net core web projesi geliştirdiğimizi düşünelim. Yazılımcı listelenen öğelerden birinin seçilerek silinmesi işini yapmış olsun ve test aşamasına geçsin.

Fonksiyonel test uygulayan ekipte yazılımcı silme işini yapan methodu test edebilmek için sırasıyla;

  • projeyi çalıştırır,
  • login olur,
  • ilgili sayfayı açar,
  • filtreleri seçerek listeleme yapar,
  • delete butonuna basar,

görüldüğü gibi metoda gidebilmek için öncesinde bir çok işlem yapması gerekir. Bu işlemler bazen çok daha uzun olabilir. Birim test yazılmış olsaydı direk metoda silinecek öğenin ID’sini ve kullaniciID sini vererek kolayca test yapabilecekti.

2.Tüm Senaryolarla Test Yapılır

Üstteki örnekten devam edecek olursak listelenen öğelerden bazılarını silmeye bazı kullanıcıların yetkisi olsun. Kodu geliştiren yazılımcı bu senaryoyu bilir ve buna göre kullanıcı ile sisteme login olup silemeyeceği öğeleri seçerek silme işlemini test yapar. Ancak ekibin test biriminin ya da sonradan dahil olan yazılımcının bu senaryodan haberi olmayabilir. Silme işlemini rastgele bir kullanıcı ve rastgele bir öğe ile test eder. Birim test uygulanıyor olsaydı bu senaryolar için ayrı birim test metotları yazılırdı ve bu metotları gören başka yazılımcı veya tester’lar böyle bir kuralın olduğundan haberdar olurlardı.

3.Gerilemeye Karşı Korur

Uygulamada bir değişiklik yapıldığında ortaya çıkan hatalara gerileme kusurları denilir. Mesela birkaç adımdan oluşan bir iş süreci olsun. Ortalardaki bir adımda bir değişiklik yapılırsa bu öncesindeki ve sonrasındaki adımları da etkileyebilir. Birim test yazıldığında her adım için testler hazırlanmış olur. İleride adımlardan birinde değişiklik yapılsa bile tüm testler çalıştırılarak adımların hala çalışır olduğunun kontrolü kolayca yapılabilir.

4.Dökümanlaştırma

Bir metodun ne iş yaptığı ya da farklı girdilerde nasıl davranacağı bilinmeyebilir. Birim testler isimlendirme standartlarına uyularak yazıldığında, test ettikleri metodun verilen girdiyi ve döneceği değeri isminde açıkça belirtir.

5.Daha az Bağımlı Kodlar

Birim test yazılmadığı zaman birbirine daha sıkı bağımlılığı olan kodlar yazılabilir. Birim testlerde her metodun ayrı ayrı test edilmesi gerektiği için bağımlılık kontrolü de yapılmış olur. Birbirine sıkı sıkıya bağımlı kodların birim testlerinin yazılması da zordur.

xUnit ile .Net’te Birim Testleri Nasıl Yazılır?

Birim testleri için kullanabileceğimiz kütüphanelerden biride xUnit kütüphanesiydi. Şimdi hızlıca kodlamaya geçerek basit bir metot ve birim testlerini yazalım.

FactorialService adında bir Class Library (.Net Core) oluşturuyoruz. FactorialService adında bir sınıf ekliyoruz ve içine aldığı int değere göre faktöriyel hesabı yapan CalculateFactorial metodunu yazıyoruz.

public int CalculateFactorial(int value)
{
if (value < 0 || value > 12)
throw new ArgumentOutOfRangeException();
int result = 1;
for (int i = value; i > 0; i--)
{
result *= i;
}
return result;
}

Solution’a sağ tıklayıp Add New Project dedikten sonra çıkan ekranda arama barına xunit yazıyoruz ve c# için olan xUnit projesini seçip FactorialService.Test adını vererek ekliyoruz.

Test projesi içine FactorialServiceTest sınıfını oluşturup CalculateFactorial metodunu için ilk test metodumuzu yazıyoruz. Test metodunun derleyici tarafından anlaşılabilmesi için [Fact] attribute ile işaretliyoruz.

Birim testleri yazarken kullanılan pattern 3 ana öğeden oluşur.

  • Arrange: Gerekli olan nesnelerin oluşturulması ve kullanıma hazırlamak.
  • Act: Testi yapılacak metodun kullanılarak dönüş değerinin alınmasıdır.
  • Assert: Gelen ve beklenen değerlerin karşılaştırılmasıdır.

Testin açık ve anlaşılır olması için bu 3 öğenin ayrı satırlarda yazılması gerekir, best practice açısından önemlidir.

[Fact]
public void CalculateFactorial_Input3_Return6()
{
//Arrange
FactorialService factorialService = new FactorialService();
//Act
var value = factorialService.CalculateFactorial(3);
//Assert
Assert.Equal(value, 6);
}

Best practice açısından bir diğer önemli nokta Test metodunun isminde şu 3 öğe bulunmalıdır.

  • Testi yapılan metot adı.
  • Metoda gönderilen parametre, ya da test senaryosu.
  • Gönderilen parametreye karşılık metottan beklenilen davranış biçimi.

Aşağıdaki kod isimlendirme ve test yazım öğelerinin ayrı yazılması bakımından yanlış bir örnektir.

[Fact]
public void CalculateFactorial_Test()
{
//Arrange
FactorialService factorialService = new FactorialService();
// Act and Assert
Assert.Equal(factorialService.CalculateFactorial(3), 6);
}

Eklenen sınıflar ve test projesi sonrası Solution Explorer görüntüsü şu şekilde olacaktır.

Test edilecek kod ve birim testi yapacak metodumuz hazır olduğuna göre test işlemini başlatabiliriz. Visual Studio da View sekmesi altındaki Text Explorer butonuna basarak testleri çalıştırabileceğimiz pencerenin görünmesini sağlıyoruz. Play simgesi olan Run butonuna basarak testlerimizi çalıştırıyoruz.

Testlerin çalışması sonrası toplam test sayısı, geçen test sayısı ve başarısız test sayısı ayrı ayrı gösterilmektedir.

Bu test metoduna herhangi bir parametre göndermeden tek değer için giriş ve çıkış değerinin testini yaptık. Birden fazla değer için test etmek isteseydik gidiş dönüş listesi yapıp döngüye alarak test mi yapacaktık? Tabi ki hayır.

[Fact] attribute parametre almayan test metotları için kullanılırken [Theory] attribute derleyiciye metoda parametre gönderileceğini bildirir ve parametreler [InlineData] attribute ile gönderilir. Metot üzerine birden fazla [InlineData] yazılarak farklı değerler için test yapılmış olur. Yukardaki test metodumuzu şu şekilde değiştiriyoruz.

[Theory]
[InlineData(0, 1)]
[InlineData(3, 6)]
[InlineData(5, 120)]
[InlineData(12, 479001600)]
public void CalculateFactorial_LessThan13PositiveNumbers_ReturnSuccesfully(int input,int expected)
{
//Arrange
FactorialService factorialService = new FactorialService();
//Act
var value = factorialService.CalculateFactorial(input);
//Assert
Assert.Equal(expected, value);
}

Pozitif ve 13 den küçük değerler için metodumuz başarıyla dönüş yapacağı için isimlendirmesini de CalculateFactorial_LessThan13PositiveNumbers_ReturnSuccesfully olarak değiştiriyoruz.

Testi çalıştırıyoruz ve başarılı sonucunu alıyoruz.

Artık CalculateFactorial_Input3_Return6 test metodunu silebiliriz.

Bir metodun birim testlerini yazarken sadece bir birim test metodu yazmak yerine farklı senaryolar için testler yazmak gerekmektedir.

Metodumuzun başarılı bir şekilde dönüş yapacağı senaryoyu yazdık. Şimdi dönüş yapabileceği max int değerinin dışındaki ve negatif sayılar için olan senaryoyu da yazalım.

[Theory]
[InlineData(-5)]
[InlineData(-1)]
[InlineData(13)]
[InlineData(15)]
public void CalculateFactorial_NegativeAndMoreThan12Numbers_ThrowArgumentOutOfRangeException(int input)
{
//Arrange
FactorialService factorialService = new FactorialService();
//Act
Action actual = () => factorialService.CalculateFactorial(input);
//Assert
Assert.Throws<ArgumentOutOfRangeException>(actual);
}

Testleri çalıştırdığımızda metodumuzun beklenilen exception fırlattığını görebiliriz.

Birim test yazarken diğer bir önemli noktada her bir test metodunun içinde yalnızca bir assert satırının olmasıdır.

Bundan kaçınmak için test metotları;

  • Parametrik yapılmalıdır,
  • Farklı senaryolar farklı metotlarda test edilmelidir.

Birim testleri aynı zamanda yazılım için dokümantasyon özelliği sağladığı için yazım kurallarına uyularak yazılmalı, bir başkası tarafından bakıldığında rahatlıkla anlaşılabilir olmalıdır.

Birim testlerdeki bilginizi daha fazla geliştirmek istiyorsanız;

Örnek birim testlerinin olduğu GitHub reposuna bakabilir, Microsoft dokümanını inceleyebilirsiniz.

Blog örneklerine aşağıdaki linkten ulaşabilirsiniz.

--

--