|
NUnit ile Birim Test |
|
Gönderiliyor lütfen bekleyin... |
|
|
NUnit yoğun olarak kullanılan, açık kaynak kodlu, DotNet Framework dilleriyle birim test geliştirme frameworküdür. GUI ve Konsol olmak üzere iki test çalıştırıcısı ile beraber kurulur. Konsol çalıştırıcısı, build otomasyonlarında sıklıkla kullanılır. Örneklerimizde biz GUI çalıştırıcısını kullanacağız.
Birim (Unit) Test
Uygulamalarımızı geliştirirken, kodun küçük bir kısmını test etmek amaçlı yazdığımız koda birim test denir. Yazılan birim testler build otomasyonunun bir parçası haline getirildiğinde birim testin asıl yararı görülecektir.
Build otomasyonu ile otomatize edilen birim testler yazdığımız kod her değiştiğinde tekrar test edilecektir.
Şimdi birim test geliştirirken kullanacağımız sınıflara ve attributelara göz atalım :
Assert Sınıfı
NUnit ile test sınıflarımızı yazarken sıklıkla kullanacağımız Assert sınıfı bize static iddia metodu sağlıyor.
Yani assert metodu ile iddia edilen durum doğru ise test başarıldı, aksi takdirde test başarısız olmuş oluyor.
Çok fazla derinine girmek istemiyorum çünkü örneklerimizde sıklıkla kullanacağız ama küçük bir örnek vermek gerekirse :
Assert.IsNotNull(objToTest.GetFirstItem());
ifadesi ile objToTest nesnesinin GetFirstItem metodundan null obje gelmesi halinde testimiz başarısız olacaktır.
NUnit Mimarisi
Kent Beck Smalltalk için SUniti yayınladıktan sonra Erich Gamma ile Java için JUniti yazdı. Ardından CppUnit, NUnit ve PyUnit geldi. Tüm bu frameworkler aynı model üzerine geliştirildiler. Bu yüzden bu frameworkler xUnit Ailesi olarak anılırlar.
Şimdi bahsettiğimiz modeli NUnit üzerinde incelemeye çalışalım.
A. Test ve TestFixture
Test Attibuteı bir birim testi ifade eder. TestFixture ise birim testler kolleksiyonudur.
Aşağıdaki sınıfa bir göz atalım. Bu sınıf, _dvds ArrayListine eleman ekleyen ve bu ArrayList içerisindeki elemanların sayısını döndüren iki metoda sahip.
public class DVDLibrary
{
public DVDLibrary()
{
_dvds = new ArrayList();
}
#region Private Members
private ArrayList _dvds;
#endregion
#region Public Methods
public void AddDVD(DVD dvd)
{
_dvds.Add(dvd);
}
public int GetDVDCount()
{
return this._dvds.Count;
}
#endregion
}
|
Bu sınıf için, elemanları ekleyip eklemediğini test eden bir birim test yazalım :
[TestFixture]
public class Demo_1_Test
{
public Demo_1_Test(){}
[Test]
public void TestGetDVDCount()
{
DVDLibrary library = new DVDLibrary();
// Boş kütüphane Testi :
Assert.AreEqual(0, library.GetDVDCount());
// 2 DVD eklenmiş kütüphane testi :
DVD dvd_1 = new DVD("Layer Cake");
library.AddDVD(dvd_1);
DVD dvd_2 = new DVD("The Sixth Sense");
library.AddDVD(dvd_2);
Assert.AreEqual(2, library.GetDVDCount());
}
}
|
Yukarıdaki örneğimizde gördüğünüz üzere Test birim test yapan metodlara gelen bir attribute, TestFixture ise birim testleri içeren bir sınıf attributeı.
Resim 1. NUnit GUIsi
Yukarıda gördüğünüz NUnit GUIinin sağ yarısı test sonuçlarımızın gösterildiği paneldir. Yeşil satır ise yazdığımız testin başarıyla geçildiğini sembolize ediyor. NUnit GUIsini kullanırken sıklıkla kırmızı renge de rastlayacaksınız. Kırmızı, test ettiğiniz sınıfın birim testinizi geçemediği anlamına geliyor.
Sarı renk testinizin atlandığını (ignore) yani yapılmadığını gösteriyor.
B. Suite ve Category
Suite Attributeı TestSuite döndüren static özelliklere verilebilir. TestSuite TestFixture sınıfları kolleksiyonudur ve isteğimize göre TestFixturelerimizi gruplamamızı sağlar.
[Suite]
public static TestSuite GeometryTestSuite
{
get
{
TestSuite suite = new TestSuite("Geometric Class Tests");
suite.Add(new Demo_2_TestTriangle());
suite.Add(new Demo_2_TestSquare());
return suite;
}
}
|
Category attributeı ise hem birim testleri (Test) hem de birim test kolleksiyonlarını (TestFixture) bölümlere ayırmaya yarar.
[Test]
[Category("Areas")]
public void TestTriangleArea()
{
// Test Triangle Areas :
int area_1 = triangle.GetArea(5,4);
Assert.AreEqual(10, area_1);
int area_2 = triangle.GetArea(8,2);
Assert.AreEqual(8, area_2);
}
|
Resim 2. Kategoriye göre başarılı olamamış bir test.
C. Hata yönetimi
Bazen yazdığımız sınıfların belli durumlarda hata fırlatmasını isteriz. Bu durumların birim testini yaparken NUnit frameworkünün ExpectedException attributeını kullanırız.
[Test, ExpectedException(typeof(ArgumentNullException))]
public void TestAddNullDVD()
{
DVDLibrary library = new DVDLibrary();
library.AddDVD(null);
}
|
D. SetUp ve TearDown
Bir method SetUp attributeı ile tanımlandığı zaman, içerisinde bulunduğu test kolleksiyonu içerisindeki her bir testin öncesinde çağrılır. SetUp attributeını TestFixtureSetUp ile değiştirirsek, bu durumda test kolleksiyonu test edilmeye başlandığında bir defaya mahsus olmak üzere bu metod çağrılır.
Peki mu metodun amacı nedir ? Bu metod test kolleksiyonu içerisindeki testlerin kullanacağı değişkenlerin tanımlanmasını, teste hazırlanmasını sağlar. Örneğin Stream nesneleri yada veritabanı bağlantı nesneleri tanımlamaları için bu metodlar kullanılabilir.
TearDown attributeı ise test kolleksiyonu içerisindeki herbir test çalıştıktan sonra çağrılacak olan metoddur. TestFixtureTearDown attributeı ile tanımlanan metod ise bir test kolleksiyonu içerisindeki tüm testler çalıştırıldıktan sonra çağrılacaktır.
Bu metodların amacı ise testler sonrası nesnelerin sonlandırılması, kaynakları bırakmalarını sağlamaktır. Örneğin SetUp metodu ile açtığımız bir veritananı bağlantı nesnesini TearDown metodu ile kapatırız.
E. Ignore
Test sınıflarımızı geliştirirken bazı sebeplerden dolayı yazdığımız testler yarım kalabilir, daha sonra yazılmak üzere bırakılabilir. Bu durumlarda test metodunu Ignore attributeu ile tanımlayıp testler çalıştırılırken yok sayılmasını sağlayabiliriz.
[Ignore("Henüz test edilecek sınıf hazır değil.")]
public void TestMergeDVDLibraries(DVDLibrary lib_1, DVDLibrary lib_2)
{
// Daha tamamlanmamış bir test
}
|
Sahte (MockObject) nesneler ve DotNetMock kütüphanesi
Testlerimizi yaparken herzaman gerçek ortam (production ortamı) gibi test yapamayabiliriz, yada yapmak istediğimiz testler için satırlarca gerekli ortamı hazırlama kodu yazmak gerekebilir. Yada en basitinden bazen yazdığımız algoritmaların istenen şekilde çalıştığını takip edebilmek için bize ajanlık yapacak nesneler isteriz.
İşte bu durumlarda gerçeğinin arayüzünü taklit eden sahte nesneler yazarız. Örneğimizde yine açık kaynak kodlu DotNetMocku kullanacağız.
Testini yapacağımız sınıf, başka bir sisteme yine kendi yazdığımız IConnection arayüzünü uygulayan eden bir sınıfı kullanarak mesaj gönderiyor :
public interface IConnection
{
void Open();
void Close();
void SendMessage(string message);
}
public class Client
{
public Client(IConnection conn)
{
this._conn = conn;
}
private IConnection _conn;
public void SendMessage(string message)
{
_conn.Open();
_conn.SendMessage(message);
_conn.Close();
}
}
|
Şimdi Sahte (MockObject) nesnemizi yazalım. Sahte nesnemiz IConnection arayüzünü uygulayıp, DotNetMock kütüphanesinin MockObject sınıfından türeyecek :
public class MockConnection : MockObject, IConnection
{
public MockConnection()
{
_expectations = new ExpectationArrayList("MockConnection");
}
private ExpectationArrayList _expectations;
public void Open()
{
_expectations.AddActual("Bağlantı açıldı.");
}
public void Close()
{
_expectations.AddActual("Bağlantı kapatıldı.");
}
public void SendMessage(string message)
{
_expectations.AddActual(message + " - mesajı gönderildi.");
}
// Umulan mesajları eklemek için Aşağıdaki metodu ekliyoruz :
public void AddExpectedMessage(string expectedMessage)
{
_expectations.AddExpected(expectedMessage);
}
}
|
Sahte nesnemiz ExpectationArrayList kolleksiyonuna ICollection arayüzünün methodlarında gerçekte olan durumu kaydediyor. Sınıfa eklediğimiz AddExpectedMessage metodu ile de testimizde gelmesi gereken mesajları atabilmemize olanak sağlıyoruz.
Şimdi testimize geçelim :
[Test]
public void TestWithMockObject()
{
MockConnection conn = new MockConnection();
string mesaj = "mock deneme";
// Beklediğimiz mesajları beklediğimiz sırada Mock nesnemize ekliyoruz :
conn.AddExpectedMessage("Bağlantı açıldı.");
conn.AddExpectedMessage(mesaj + " - mesajı gönderildi.");
conn.AddExpectedMessage("Bağlantı kapatıldı.");
Client client = new Client(conn);
// Testimizi yapıyoruz :
client.SendMessage(mesaj);
// Mesajları doğruluyoruz :
conn.Verify();
}
|
MockObject sınıfı Verify metodu ile beklenen (expected) değerlerin, o anki (actual) değerlerle paralel olup olmadığını test ediyor.
NUnit NAnt Entegrasyonu
.NET araçları ve Frameworklari serimin ilk makalesinde incelediğim NAnt build otomasyon aracı ile unit testleri build otomasyonumuza eklemek için <nunit2> görevini (task) kullanabiliriz.
<?xml version="1.0"?>
<project name="NUnit Test" default="test">
<target name="test">
<nunit2>
<!-- <formatter type="Plain" /> -->
<formatter type="Xml" usefile="true" extension=".xml"
outputdir="E:\Works\C#\NUnitDemo\UnitTests\bin\Debug\" />
<test assemblyname="E:\Works\C#\NUnitDemo\UnitTests\bin\Debug\UnitTests.dll"/>
</nunit2>
</target>
</project>
|
Burada <formatter> tagi ile Unit testlerimizin çıktı formatını belirliyoruz. Plain ile çıktıyı konsol ekranına basarken, Xml ile çıktıyı Xml formatında alıp, Xsl ile görsel raporlar oluşturabiliriz.
Sonuç
Projeler için birim testleri yazmak ilk bakışta zaman kaybı gibi görünsede, projenin ilerleyen süreçlerinde çok önemli bir hal alır. Önceden yazılmış bir kod içerisinde yapılacak değişiklik, iyi test edilmezse, projeyi tabandan sarsabilir.
Projenin değişiklik yapıldıktan sonra yazılı testlerle tekrar test edilmesi, zaman ve para olarak geri dönecektir.
Kaynaklar ve Linkler
- NUnit
- DotNetMock
- NCover
- Andrew Hunt, David Thomas - Pragmatic Unit Testing In C# With NUnit
- Paul Hamill - Unit Test Frameworks
- Brian Nantz - Open Source .NET Development
Makalede anlatılan örnekleri indirmek için tıklayın.
Makale:
NUnit ile Birim Test C#, Visual C# ve .NET Mustafa Erhan Ersoy
|
|
|
-
-
Eklenen Son 10
-
Bu Konuda Geçmiş 10
Bu Konuda Yazılmış Yazılmış 10 Makale Yükleniyor
Son Eklenen 10 Makale Yükleniyor
Bu Konuda Yazılmış Geçmiş Makaleler Yükleniyor
|
|