TDD Cycle
TDD, önce test case'in yazılmasını sonrasında kodun yazılmasını söyler.
TDD döngüsel bir süreçtir ve RED, GREEN, REAFTOR olmak üzere üç aşamayı içerir. RED aşamasında test case yazılır ve çalıştırılır. Test başarısız olur. GREEN aşamasında test case'in geçmesi için ilk kod yazılır. Kodun clean olmasına ihtiyaç yoktur. Asıl amaç test case'in geçmesi için en basit kodu yazmaktır. Test case tekrar çalıştırılır. Test başarılı olduysa REFACTOR aşamasına geçilir. Bu aşamada ise kod refactor edilebiliyorsa edilir. Test case tekrar çalıştırılır. Eğer test case başarısız olursa kod tekrar düzenlenir. Bu döngü kod istenen hale gelene kadar devam ettirilir.
E-ticaret sistemindeki sepet örneğini düşünelim ve ürünün sepete eklendiği senaryoyu canlandıralım. İlk olarak test case'imizi yazıyoruz. Bu RED aşaması. Birinci üründen bir adet, ikinci üründen iki adet olmak üzere sepete iki adet farklı ürün ekliyoruz. Sepet toplam tutarının 500 olmasını, sepetteki ürünlerin sayısının ise 2 olmasını bekliyoruz.
Test case'imizi çalıştırıyoruz ve başarısız olduğunu görüyoruz. Çünkü Cart sınıfına AddItem metotunu ekledik ama içerisine herhangi bir kod yazmadık. Bu aşamada amacımız kod yazmak değil.
Şimdi GREEN aşamasına geçiyoruz. Bu aşamada AddItem metodunu test case geçecek şekilde düzenliyoruz. Burada amacımız mükemmel bir kod yazmak değil. Test case'in geçmesi için gerekli olan en hızlı, en basit kodu yazıyoruz.
Testimiz geçti. REFACTOR aşamasına geldik. Bu aşamada kodumuzu temizliyoruz ve düzenliyoruz. Örnekteki gibi TotalPrice'ı computed property'ye dönüştürüyoruz. Items'ı ise immutable'a çeviriyoruz. En son olarak AddItem metotunda null check yapıyoruz. Test case'i çalıştırıyoruz ve geçtiğini görüyoruz.
Dikkat edersek refactor aşamasında bir test case daha ortaya çıktı. AddItem metotuna null değer geçirildiğinde hata oluşmasını bekliyoruz. O yüzden ikinci bir test case yazıyoruz. Bu test case'de AddItem metotuna null değer geçirip hata oluşmasını bekliyoruz.
AAA Pattern
Her test sırayla yürütülen üç farklı aşamaya sahip olacak şekilde tasarlanır. Bu aşamalar ARRANGE, ACT ve ASSERT.
Arrange aşamasında test için gerekli ön hazırlıklar yapılır. Kurulum için gerekli olan bağımlıklar hazırlanır. Örneğimizde bir tane sepet nesnesi hazırlıyoruz. Sepete bir adet ürün ekliyoruz.
Act aşamasında test gerçekleştirilir. Örneğimizde IncreaseItemQuantity metotunu çalıştırıyoruz.
Assert aşamasında test sonucu doğrulanır. Örneğimizde sepetin toplam tutarını ve ürünün sayısını kontrol ediyoruz.
Four-Phase Pattern
Her test sırayla yürütülen dört farklı aşamaya sahip olacak şekilde tasarlanır. Bu aşamalar SETUP, EXERCISE, VERIFY ve TEARDOWN.
Setup her bir test case çalıştırılmadan önce çağrılır. Arrange aşamasındaki gibi gerekli ön hazırlıklar bu aşamada yapılır. nUnit kullanıyorsanız hazırlıklarınızı SetUp attribute'u ile işaretlediğiniz ayrı bir metotta yapabilirsiniz. xUnit kullanıyorsanız constructor metotunda yapabilirsiniz.
Teardown her bir test case sonlandırılmadan önce çağrılır. Setup aşamasındaki yapılandırmalar sıfırlanır. nUnit kullanıyorsanız sıfırlamalarınızı Teardown attribute'u ile işaretlediğiniz ayrı bir metotta yapabilirsiniz. xUnit kullanıyorsanız Dispose metotunda yapabilirsiniz.
Exercise ve Verify aşamaları test case içerisinde bulunur. Exercise aşamasında test gerçekleştirilir. Verify aşamasında test sonucu doğrulanır.
Global Setup test case'ler çalıştırılmadan önce bir kez çalıştırılır. nUnit kullanıyorsanız hazırlıklarınızı OneTimeSetUp attribute'u ile işaretlediğiniz ayrı bir metotta yapabilirsiniz. xUnit kullanıyorsanız Fixture tanımlayıp, onun constructor metotunda yapabilirsiniz.
Global Teardown test case'ler sonlandırılmadan önce çağrılır. nUnit kullanıyorsanız sıfırlamalarınızı OneTimeTearDown attribute'u ile işaretlediğiniz ayrı bir metotta yapabilirsiniz. xUnit kullanıyorsanız Fixture tanımlayıp, onun Dispose metotunda yapabilirsiniz.
Adlandırma Kuralları (Naming Convention)
Test case adlandırmaları için belirli kurallar (naming conventions) var.
MethodName_StateUnderTest_ExpectedBehavior
Test edilen metotun ismi, test edilen koşul ve beklenen davranış kullanılır. Bu kuralın en büyük dezavantajı metot isimleri değiştiğinde test case metot isimlerinin de düzenlenmesi.
Örnek kullanım : DecreaseItem_ItemQuantityIsOne_RemoveItem
Should_ExpectedBehavior_When_StateUnderTest
Beklenen davranış ve test edilen koşul kullanılır. Okunması gayet kolay.
Örnek kullanım : Should_RemoveItem_When_DecreaseItem_If_ItemQuantityIsOne
When_StateUnderTest_Expect_ExpectedBehavior
Test edilen koşul ve beklenen davranış kullanılır. Okunması gayet kolay.
Örnek kullanım : When_ItemQuantityIsOne_Expect_RemoveItem
Görüldüğü gibi hepsi de gayet anlaşılır. Dikkat etmemiz gereken şey, hangi kuralı tercih ediyorsak tüm test case'lerde aynı kuralı uygulamalıyız.
Test türleri
Üç farklı test türü var. Unit Test, Integration Test ve E2E Test.
Yazının başında TDD'nin asıl amacının test case'ler yazarak kodu geliştirmek olduğunu belirtmiştim. Bu yüzden UnitTest, TDD için en önemli test. Unit Test'in en önemli kuralı 3rd party bağımlılıkların ortadan kaldırılarak test yapılması. Bir kuponun ForLoyal özelliği olduğunu düşünelim. Müşterinin loyal olup olmadığının sorgulanması ve ona göre kuponun uygulanması gerekli. Yani öncelikle kuponun kupon servisinden çekilmesi gerekli. Daha sonra müşteri servisinden müşteri bilgisinin sorgulanması gerekli. Bu servisler 3rd party bağımlılıklardır. Çünkü bu servisler veritabanı ile iletişime geçmektedir. Ama bizim asıl amacımız belirttiğimiz kuponun sepet için uygulanıp uygulanmadığını test etmektir. O yüzden veritabanı ile iletişime geçen servisler mock'lanır. Mock'lanan bu servislerden test case'imize uygun dummy kuponlar döndürülür. Örneğin; tarihi geçmiş bir kupon döndürülebilir. Ya da ForLoyal bir kupon döndürülüp, müşteri servisinden Loyal olmayan bir müşteri döndürülebilir.
Integration Test'de ise 3rd party bağımlıklar ile birlikte gerçek test gerçekleştirilir. Gerçekten veritabanına gidilir ve kupon sorgulanır. Kupon uygulandıktan sonra yine aynı şekilde veritabanına kayıt edilebilir. Bu sebeple integration testler daha komplekstir.
E2E Test'de ise test API'si ayağa kaldırılır. Endpoint'lere istek atılır ve sonuçlar kontrol edilir. En maliyetli test de budur.
Code Coverage
Code coverage, kodun ne kadarının test case'lerde doğrulandığını gösteren bir metrik. Koddaki senaryolar doğrulandıkça coverage da artar. Coverage ne kadar yüksekse uygulama da o kadar güvenli demektir.
Örnek projeye Github adresimden ulaşabilirsiniz.
Vesselam.
Yorum bırak
Yanıtla
Yanıtlamayı iptal et