Test Driven Development

01.07.2022 | dakika okuma
TDD Workshop

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.

TDD Cycle - RED - Write Test

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.

Feature Request - Add Item to Cart

Ş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.

TDD Cycle - GREEN - Write Code

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.

TDD Cycle - REFACTOR - Clean Code

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.

TDD Cycle - RED - Write Test

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.

TDD - AAA Pattern

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.

Setup & Teardown

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.

Global Setup & Global Teardown

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.

Test Pyramid

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.

Unit Test

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.

Integration Test

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.

E2E Test

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.

Code Coverage

Örnek projeye Github adresimden ulaşabilirsiniz.

ahmetkucukoglu/tdd-cart-app

Shopping Cart App with TDD

C#
0
0

Vesselam.

Yazıyı Paylaş

Önceki yazı
Modular Monolith Nedir?

Yorum bırak

Yanıtla

Yanıtlamayı iptal et
Bu site reCAPTCHA tarafından korunmaktadır ve Google Gizlilik Politikası ve Hizmet Şartları geçerlidir. Yorumunuz başarılı şekilde gönderildi reCaptcha doğrulanamadı
Muhabbetle ASP.NET Core ile geliştirildi.