Behavior Driven Development (BDD) & Cucumber
Behavior Driven Development (BDD) Nedir?
BDD (Davranış Odaklı Geliştirme) geliştiriciler ile iş analisti, müşteri gibi teknik olmayan kişilerin ortak ürün geliştirebilmesi ve ürünün davranışlarını teknik olmayan günlük konuşma diline yakın bir ifade ile tanımlayabilmesini sağlayan geliştirme yaklaşımıdır. BDD ile amaç, ürünü geliştirmeye başlamadan tüm paydaşlar ile ortak bir şekilde ürün davranışını tanımlamak ve kabul kriterlerini belirlemektir.
BDD vs TDD kıyaslamasını fazlaca duymuş olabilirsiniz. BDD, TDD gibi ürünü test etmeyi amaçlamaz, bunun yerine beklenilen davranışların tanımlanmasını ve tüm paydaşların el sıkışmasını sağlar. BDD testleri ise tanımlanan davranışları koda dökerek bu davranışların istenilen şekilde çalışıp çalışmadığından olduğundan emin olmamızı sağlar.
BDD felsefesini derinlemesine anlamak isterseniz BDD in Action kitabına göz atabilirsiniz, bu yazıda ki amaç yazılım geliştiriciler için BDD testlerinin nasıl yazılabileceği ve bu testler için geliştirilmiş Cucumber aracı olacaktır.
Cucumber & Gherkin
Behavior Driven Development’ı (BDD) uygulayabilmeyi ve BDD testleri yazabilmeyi sağlayan bir test platformudur. Cucumber, BDD testi yazmak için gerekli ücretli/ücretsiz test araçları sunar ve farklı dil ve teknolojiler için kütüphanelere sahiptir.
Cucumber, Gherkin adını verdiği, bir dil ile günlük konuşma diline yakın testler yazabilmeyi sağlar. Gherkin ile Given/When/Then ifadeleriyle test senaryoları yazılır.
given : davranışın gerçekleşmesi için gerekli başlangıç koşulları
when : davranışın gerçekleşmesi
then : davranışın sonuçları
Cucumber ile BDD Testi
Öncelikle BDD testleri yazmak ve çalıştırabilmek için test projenize ilgili Cucumber paketlerini projenize eklemelisiniz, kurulum için buraya göz atabilirsiniz.
Cucumber testleri feature uzantılı test dosyalarda barındırılır. Testlerin feature’bazında test edileceği düşüncesi ile ortaya çıkmış bir formattır. Feature’lar kendi içerisinde birden fazla Scenario barındırır, senaryolar ise Given/When/Then ile oluşturulmuş Action Word’lerden oluşur.
BDD Süreci
Test için, örnek olarak bir e-ticaret platformu için ürün satın alma sürecini tasarlayarak ilerleyelim.
Feature 1: Kullanıcılar e-tiracret platformu üzerinden seçtikleri ürünü satın alabilir.
Senaryo 1: (Başarılı) Satın alınan ürününün stok miktarı, satın alınan adet kadar azalalır.
Senaryo 2: (Başarısız) Satın alınan ürününün stok miktarı, talep edilen adetten az ise satınalma gerçekleştirilemez.
Şeklinde devam eden farklı fail ve success senaryosu eklenebilir.
Belirlediğimiz ürün satın alma özelliği için kararlaştırılan iki senaryo üzerinden BDD testleri yazmak istediğimizde;
Feature: Kullanıcılar e-tiracret platformu üzerinden seçtikleri ürünü satın alabilir. Scenario: (Success) Satın alınan ürününün stok miktarı, satın alınan adet kadar azalalır. Given "USER1" kodlu kullanıcı oturum açmalıdır And "PRODUCT1" kodlu ürünün stok miktarı 100 olarak güncellenir And "USER1" kodlu kullanıcının sepeti temizlenir And "PRODUCT1" kodlu üründen sepete 1 adet eklenir When "USER1" kodlu kullanıcı ödeme işlemi gerçekleştirildiğinde Then Başarılı sonucu alınmalıdır And "USER1" kodlu kullanıcı için "PRODUCT1" ürününü içeren sipariş oluşmalıdır And "PRODUCT1" kodlu ürün stoğu 99 olmalıdır Scenario: (Fail) Satın alınan ürününün stok miktarı, talep edilen adetten az ise satınalma gerçekleştirilemez. Given "USER1" kodlu kullanıcı oturum açmalıdır And "PRODUCT1" kodlu ürünün stok miktarı 0 olarak güncellenir And "USER1" kodlu kullanıcının sepeti temizlenir And "PRODUCT1" kodlu üründen sepete 1 adet eklenir When "USER1" kodlu kullanıcı ödeme işlemi gerçekleştirildiğinde Then Başarısız sonucu alınmalıdır And "USER1" kodlu kullanıcı için sipariş oluşturulmamalıdır And "PRODUCT1" kodlu ürün stoğu 0 olmalıdır
Yukarı belirlediğimiz iki senaryonun davranışını Gherkin ile yazmış olduk, Gherkin; değişken veri tipleri gibi, tekrarlayan ve parametrik testler yazmamızı sağlayan yeteneklere sahiptir. Gherkin’in tüm yetenekleri için buradaki bağlantıya göz atabilirsiniz.
Burada bir parantez açmak istiyorum, Cucumber bir test aracıdır bu sebeple projenizde bir BDD test altyapısını sizin kurmanız gerekebilir, ilgili kurulum dokümanlarına ve dokümanda yeralan örnek projeye gözatınız. Testleri çalıştırabilmek için JUnit, Xunit, vb. bir test projesine, senaryolar ve feature’lar arasında state paylaşımı için test context, scenario context yapısına, mock için kullandığınız dile göre Mockito,GoMock, Moq, vb. kütüphanelere ihtiyacınız olacaktır.
Senaryoları tanımladıktan sonra, senaryoların test kodlarını yazarak tanımlanan davranışı test edebilir, bunun için Cucumber kullanarak çalıştırabileceğimiz test kodlarını yazabiliriz. Feature içerisinde Given/When/Then ile yazdığımız her bir davranışı aşağıdaki kodlamamız gerekiyor (Örnek olması açısından Java ile paylaşıyorum).
class ProductPurchasingStepDefinitions { @Given("{string} kodlu kullanıcı oturum açmalıdır") public void kullaniciOturumAcmalidir(String p1) { User user = new User(); user.code = p1; user.password = "****"; RestUtils.Post("http://localhost:8080/userservice/user/login", user); } @Given("{string} kodlu ürünün stok miktarı {long} olarak güncellenir") public void urununStokMiktariGuncellenir(String p1, Long p2) { Product product = RestUtils.Get("http://localhost:8080/productservice/"+p1); product.stock = p2; RestUtils.Put("http://localhost:8080/productservice/"+p1, product); } @Given("{string} kodlu kullanıcının sepeti temizlenir") public void kullanicininSepetiTemizlenir(String p1) { ... } @Given("{string} kodlu üründen sepete {long} adet eklenir") public void sepeteUrunEklenir(String p1, Long p2) { ... } @When("{string}kodlu kullanıcı ödeme işlemi gerçekleştirildiğinde") public void kullaniciOdemeIslemiGerceklestirildiginde(String p1) { ... } @Then("Başarılı sonucu alınmalıdır") public void basariliSonucuAlinmalidir() { ... } @Then("{string} kodlu kullanıcı için {string} ürününü içeren sipariş oluşmalıdır") public void siparisOlusmalidir(String p1, String p2) { List<Order> orders = RestUtils.Get("http://localhost:8080/orderservice/order"); assertThat(orders.getMyItems(), hasItem(hasProperty("code", is(p2)))); } @Then("{string} kodlu ürün stoğu {long} olmalıdır") public void urunStoguKontrolEdilir(String p1, Long p2) { Product product = RestUtils.Get("http://localhost:8080/productservice/product/"+p1); assertEquals(product.stock, p2); } @Given("{string} kodlu ürünün stok miktarı {long} olarak güncellenir") public void stokGuncellenir(String p1, Long p2) { ... } @Then("Başarısız sonucu alınmalıdır") public void basarisizSonucuAlinmalidir() { ... } @Then("{string} kodlu kullanıcı için sipariş oluşturulmamalıdır") public void siparisOlusturulmamalidir(String p1) { ... } }
Artık yapmanız gereken ilgili test senaryolarını kodlamak olacaktır. Cucumber feature dosyalarını çalıştırmaya başladığında feature içerisinde yer alan action word’lerin karşılığı olan metotlarınızı çalıştıracaktır. Cucumber bu testleri çalıştırırken, feature dosyası içerisinde belirlediğiniz senaryoları bir birinden bağımsız ve sıralı bir şekilde ele alır.
Yukarıdaki test kodları içerisinde her bir metot altında servis çağrısı, DB işlemi, vb. yapabilirsiniz. Action word sonuçlarına Assertions kullanabilir, bu sayede dilediğiniz durumda testi fail veya pass edebilirsiniz.
Tavsiyeler
- Tekrarlayan Action Word metotları yazmaktan kaçının, örneğin servis cevabını kontrol etmek için parametreler kullanın örn: “Servis HTTP {integer} cevabı dönmelidir”
- Çok fazla parametre kullanarak testleri okunmaz duruma getirmek yerine DataTable gibi veri yapıları kullanabilirsiniz.
- BDD testlerini Unit Test gibi yazmayın, unutmayın ki amaç ilgili metotların doğruluğunu test etmekten ziyada sabit girdiler altında istenilen çıktının gerçekleştiğimdem emin olmaktır.
- Cucumber Studio Automation ile feature dosyalarınızdan istediğiniz dilde karşılık metotları oluşturabilirsiniz.
- Lisans ücreti dahilinde Cucumber Studio ile BDD test süreçlerinizi uçtan uca yönetebilirsiniz, göz atmanızı tavsiye ederim.
- BDD integration test içindir düşüncesine kapılmayın, sonuç olarak entegrasyon noktalarınızı mock’ladığınızda projenizin istediğiniz servislerinin davranışlarını da test edebileceksiniz.
- Testlerinizi CI/CD süreçlerine entegre edin ayrıca Cucumber Export ile dilediğiniz formatta test sonuçlarını da alabilirsiniz.
- Java ile geliştirme yapıyorsanız, entegrasyon noktalarını mock’lamak için WireMock’a şans verin.
- Java ile geliştirme yapıyorsanız bazı third party araçların in-memory alternatifleri mevcut, örneğin Redis ve Kafka için EmbeddedKafka ve EmbeddedRedis kullanabilirsiniz.
- Java veya .Net ile geliştirme yapıyorsanız Inmemory DB için H2 veya EF Core in-memory kullanabilirsiniz.