Nedir bu Unit Test? — Unit Test 101

Semih Elitaş
Ada Yazılım
Published in
5 min readJul 23, 2023
Photo by ThisisEngineering RAEng on Unsplash

Nedir Bu Unit Test?

Unit Test, yazılım geliştirme sürecindeki en temel test türlerinden biridir ve birim test olarak da adlandırılır. Bu test türü, yazılımın en küçük işlevsel birimlerini (fonksiyonlar, metodlar, sınıflar vb.) izole ederek, her bir birimin beklenen davranışı sergileyip sergilemediğini doğrulamak için kullanılır. Birim testler, genellikle geliştiriciler tarafından yazılır ve kodun parçalarını ayrı ayrı test ederek hataları erken aşamalarda tespit etmeye ve düzeltmeye yardımcı olur.

Bu test türü, yazılımın diğer bölümleriyle bağımsız olarak çalışmalıdır ve diğer birimlerden etkilenmemelidir. Ayrıca, kod kalitesini artıran ve yazılımın istikrarını ve güvenilirliğini sağlayan önemli bir uygulamadır.

Neden Unit Test Yazmalıyız?

Unit Test yazmanın birçok önemli nedeni vardır, gelin bu nedenlerden birlikte birkaçına detaylı değinelim:

  1. Hata Tespitini Erken Aşamada Sağlaması:

Unit Testler, kodun yazım aşamasında hataların tespit edilmesine olanak tanır. Hatalar, uygulamanın diğer kısımlarına yayılmadan önce tespit edilir ve düzeltilir.

2. Kodun Güvenilirliğini Sağlaması:

Unit Testler, yazılımın küçük birimlerini izole ederek her birimin doğru çalıştığından emin olmamızı sağlar. Bu sayede, kodun güvenilirliği artar ve yazılımın beklenen şekilde çalışacağından emin oluruz.

3. Kodun Değişikliklerden Etkilenme Riskini Azaltması:

Profesyonel iş hayatında biz yazılımcıların en çok başına gelen şeydir. Mevcut kodu değiştirmek, istemeden başka birimlerde hatalara neden olabilir. Unit Testler, bu tür değişikliklerin mevcut işlevselliği bozmadığından emin olmamıza yardımcı olur.

4. Dokümantasyon Sağlar:

Unit Testler, birimlerin nasıl kullanılması gerektiğini ve beklenen davranışı tanımlayan dokümantasyon görevi görür. Bu, takım arkadaşlarınızın veya sizin ileride kodu kullanırken kolaylıkla anlamasına yardımcı olacaktır.

5. Refactoring’i Kolaylaştırır (^^ ❤):

Refactoring sırasında, Unit Testler sayesinde var olan işlevselliğin bozulmadığından emin olabilir ve refactoring işlemini daha güvenli bir şekilde gerçekleştirebiliriz.

Test Çalışma Döngüsü: AAA

Unit testlerin düzenli ve yapılandırılmış bir şekilde yazılmasını sağlayan temel bir yöntemdir. Bu yaklaşım, unit testlerin daha anlaşılır, bakımı kolay ve tutarlı olmasına yardımcı olur. Test Çalışma Döngüsü genellikle “Arrange, Act, Assert” olarak yani AAA biçiminde ifade edilir.

Arrange (Düzenle):

  • Testin gereksinimlerini oluşturmak ve ayarlamak için gerekli hazırlık adımlarını gerçekleştiririz.
  • Test edilecek birimin durumunu ayarlamak için gerekli verileri hazırlarız.
  • Gerekli bağımlılıkları oluşturabilir veya fake nesneler (mock objects) kullanarak bağımlılıkları taklit edebiliriz.
  • Bu adımda, testin başlangıç durumunu oluştururuz.

Act (İşlem Yap):

  • Önceden düzenlenen test durumunda, test edilecek birimi (fonksiyonu, metodları vb.) çalıştırırız.
  • Birim, üzerine çalıştırılacak işlemler gerçekleştirir ve sonuçları üretir.
  • Testi gerçekleştirmek için, testin odaklandığı belirli işlevselliği tetikleriz.

Assert (Doğrula):

  • Act adımında elde edilen sonuçları, beklenen sonuçlarla karşılaştırırız.
  • Birimin, beklenen çıktıları veya durumları üretip üretmediğini doğrulamak için assert ifadeleri kullanırız.
  • Eğer beklenen sonuçlarla elde edilen sonuçlar eşleşiyorsa, test başarılı olarak kabul edilir ve test geçerlidir. Ancak sonuçlar eşleşmezse, test başarısız olur ve bir hata olduğunu gösterir.

Bu AAA yapısı unit testlerin diğer yazılımcılar tarafından anlaşılabilir ve sürdürülebilir olmasına yardımcı olur. Bu nedenle testlerin yazımında yaygın olarak kullanılan bir yöntem, bir standarttır.

Haydi Test Yazalım

Ben aktif olarak sigortacılık domaininde çalıştığım ve bu domain üzerine ürünler geliştirdiğimiz için örneğide sigortacılık üzerinden vereceğim :)

Yaş ve daha önceki kaza geçmişine göre bir sigorta poliçesinin temel primini hesaplayan bir sınıfımızın olduğunu varsayalım:

public class SigortaPrimHesaplayici
{
public decimal Hesapla(int yas, bool kazaGecmisiVar)
{
decimal temelPrim = 1000;

if (yas < 25)
{
temelPrim += 500;
}

if (kazaGecmisiVar)
{
temelPrim *= 1.2m;
}

return temelPrim;
}
}

SigortaHesaplayici sınıfına ait Hesapla metodu, eğer kişi 25 yaş altındaysa veya daha önce kaza geçmişi varsa, prim arttırmakta, bu koşullara uymuyorsa temel primi dönmektedir.

Gelin bu metoda ait testleri yazalım:

using NUnit.Framework;

[TestFixture]
public class SigortaPrimHesaplayiciTests
{
[Test]
public void Hesapla_25YasAltindaKazaGecmisiVar_ReturnsArtmisPrim()
{
// Arrange (Düzenle)
int yas = 23;
bool kazaGecmisiVar = true;
SigortaPrimHesaplayici hesaplayici = new SigortaPrimHesaplayici();

// Act (İşlem Yap)
decimal prim = hesaplayici.Hesapla(yas, kazaGecmisiVar);

// Assert (Doğrula)
decimal beklenenPrim = 1000 + 500; // Temel prim + 25 yaş altı artışı
beklenenPrim *= 1.2m; // Kazalara uygulanacak artış
Assert.AreEqual(beklenenPrim, prim);
}

[Test]
public void Hesapla_25YasVeUstundeKazaGecmisiVar_ReturnsTemelPrim()
{
// Arrange (Düzenle)
int yas = 30;
bool kazaGecmisiVar = true;
SigortaPrimHesaplayici hesaplayici = new SigortaPrimHesaplayici();

// Act (İşlem Yap)
decimal prim = hesaplayici.Hesapla(yas, kazaGecmisiVar);

// Assert (Doğrula)
decimal beklenenPrim = 1000; // Temel prim
beklenenPrim *= 1.2m; // Kazalara uygulanacak artış
Assert.AreEqual(beklenenPrim, prim);
}

[Test]
public void Hesapla_25YasAltindaKazaGecmisiYok_ReturnsArtmisPrim()
{
// Arrange (Düzenle)
int yas = 22;
bool kazaGecmisiVar = false;
SigortaPrimHesaplayici hesaplayici = new SigortaPrimHesaplayici();

// Act (İşlem Yap)
decimal prim = hesaplayici.Hesapla(yas, kazaGecmisiVar);

// Assert (Doğrula)
decimal beklenenPrim = 1000 + 500; // Temel prim + 25 yaş altı artışı
Assert.AreEqual(beklenenPrim, prim);
}

[Test]
public void Hesapla_25YasVeUstundeKazaGecmisiYok_ReturnsTemelPrim()
{
// Arrange (Düzenle)
int yas = 30;
bool kazaGecmisiVar = false;
SigortaPrimHesaplayici hesaplayici = new SigortaPrimHesaplayici();

// Act (İşlem Yap)
decimal prim = hesaplayici.Hesapla(yas, kazaGecmisiVar);

// Assert (Doğrula)
decimal beklenenPrim = 1000; // Temel prim
Assert.AreEqual(beklenenPrim, prim);
}
}

Evet gördüğünüz gibi, metodumuzda oluşabilecek tüm senaryolar için ayrı ayrı testlerimizi yazdık.

Test sınıflarının isimlendirilmesi önemlidir. İlgili test sınıfının isimlendirmesi, hangi sınıfın testlerini içerdiğini açık şekilde ortaya koymalıdır. Bu bağlamda SigortaPrimHesaplayiciTests adlı test sınıfının SigortaPrimHesaplayici sınıfının testlerini içerdiği çok barizdir.

Testlerle ilgili değinmemiz gereken bir kaç ufak konu daha var:

Magic Numbers (Sihirli Sayılar) Kullanımı: Kodun okunabilirliğini artırmak için “sihirli sayılar” denilen doğrudan yazılmış sabit sayılar yerine, bu sayıları anlatıcı değişkenler kullanmak daha iyi bir uygulamadır. Örneğin, 25 yaş altı prim artışı için bir const veya readonly değişken tanımlanabilir ve kullanılabilir.

Mocking: Eğer SigortaPrimHesaplayici sınıfı dışa bağımlılıklar içerse ve bunlar, testin sonucunu etkileyebilecek işlemleri içerseydi, bu dışa bağımlılıkları mocklamak (mock objelerle değiştirilmesi) gerekecekti.

Gelin Mocking nedir biraz daha detaylı, örneklerleriyle inceleyelim.

Mocking

Mocking, bir sınıfın dışa bağımlılıklarını, gerçek uygulamalar yerine testlerde kontrol edilebilir sahte (mock) nesnelerle değiştirme işlemidir. Bu sayede testler, gerçek dışa bağımlılıkların sonuçlarından bağımsız olarak çalışabilir ve daha öngörülebilir hale gelir.

Yukarıdaki örnekten devam edelim.SigortaPrimHesaplayici sınıfının bir dışa bağımlılığı olduğunu ve bu bağımlılığın SigortaPrimHesapServisi adında bir interface ile temsil edildiğini düşünelim. SigortaPrimHesapServisi arayüzü, sigorta primi hesaplamakla ilgili işlemleri tanımlayan metotları içeren bir interface olsun.

public interface ISigortaPrimHesapServisi
{
decimal Hesapla(int yas, bool kazaGecmisiVar);
}

SigortaPrimHesaplayici sınıfı bu interface’i kullanarak prim hesabını yapsın:

public class SigortaPrimHesaplayici
{
private readonly ISigortaPrimHesapServisi _sigortaPrimHesapServisi;

public SigortaPrimHesaplayici(ISigortaPrimHesapServisi sigortaPrimHesapServisi)
{
_sigortaPrimHesapServisi = sigortaPrimHesapServisi;
}

public decimal Hesapla(int yas, bool kazaGecmisiVar)
{
// Burada _sigortaPrimHesapServisi nesnesini kullanarak prim hesaplaması yapılır.
// ...
}
}

Şimdi, SigortaPrimHesaplayici sınıfını test etmek için mocking kullanalım. Mocking için bir mock framework kullanabiliriz. Örneğin, Moq framework'ünü kullanarak aşağıdaki gibi bir test senaryosu oluşturalım:

using Moq;

[Test]
public void Hesapla_25YasAltindaKazaGecmisiVar_SigortaPrimiDogruHesaplanir()
{
// Arrange (Düzenle)
int yas = 23;
bool kazaGecmisiVar = true;
decimal beklenenPrim = 2000; // Burada beklenen değeri dilediğiniz gibi ayarlayabilirsiniz.

// Mocking (Mocklama işlemi)
var mockSigortaPrimHesapServisi = new Mock<ISigortaPrimHesapServisi>();
mockSigortaPrimHesapServisi.Setup(x => x.Hesapla(yas, kazaGecmisiVar)).Returns(beklenenPrim);

// Burada constructura gerçek bir obje yaratıp göndermek yerine yerine mock obje gönderiyoruz.
SigortaPrimHesaplayici hesaplayici = new SigortaPrimHesaplayici(mockSigortaPrimHesapServisi.Object);

// Act (İşlem Yap)
decimal prim = hesaplayici.Hesapla(yas, kazaGecmisiVar);

// Assert (Doğrula)
Assert.AreEqual(beklenenPrim, prim, "Sigorta primi doğru hesaplanmalıdır.");
}

Gördüğünüz gibi yukarıdaki test senaryosunda, ISigortaHesapServisi arayüzünden türetilen bir mock nesnesi oluşturduk. Ardından Setup metodunu kullanarak, HesaplaPrim metodu için verdiğimiz girdilere göre beklenen çıktıyı belirledik. Bu sayede SigortaPrimHesaplayici sınıfı SigortaPrimHesapServisi arayüzünü kullandığında, mock nesnenin döndürdüğümüz değeri almasını sağladık. Böylece SigortaPrimHesaplayici sınıfı, test senaryosu için önceden belirlenen çıktıyı alarak doğru çalıştığından emin olunur.

Mocking, özellikle dışa bağımlılıkların olduğu durumlarda test edilebilirlik açısından oldukça faydalı ve kesinlikle kullanmamız gereken bir tekniktir. Bu teknikle birlikte yazdığımız testler, dışa bağımlılıklardan bağımsız olarak çalışır ve test sonuçları daha güvenilir olur.

Unutmayın,

Unit testler, gerçek bir işlem yapmadan sadece ilgili metodu izole ederek ve diğer bağımlılıkları sahte (mock) nesnelerle yer değiştirerek çalıştırır. Bu şekilde, testin sonucu, sadece test edilen kodun sonucunu yansıtır ve harici bağımlılıklardan etkilenmez. Böylece, algoritma ve mantıksal işlemlerin doğruluğunu test etmek ve kodun beklenen şekilde çalıştığından emin olmak için kullanılırlar.

Yazdığınız her testin geçtiği, az bug’lu kodlamalar :)

--

--