Cucumber Ile Bdd
Behaviour Driven Development kavramı son zamanlarda dikkatimi çeken bir konu. İlk önce Dan North tarafından ortaya atılmış. Dan North, TDD(Test Driven Development) ile ilgili bazı tecrübelerinden yola çıkarak gördüğü aşağıdaki gibi bazı eksikliklerden dolayı bu kavramı ortaya atmış;
- Testler nerede başlar nerede biter belli olmuyor,
- Testlere verilen isimleri sadece developer’lar anlıyor,
- Ortak bir dil(ubiquitous language) kullanılamıyor,
- Test, Analist, Development arasında ortak bir anlayış sürece dahil edilemiyor.
BDD ile aynı amacı güden pratikler farklı isimlerle anılabiliyor, bunlardan bazıları; Specification by Example, Acceptance Test Driven Development, Extenden Test Driven Development.
BDD ile ilgili Liz Keogh’un şu şekilde küçük ve kapsayıcı bir tanımı var; “Using examples in conversation to illustrate behaviour.”
BDD, TDD’nin bir alternatifi değil, daha çok Eric Evans’ın Domain Driven Design kitabında bahsettiği kavramlar ile TDD’nin birleşimi gibi. TDD yapmanız BDD yapmanızı gereksiz kılmıyor, tersi de geçerli, BDD yapıyor olmanız TDD yapmanızı gereksiz kılmıyor.
Hem BDD hem de TDD test pratiğinden ziyade tasarım pratiği olarak görülüyor. Çünkü her iki pratiğin de asıl odak noktası test veya test otomasyonu değil, ama bu pratik uygulandığında test otomasyonu sürecin doğal bir çıktısı oluyor. BDD ve TDD’nin ayrıldıkları ana nokta ikisinin odaklandıkları konular; TDD tarafında daha çok koda odaklanılıyor, “iyi tasarlanmış temiz kod” amacı öne çıkıyor. “Test, Code, Refactor” döngüsü ile bu sağlanmaya çalışılıyor. BDD tarafında ise birinci sıraya iş birliği ve ortak anlayış konuluyor, bunu sağlamak için de davranışların çalıştırılabilir(executable) tanımları(spesifikasyonlar) three amigos denilen test, analist ve developer üçlüsü tarafından yazılıyor. Yani BDD ise test’den öte çalıştırılabilir spesifikasyonlar sunuyor.
Her iki pratikte de önce test kodları yazılsın sonra bu kodları gececek gercek kodlar yazılsın mantığı var, BDD tarafında buna “outside-in” development diyorlar. BDD tarafında aslında test kodlarından önce bir de three amigos tarafından davranışın tanımlanması adımı var. Yani ilk önce davranış tanımı, sonra bu davranışları test edecek test kodları, sonra da bu davranışı gerçekleştirecek gerçek kodlar yazılıyor, dışarıdan içeriye doğru gelme bu şekilde oluyor.
Pratiğin gerçekleştirilmesi anlamında bana BDD’ye daha çabuk uyum sağlanacakmış gibi geliyor, çünkü BDD tarafı TDD’ye göre biraz daha üst seviyede kalıyor. TDD tarafında biraz daha birim(methdod, sınıf) seviyesinde önce test kodu sonra gerçek kod yazılması, developer’ın kod yazmaya bakış açısında daha ciddi bir değişiklik gerektiriyormuş gibi geliyor. Bu değişikliğe uyum da kişiden kişiye değişmekle birlikte muhtemel 5-6 aydan önce oturmuyor.
Neden BDD yapalım? Diye sorunca; sistemin/projenin gereksinimleri/davranışları anlamında; analist(veya müşteri), testci ve developer arasında güçlü bir iletişime ve ortak bir anlayışa olan ihtiyaç karşımıza çıkıyor. BDD’nin temel amacı yazılım geliştirmedeki bu temel rollerin(analist, test, developer) güçlü bir işbirliği ile çalışmaları. Bu temel amaca erişmek için yapılan pratiğin bize iki önemli çıktısı oluyor, birincisi; sistemin temel davranışlarının test edilmesinin otomatize edilmesi, ikincisi; sistemin davranışlarının güncel bir dökümantasyonunun sağlanması.
Cucumber BDD yapmak için kullanılan bir araç. BDD kavramını kullanabilmeniz için farklı programlama dilleri, farklı ortamlar için sağlanmış başka araçlar da var. Cucumber three amigos tarafından yazılan çalıştırılabilir spesifikasyonların çalıştırılmasını ve sonuclarının raporlanmasını sağlıyor. GitHub üzerinde açık kaynak kodlu bir proje ve arkasında Aslak Hellesoy, Matt Wynne gibi isimler var. İlk önce Ruby için yazılmış sonra Java, Groovy, .NET, JavaScript vs. 14 farklı dili destekliyor ve Spring, Selenium, PicoContainer vs. 5 farklı framework ile entegrasyonu var.
Cucumber çalıştırılabilir spesifikasyonlar yazmak için Gherkin isimli bir dili kullanıyor. Basit bir kaç tane keyword’ü ve formatı olan bir dil bu. Size DSL(Domain Specific Language) ile doğal dil kullanarak spesifikasyonlarınızı(davranışlarınızı) yazma imkanı veriyor. Gherkin’de en son 56 farklı dilde spesifikasyonlarınızı yazabiliyorsunuz.
Cucumber üst seviyede şu bileşenlerden oluşuyor; Business Facing(Features, Scenarios, Steps), Technology Facing(Step Definitions, Support Code, Automation Library, Your System). Burada three amigos’un beraber çalıştığı kısım sadece “Business Facing” olan taraf, “Techology Facing” kısmı sadece developer’lara kalıyor. Bu bileşenlere kısaca bakacağız.
Sisteminizdeki/projenizdeki her bir özellik(feature) için bir tane “.feature” uzantılı Gherkin dosyanız oluyor. Bu feature dosyasında sistemin ilgili özelliğinin tanımı ve bu özelliğe göre farklı durumlardaki davranışı senaryo adı altında yazılıyor. Senaryoları kabul kriteri olarak düşenebiliriz, bu senaryolar Given/When/Then formatında, “steps” denilen adımlarla, yazılıyor. Given ile bir ön koşulu, When ile olayı, Then ile de sonucu tanımlıyorsunuz. Örnek bir Gherkin feature file aşağıdaki gibi;
@foo
Feature: Basic Arithmetic
Background: A Calculator
Given a calculator I just turned on
Scenario: Addition
# Try to change one of the values below to provoke a failure
When I add 4 and 5
Then the result is 9
Cucumber bu step’leri çalıştırabilmek için bunlara karşılık bir kod parcacığının olmasını bekliyor ve bunlara “step definitions” diyor, Java’da bunlar method’lar oluyor. Cucumber aradaki eşleştirmeyi regular expression’lar ile yapıyor. Step tanımlarındaki textleri methodlar üzerine Java annotation’ları ile yazılan ifadeler ile eşleştirmeye çalışıyor ve eşleştirdiği method’u çalıştırıyor. Sisteminizdeki feature dosyalarında yazdığınız her bir step’in tüm sisteminiz boyunca tek bir anlamının olması gerekiyor. Bir step icin Cucumber birden fazla step definiton bulursa bunu hata olarak size yansıtıyor. Yukarıdaki örnek step’ler için step definition’lar aşağıdaki gibi oluyor;
@Given("^a calculator I just turned on$")
public void a_calculator_I_just_turned_on() {
calc = new RpnCalculator();
}
@When("^I add (\\d+) and (\\d+)$")
public void adding(int arg1, int arg2) {
calc.push(arg1);
calc.push(arg2);
calc.push("+");
}
@Then("^the result is (\\d+)$")
public void the_result_is(double expected) {
assertEquals(expected, calc.value());
}
Bu step denifiniton’lar içinde siz gerçek sisteminizde beklenen davranışı test etmeye çalışıyorsunuz. Feature dosyasından methodlarınıza parametre geçirmek için regular expression’ın capture group('()’ icindeki kısımlar) mantığı kullanılıyor.
Cucumber’ın feature/senaryo/step’leri çalıştırma mantığı aşağıdaki gibi;
Bir senaryonun başarılı olması altındaki tüm step’lerin başarılı olmasıdır, bir step’in başarılı olması da ilgili step definition’dan exception(PendingException dışında) fırlatılmaması demektir.
Cucumber senaryo bazında bağımsız çalışıyor, JUnit’deki her bir test methodunun kendi icinde bagımsız calışması gibi. Senaryoların da buna göre tasarlanmasını bekliyor. Yani senaryonun başındaki sistemin state’i ile sonundakinin aynı olmasını bekliyor.
Senaryolara “hooks” denilen yöntemler ile öncesinde/sonrasında çalışacak kodlar ekleyebiliyorsunuz. TDD’deki setup/teardown veya @Before/@After olarak işaretlenmiş methodlar gibi. Örneğin bir senaryo çalıştırılmadan önce veritabanı bağlantısı alınıp, çalıştırıldıktan sonra bu bağlantının kapatılmasını istiyorsunuz, bunu bu hook methodlar ile yapabiliyorsunuz.
Cucumber test otomasyonu anlamında size browser’ı veya swing ekranlarınızı kontrol etme gibi yetenekler sağlamıyor, bu tarz ihtiyaçlar için selenium web driver gibi araçları kullanmak gerekiyor.
Feature ve senaryolarınızı tagleyebiliyorsunuz, örneğin yukarıda verilen örnekte @foo tag’i kullanılmış. Tagler size istediğiniz feature veya senaryo grubunu çalıştırma imkanı veriyor. Ek olarak hook methodları tagler ile ayarlıyorsunuz hangi senaryodalar calışacağını belirtmek için. Örneğin çok yavaş çalışan senaryolarınız var bunları her build’de çalıştırmak istemiyorsunuz, sadece gece yapılan build’de çalıştırmak istiyorsunuz, bu tarz ihtiyacları tag’ler yardımı ile gerçekleştirebiliyorsunuz.
Cucumber ile BDD yapılırken insanlar çok çeşitli sorunlar ile karşılaşabiliyorlar, hatta öyle bir noktaya geliniyorki artık Cucumber ile çalışmak faydadan ziyade zarar vermeye, ayak bağı olmaya başlıyor. En çok yaşanılan sorunlar;
- Rastgele başarısız olan testler(bir çalışıyor bir çalışmıyor),
- Kendisi ile ilgisi olmayan değişikliklerden etkilenip başarısız olan testler(kırılganlık),
- Testlerin çok yavaş çalışması,
- Paydaşlarla iş birliğinin olmaması(sadece dev, sadece test feature’ları yazıyor gibi)
Bu tarz durumlardan kacınmak icin öncelikle test icin yazılan kodların da gercek kodlar gibi tasarım, temizlik, ilgi, bakım gerektirdiğini unutmamak gerekiyor. Dikkat edilmesi gereken bazı konular;
- Feature dosyalarını önemsiz detaylar ile doldurmayın, ilgili feature’un temel olarak neyi kontrol etmeyi gerektirdiğine odaklanın,
- Feature dosyalarında sistemi komut komut yönlendirmek yerine biraz daha üst seviyede düşünün, bu tarz gereksinimleri step definition tarafına bırakın,
- Duplication’lardan kaçının, aynı işi yapan ancak isimleri farklı olan step definition’lar burada krtik oluyor,
- Ortak dil kavramına önem verin,
- Yazılan feature’ları otomatik olarak bir dökümantasyona dönüştürüp, müşteri gibi paydaşlardan geri bildirim alın. Bu işlemi yapan online araçlar da var, relish gibi.
Sonuç olarak; BDD’nin temel amaçı olarak işbirliği ve iletişim ön plana çıkıyor. Bu işe başlayacaksanız detaylarda boğulmamak önemli bir kriter, örneğin UI testleri bu detaylarda boğulmayı çok daha hızlandırabiliyor, o yüzden ilk defa başlayacaksanız UI testlerinden ziyade, backend servislerinizin testleri ile başlamak daha anlamlı olabilir. Test otomasyonu BDD’nin bir çıktısı olarak size çok büyük faydalar sağlayacaktır, her bir geliştirme sonrasında günlerce, gecelerce regresyon testi koşma derdiniz olmayacaktır, sisteminizin sağlığını her an kontrol edebilir olacaksınız. Yaşayan bir dökümantasyona sahip olabilirsiniz, ancak yaşayan dökümantasyon bu işe başlarken aklınızda olursa feature’larınızı da ona uygun olarak yazabilirsiniz. Bu feature’lardan RTM(Requirement Tracebility Matrix), TDD(Test Durum Dökümanı) gibi dökümanları çıkarmak hiç zor olmayacaktır. Zaten var olan bir projeye tüm sistemin otomasyonu yapılacak amacıyla feature’larınızı yazmak size gercek faydayı vermeyebilir, bu şekilde bir ihtiyacınız varsa da bunu zamana yaymak, önce yeni gelen işlerle başlamak daha anlamlı olabilir.
Referans olarak benim okuduğum aşağıdaki iki kitap var;
- Specification by Example: How Successful Teams Deliver the Right Software, by Gojko Adzic
- The Cucumber for Java Book: Behaviour-Driven Development for Testers and Developers, by Seb Rose, Matt Wynne, Aslak Hellesoy
Gojko Adzic’in kitabı daha cok kavram üzerine, spesifik bir BDD aracı üzerine değil. Bir çok yaşanmış örnekten yola çıkarak size bu yolda neler ile karşılaşacağınızı ve bunlara karşı neler yapmanız gerektiğini anlatıyor.