Yakın zamanda Devnot‘a yazdığım TDD (Test-Driven Development) Yapmak Gerçekten Zor Mu? yazısının devamı niteliğinde biraz daha kod örnekleri ile bu sormuş olduğum sorunun cevabını vermeye çalışacağım. Basit bir senaryo üzerinden, yine basit bir kod yazarak; hani kod yazmadan, nasıl testi yazılır problemi var ya, bunu anlamaya bir yandan da anlatmaya çalışacağım…
Belli bir miktarda paranın aylık faiz miktarını hesaplamamız gerekiyor. Ana para 10.000’den büyük olursa faiz oranı maksimum % 3, 10.000’den küçük olursa da maksimum %1.5 olacak şekilde hesaplanmalıdır. Sadece aylık zaman dilimine göre hesaplanmalı ve bu zaman 1 yılı geçmemeli.
Ekonomiye can katacak bu çok basit müşteri talebi ya da analiz çakması ihtiyacımız; işte artık nasıl yorumlarsanız, uygulamamızın ilk hali aslında. Böyle uzun uzadıya tasarım yapabileceğimiz bir örnek olmasa da kabaca nasıl yaparız bunu düşünelim önce.
Öncelike bir tane FinancialManager diye bir sınıfımız olsun mesela. Bu ana paramızı emanet ettiğimiz sınıf…SetCapital metotu ile ana paramızı belirtelim. Bu sınıf üzerinden faiz oranını tanımlayabilelim, SetInterestRate mesela. İstersek değiştirebilelim. Aynı şekilde SetPeriod ile aylık zamanı da belirtip, değiştirelim. Bütün bu tanımlamalara, FinancialManager sınıfının özelliklerinden ulaşabilelim. Ana para(Capital), faiz oranı(InterestRate), aylık zaman(Period) falan…Bütün bu özellikler belirtildiği zamanda CalculateInterest ile faiz miktarını hesaplayabilelim.
Sınıf, metot, özellik falan…Biraz daha koda yaklaştık. Teknik olarak biraz daha anlaşılır oldu sanki. Bu aşamadan sonra kodu yazmak biraz daha kolay, hatta direkt yazalım…Test etmeye ne gerek var?
Dııttt yanlış….
Teknik olarak, ne yapacağımızı oluştursak da, kodu yazarken hata yapma oranımız çok yüksek. Çünkü parametrelerimiz değişken, dikkat etmemiz gereken ve sağlamamız gereken belli şartlar var falan filan… Bu hatalar kod gelişmeye başladıkça karşımıza zaten çıkacak. Baştan farkında olup, kod geliştikçe ortadan kalkacak şekilde düşünürsek ama, daha sağlıklı bir kodumuz olacaktır. O yüzden hadi test yazalım…
Bu örneği Visual Studio’nun test proje şablonunu kullanarak yapacağım. Farklı proje şablonları ya da test framework’leri de kullanabilirsiniz tabi. Birim testi için bir sürü framework var. Farklılıkları olsa da hepsinin ortak noktası daha çok. O yüzden araçtan çok, konsepte odaklanmak bu aşamada daha doğru olacaktır.
Öncelikle bir tane Unit Test, bir tane de Class Library şablonundan iki proje oluşturalım. FinanceApp olsun bir tanesi, bunun test projesi de FinanceApp.Test olsun. FinanceApp’in içerisinde de hiç bir sınıf falan olmasın…Silelim, şablonla gelen Class1’i…Gerek yok.
Bizim şimdilik işimiz FinanceApp.Test projesi… O yüzden UnitTest.cs dosyasını açalım. Fark etmiş olduğunuz gibi içinde boş bir tane test metodu olan, test sınıfımız bu dosyanın içinde.
namespace FinanceApp.Test { [TestClass] public class UnitTest { [TestMethod] public void TestMethod1() { } } }
İlk testimizi de, bu TestMethod1’in ismini Test_SetCapital şeklinde değiştirerek içine yazalım…
namespace FinanceApp.Test { [TestClass] public class UnitTest { [TestMethod] public void Test_SetCapital() { FinancialManager manager = new FinancialManager(); manager.SetCapital(100.0); double expected = 100.0; double actual = manager.Capital; Assert.AreEqual(expected, actual); } } }
Yukarıda özetlediğim gibi, basitçe test kodunu yazmaya çalıştım. FinancialManager diye bir sınıf olduğunu farz edip, bundan bir nesne yarattım. SetCapital() metodu ile de ana paramı tanımladım. Daha sonra expected ve actual değerlerini, karşılaştırıyorum. Bu iki değer bir biri ile aynı olursa testimiz başarılı sonuçlanmış demektir.
Şimdi bu testi yazdık güzel ama bu çalışmayacak, hatta derlenmeyecektir bile. Böyle dan dun yazarsak tabii olmaz 🙂 Çünkü FinancialManager diye bir sınıf bile yok…O zaman şimdi bu sınıfımız kodunu yazalım.
Aslında bu ilk aşamayı, Visual Studio bizim için yapacak. FinancialManager ifadesinin üstüne sağ tıklayıp, “Generate > New Type“… diyerek, bu sınıfın yaratılmasını sağlıyoruz.
Buradan sadece bu sınıfın oluşturalacağı projeyi değiştirmemiz yeterli olacaktır. Dolayısıyla FinanceApp’i seçiyorum.
Benzer şeyleri SetCapital() metoduna ve Capital özelliğine de yapıp ilgili kodların yaratılmasını sağlıyoruz. SetCapital() için sağ tıklayıp “Generate > Method Stub”, Capital için de sağ tıklayıp “Generate > Property” dediğimizde, FinanceApp projemiz içerisinde artık FinancialManager sınıfı oluşturmuş oluyoruz.
namespace FinanceApp { public class FinancialManager { public void SetCapital(double p) { throw new NotImplementedException(); } public double Capital { get; set; } } }
Bu aşamada daha metodumuzun içeriği yok doğal olarak. Test projemize geri dönüp, testi çalıştırdığımız zaman bakalım ne olacak?
Tabi ki testimiz hata alacak. Çünkü oluşturduğumuz kod henüz bir şey yapmıyor. Testimizin gerçekçi olması için kodumuzu güncelliyoruz. Bir başka deyişle, re-factoring yapıyoruz.
namespace FinanceApp { public class FinancialManager { public void SetCapital(double p) { this.Capital = p; } public double Capital { get; private set; } } }
Testimizi tekrar çalıştırırsak, bakalım bu sefer ne olacak?
Ve yeşil… Testimiz başarılı bir şekilde çalıştı. Şimdi yeni bir test yazarak kodumuzu geliştirmeye devam edelim.
[TestMethod] public void Test_SetCapitalAsNegative() { FinancialManager manager = new FinancialManager(); manager.SetCapital(-20); double expected = 0; double actual = manager.Capital; Assert.AreEqual(expected, actual); }
Burada dikkat ederseniz artık yeni bir testimiz ve yeni bir durumumuz var. Ana parayı negatif(-) bir değer verdiğimiz zaman, beklentimiz 0 değerini almak.
Tekrar testlerimizi çalıştıralım, bakalım bu sefer ne olacak.
Bu sefer ilk testimiz başarılı, yeni yazdığımız test başarısız… O zaman hemen bir re-factoring daha yapıyoruz. Burada önemli olan bir noktanın altını çizmek isterim. Normalde negatif bir değer aldığı zaman SetCapital()’in ne yapacağını kestirmek zor olabilirdi. Daha doğrusu bilemeyebilirdik. Testlerin, analiz ve tasarımla eşlenik olması bu noktada bu tarz atlayabileceğimiz durumları ortadan kaldıracaktır. Bu tarz farkındalıkları oluşturmak için, testleri yazan ve kodları yazan kişilerin farklı kişiler olması daha sağlıklı olabilir belki. Ama ne yazık ki böyle bir kaynağa sahip olmak ve yönetmek pek mümkün değil gerçekte sanırım…
Çok fazla uzatmadan bir test daha yazalım.
[TestMethod] public void Test_SetInterestRate() { FinancialManager manager = new FinancialManager(); manager.SetInterestRate(4); double expected = 0; double actual = manager.InterestRate; Assert.AreEqual(expected, actual); }
Bu testimizde de, ana paramızı belirtmeden, faiz oranını belirtiyoruz. Beklentimiz, oranın 0 olması yönünde. Buna göre kodumuzu hemen re-factor ediyor ve aşağıdaki gibi kodumuzu düzenliyoruz.
namespace FinanceApp { public class FinancialManager { public double Capital { get; private set; } public double InterestRate { get; private set; } public void SetCapital(double p) { if (p <= 0) this.Capital = 0; else this.Capital = p; } public void SetInterestRate(int p) { if (Capital <= 0 || p <= 0) InterestRate = 0; else this.InterestRate = p; } } }
Son güncellemelerden sonra, testlerimizi çalıştırdığımız zaman, yem yeşil bir sonuç ekranı ile karşılaşıyor olacağız.
Diğer teknik gereksinimleri de bu şekilde önce testleri yazarak oldukça kolay(!) bir şekilde geliştirebiliriz. Hatta bu örnek üzerinden devam edip, diğer metodları yapabilirseniz başlamak için güzel bir fırsat olabilir. Ama açıkcası çok kolay olmayacak aslında, ama bu şekilde bir alışkanlık kazandığınız zaman, kolaylaşacaktır. Bütün gereksinimlerinizi bu şekilde test yazarak yazmanızda mümkün olmayacaktır, ama ne kadar buna yönelirseniz, gereksinimlerinizi test edebilecek şekilde düşünmeye başlayacaksınızdır. Bu da uzun vade de çok getirisi olan bir şey olacak.
Vermiş olduğum örnek oldukça basit ve sade olmuş olabilir, ama önemli olan daha ortada kod yokken, testlerini yazarak kodu nasıl oluşturabildiğimizi anlamak. Bu şekilde tekrardan düşünürseniz belki daha anlaşılır olur.
TDD ile ilgili olarak, arada bu tarz yazılar ile öğrendiklerimi paylaşmaya devam edeceğim. TDD’yi geliştirme yöntemi olarak tercih ediyorsanız zaten, ne güzel, etmiyorsanız da başlamak için ya da en azından bir şans vermek için çok geç değil…Bu arada düşüncelerinizi ve yorumlarınızı paylaşabilirsinz…Hatta paylaşın, bir şey demiyorsunuz sonra üzülüyorum 🙂 Bir sonraki yazıya kadar, bol testli kodlar…