Birim Test (Unit Testing)

bi4code
6 min readMar 22, 2020

Yazılım geliştirme yaşam döngüsü(Software Development Life Cycle) sırasıyla gereksinim analizi, tasarım, geliştirme ve test aşamalarından oluşur. Geliştirilen yazılıma her aşamada farklı test türleri uygulanır. Bu yazıda; yazılımın geliştirilmeye başlanmasından itibaren yazılan Birim Test konseptinden bahsedilecektir. Bir yazılımda test edilebilecek en küçük kod blok’una birim denir. Bu birimin farklı şartlar ve veriler ile testinin yapılıp davranışının gözlemlenmesi işlemine Birim Test adı verilir. Testi yapılacak birim, nesne yönelimli programlama dillerinde bir sınıf, birkaç sınıfın birleşimi veya bir sınıf içerisindeki birkaç metot’un birleşimi olabileceği gibi fonksiyonel programlama dillerinde her bir fonksiyon bir birim olarak kabul edilir(Martin Fowler). Geliştirilen yazılımdaki birimlerin hatasız çalışır olduğunu somut olarak görmek, yazılım üzerinde sonradan yapılan değişikliklerin etkisini gözlemlemek, hataları önceden tespit ederek testeri otomatize(continuos integration) etmek, zamanı ve maliyeti azaltmak Birim Test’in en belirgin özellikleridir. Birim test yazmak yazılımın daha güvenilir bir şekilde geliştirilmesini ve kaliteli bir ürün olarak sunulmasını sağlamakla birlikte sürdürülebilir ve ölçeklebilir bir kalite kontrol (Quality Process)’ün oluşmasını sağlar. Örneğin, tespit edilen hataların yüzlerce geliştirici arasından sadece ilgili geliştiriciye gönderilmesi. Yazarların eserlerini kontrol ettirmek için editör ve profesyonel okuyucular ile anlaşmalarına benzer şekilde yazılımında kalite kontrolünün yapılabilmesi için Birim Test’inin yazılması gereklidir. Bu yazıda Birim Test’in avantajları ve dezavantajları, test yazarken dikkat edilmesi gerekenler ve sık kullanılan convention’lardan bahsedilecektir.

Test Birim’lerinin Kaynak Kod’taki Karşılığı cited by martinfowler.com

Avantajları;

Birçok otorite tarafından birim test yazmanın hem kod kalitesini arttırdığı, daha üretken olmayı sağladığı, kaynak kod’u kısalttığı, zaman ve maliyeti düşürdüğü gürülmüştür. Ayrıca geliştiricileri sürekli bug-fix döngüsü içerisinde kalarak aynı kodu tekrar tekrar düzeltme (refoactor) eforundan kurtararak daha kaliteli ve hızlı geliştirilen bir yazılım ortaya çıkmasını sağlar. Birim test’in yazılabilmesi için test edilecek fonksiyon ve sınıflar birbirlerinden bağımsız(decoupled) olmalıdır. DI ve IOC prensiplerine uygun olmalıdır. Bu prensipler çerçevesinde geliştirilen yazılım, hem reusable hem de geliştiricinin yazılımın tasarımına çok kafa yormasını gerektirmeden kendiliğinden moduler bir yapı ortaya çıkmasını sağlar. Geliştiriciyi kod yazmadan önce plan yapmaya zorlar. Birim Test’i yazılan kod’un fonksiyonelliğin test edilmesi için GUI geliştirilmesine gerek kalmaz. Birim test’te bug-fix için gereken efor entegrasyon ve kabul test’lerinde gerekenden çok daha azdır. Debugging işlemi birim test’i olan yazılımda daha kolaydır. Çünkü değişiklik yapıldıktan sonra sadece testi hata veren birime odaklanmak yeterlidir hatayı bulmak için debug yapmaya gerek yoktur. Birim test, yazılan kod’un refactor edilme hızını arttırır. Testi yapılan birimin fonksiyonelliğini ortaya koyarak ekibe sonradan katılan geliştiricinin codebase’e daha kolay ve kısa sürede adapte olmasını sağlar(living documentations of application). Birim Test yazmak, geliştiriciye yazdığı kod’a güven duymasını sağlar. Geliştiriciye çevik(agile) ve sağlam bir tasarım yaptığının hissiyatını verir.

Dezavantajları;

  • Kod yazma süresini arttırır fakat sonrasındaki birçok süreci kısaltır. Örneğin bug-fixing süresini kısaltır.
  • Tek başına yeterli değildir(Tüm sistemi kapsamaz). Sadece data’yi onun fonksiyonelliğini test eder. Entegrasyon ve sistem testleriyle desteklenmelidir.
  • Bir satırın test edilmesi için birden fazla satır kod yazmak gerekir.

Daha İyi Birim Test Yazmak İçin; (Best Practise of Unit Testing)

  • Her bir fonksiyon için test yazmak doğru değildir. Sadece sistemin davranışını etkileyen birimler için test yazmak daha uygundur. Örneğin getter and setter metot’ları için test yazmamak.
  • Birim Test otomatize edilebilecek şekilde olmalıdır(CI/CD).
  • Her bir logic için ayrı bir test metot’u yazılmalıdır(one test per logical path).
  • Sadece mantık(bussiness logic) içeren birimler için değil aynı zamanda birimin performansını ölçmek içinde Birim Test yazılmalıdır.
  • Test’i yapılan birimde güncelleme yapıldığında(bussiness logic hariç), birimin test’inin güncellemesine gerek olamayacak şekilde yazılmış olması gerekir.
  • Private method’lar için birim test yazmak gerekmez. Public method’lar için Birim Test yazılması yeterlidir.(Public method’lar scope’ları içerisinde private method’ları çağıracağı için private method’lar dolaylı yoldan test edilmiş olurlar.)
  • Birim Test yazılırken Arrange-Act-Assert veya Given-When-Then gelenekleri(convention) yayğın kullanılmaktadır.
  • Önce test yazılmalıdır. Sonra testi geçebilecek(success) birim yazılmalıdır.
  • Kaynak kod ile test kod’unu izole etmek gerekir.(Kaynak kod’lar -> src/main, Birim Test kod’ları src/test)
  • Hata(bug) çıktığında düzeltmeden(fix) önce testini sağlamlaştırıp(expose test) hata düzeltmek yapmak birim testing güvenilirliğini arttıracaktır.
  • Testleri bir birinden bağımsız yazmak tercih edilmelidir. Örneğin database ile ilgili bir sınıfın testleri yazılırken doğrodan database bağlantısı oluşturmak yerine abstract sınıflar üzerinden veya mock database connection nesnesi(mock objects fill missing parts) üzerinden bağlantı sağlanmalıdır.
  • Yazılan test, tüm olası durumları içermelidir(cover all the paths).
  • Test metot’larının ismi okunabilir(readable) ve testi yapılan birimi kolay takip edilebilecek şekilde olmalıdır(self descriptive).
  • Test script’lerinde yapılan değişiklikleri gözlemlemek için versiyon kontrol sistemi kullanılmalıdır.
  • Hata yakalama(exception handling) durumlarını test etmek için test metot’u içerisinde Try-Catch blok’u kullanılmamalıdır(annotation kullanılabilir).
  • POJO test edilmemelidir(don’t test java).
  • Test metot’u gittikçe karmaşıklaşıyorsa(ortalama 5–10 satır ideal), mock object sayısı çok fazlaysa(en fazla 4 mock object ideal) kodun yeniden yapılandırması gerekmektedir.
  • Her bir bug bulunduğunda yeni bir Birim Test eklenmelidir.
  • Test yazma sürecini hızlandırmak için tool kullanmak. Örneğin java için JUnit framework’ünü kullanarak Birim Test yazmak.
  • Konfigürasyon ve log alma işlemleri test edilmemelidir.
  • Test kurulum aşaması(setup) mümkün olduğunca az kod satırından oluşmalıdır. Test sınıfının kod satır sayısı kaynak kod’taki sınıfı geçmemelidir.
  • Aynı test metodu içerisinde birbirinden bağımsız durumu(case) test eden birden fazla Assert kullanılmamalıdır.
  • Aynı durumu(case) test eden yapı birden fazla test metot’u ile bağlantılı olmamalıdır. Aksi halde kaynak kod değiştiğinde tüm test metot’larınında değişmesi gerekir.

Eğer Test Metot’u;

  • Veritabanı ile bağlantı kuruyorsa
  • Network üzerinden iletişim sağlıyorsa
  • Dosya sistemiyle etkileşim halindeyse
  • Diğer Birim Test’ler ile eş zamanlı çalışıyorsa
  • Konfigürasyon dosyası üzerinde değişiklikler yapıyorsa

Birim Test değildir.

Birim Test hata(fail) verdiğinde hatanın neyden kaynaklandığı tespit edilemezse yapılan test Birim Test olmaz(hata internet bağlantısının yavaşlamış olmasında kaynaklanabilir veya veri tabanında bir problem olmuş olabilir).

Sadece Sistem Test’inin Yapıldığı Bir Yazılımda;

Entegrasyon ve birim test’lerin yazılmadığı, iki farklı birim içeren bir yazılım için sadece sistem testi yapılmışve bir hata ile karşılaşılmış olsunsun hatanın;

  • Birim 1’den mi?
  • Birim 2’den mi?
  • Hem birim 1 hem birim 2'den mi?
  • İki birim arasındaki interface’ten mi?

hatanın kaynağını bulmak zorlaşacağından, birim test yazmanın önemi de kolaylıkla fark edilebilmektedir.

Farklı Test Türlerinde Hata Düzeltme Maliyet Grafiği cited by fullstackmark.com

Yazılım test aşamaları sırayla; unit test -> integration test -> system test -> acceptance test.

Test Güdümlü Geliştirme (Test Driven Development - Test First Development)

Test Driven Development(TDD) önce Birim Test metot’larının yazılması sonra kaynak kod içerisinde Birim Test’i geçebilecek birim’lerin yazması prensib’ini benimsemektedir. Başlangıçta test’i yazılacak birim var olmadığı için test hata(fail) verecektir. Sonrasında testi geçebilmek için kaynak kod içerisinde birim’in kod’u yazılır(success). Bu sayede çevik(agile) bir süreç elde edilmiş olur.

TDD Yaşam Döngüsü cited by codegrip.tech

Java için geliştirilmiş açık kaynak birim test framework’leri;

  • JUnit(child of XUnit) — first testing then coding
  • TestNG
  • JTest(Shows Code Covorage)
  • JMockit
  • SonarQube
  • EMMA
  • JBehave
  • Serenity
  • Selenium

Hangi framework’ü seçmeliyim?

Birim Test tool’ları kod kalitesini arttırırken, maliyet ve test için harcanan zamanı azaltır. Bu kriterler göz önünde bulundurularak uygun tool seçimi yapılmalıdır. Maliyeti düşürmek için açık kaynak kod’lu test tool’ları tercih edilebilir.

Birim Test İle İlgili Bilinen Yaygın Hatalar;

  • Birim Test’i test mühendisleri yazmalıdır.
  • Birim Test’i codebase tamamlandıktan sonra yazarım.
  • Birim Test yazmak için zamanım yok.
  • Birim Test yazmak zor.
  • Test Kod’unun müşteri için önemi yoktur. Test kod’unun müşteriye satılamaz.
  • Birim Test yazmayı, yazılım geliştirmenin bir parçası olarak görmemek.
  • Birim Test yazmak tüm hataları(bug) bulmayı garanti eder. Hayır tek başına yeterli değildir sistem ve entagrasyon test’lerininde yapılması gereklidir.

Kavramlar;

Code or Application Under Test(CUT): Belirli bir fonksiyonun, kod bloğunun veya uygulamanın test edilmesi durumuna denir.

Code Covarage: Yazılımın test edilme oranını ifade eder. Code Covarage arttıkça yazılımdaki test edilen birim sayısıda artar(80% Code Covarage idael). Code Covarage Line ve Branch olmak üzere ikiye ayrılır.

White box testing: Birim test’in yanı sıra bir çok test türünü içeren ve kaynak kodlara hakim kişiler(developers) tarafından yapılan testlere denir.

“AAA” approach: Arrange,Act ve Assert ‘in kısaltmasıdır. Arrange -> test için ortamın hazırlanması, Act -> test’in icra edilmesi, Assert -> test sonucunun değerlendirilmesi(success or fail).

Given, When and Then Approach(BDD): Birim Test’in daha okunabilir(readable) olması için oraya konulmuş bir yaklaşımdır. Given(test edilecek davranış için veri sağlanması), When(gerçekleştirilecek davranış), Then(beklenen davranış).

Continuos Integration(Birim Test Açısından): Birim Test’lerin pull request yapmadan önce her geliştiricinin local’inde çalıştırılıp test’ten geçmesi halinde push’lanması durumu.

Test Double: Stunt Double(dublör) terimine benzer şekilde test yazılımında gerçek nesnelerinin yerine kullanılan nesnelerdir.

Mock: Test ortamında bazı nesneleri(Database, File System) simüle etmek için kullanılır.

TLDR;

“The purpose of unit testing is not for finding bugs.” — anonym

“Tests are the stories we tell the next generation of programmers on a project.” — Roy Osherove

“If it talks to the database, it talks across the network, it touches the file system, it requires system configuration, or it can’t be run at the same time as any other test.” — Michael Feathers

Referanslar;

http://www.oracleunittesting.com/2012/07/13/why-why-why-unit-testing/#more-543

https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/

http://softwaretestingfundamentals.com/unit-testing/

https://fullstackmark.com/post/7/learning-unit-testing-in-aspnet-core

https://martinfowler.com/bliki/UnitTest.html

https://stackify.com/unit-testing-basics-best-practices/

https://www.simform.com/unit-testing-tdd/

https://www.leadingagile.com/2018/11/whats-the-scope-of-a-unit-test/

https://www.educba.com/unit-testing/

https://less.works/less/technical-excellence/unit-testing.html

https://www.slideshare.net/Tricode/best-practices-unit-testing

https://smartpuffin.com/unit-testing-best-practices/

https://blog.parasoft.com/unit-testing-best-practices-getting-the-most-out-of-your-test-automation

http://zezhen.me/posts/unit-test-best-practices/

--

--