Yazılım Geliştirme Sürecinde Verimli Birim Testi Yazımı

İçindekiler
- Giriş
- Birim Testlerinin Amaçları
- Birim Testlerinin Yapılandırılması
- İyi Bir Birim Testinin Dört Temel Özelliği
- Birim Testlerinin Yazım Tarzları
- Birim Testinde Uygulanan İyi Pratikler
- Yazılım Dünyasında Birim Test Alışkanlıkları Anketi
- Sonuç
- Kaynakça
1. Giriş
Test, yaşamımızın her alanında var olan bir kavramdır. “Sınama, deneme, teyit etme” gibi anlamlara sahiptir. Günlük hayatımızda yapmış olduğumuz birçok şey (araç sürerken gaz/fren pedalına basıp araçtan beklediğimiz davranışı gözlemlemek, elimizi yıkarken suyun sıcaklığını ayarlamak, bilgisayarımıza kulaklığımızı takıp ses çıkışını kulaklığa yönlendirerek haberleşmeyi kulaklıktan deneyimlemek gibi) temelinde test kavramına dayanır. Yaşamımızla bu kadar iç içe olan bir kavram, hayatımızı kolaylaştıran ve vazgeçilmez bir parçamız haline gelen yazılımlarla da doğrudan ilişkilidir.
Yazılım testi, birçok tanımı barındırmakla birlikte en basit tabiriyle bir yazılımın kalitesini ve işlevselliğini doğrulamak amacıyla gerçekleştirilen işlemler bütünüdür. Ayrıca yazılımın hedeflenen gereksinimleri karşıladığını, doğru ve beklenen sonuçları ürettiğini ve istikrarlı bir şekilde çalıştığını belirlemek için yapılan bir dizi aktiviteyi içerir. Özetle, yazılım beklenen ve istenen şekilde çalışıyor mu onu teyit etmek için yapılan faaliyetlerdir.
Yazılım testi, uygulamanın birçok seviyesinde yapılabilir.
- Birim Testi: Yazılımın en küçük işlevsel birimlerinin, genellikle fonksiyon veya modül düzeyinde, test edilmesidir. En düşük seviyede gerçekleştirilen test seviyesidir.
- Entegrasyon Testi: Birimlerin bir araya getirilerek birbirleri ile çalışma uyumluluğunun ve haberleşmelerinin test edildiği seviyedir. Birimlerin bir araya geldiği (birleştirildiği) entegrasyon noktalarında hataların tespit edilmesi amacına yönelik yapılan faaliyetlerdir.
- Sistem Testi: Tüm yazılımın bütünleşik bir sistem olarak test edildiği bir seviyedir. Kullanıcı gereksinimlerinin karşılandığının doğrulanması hedefine yönelik yapılan faaliyetlerdir.
- Kabul Testi: Yazılımın son kullanıcıların veya müşterinin beklentilerine uygun şekilde çalışıp çalışmadığının doğrulandığı seviyedir. Müşteri veya son kullanıcı tarafından bizzat gerçekleştirilen bir testtir ve yazılımın kabul edilmesi kararına yönelik yapılan faaliyetlerdir.
Bu yazımızda birim testler üzerine eğileceğiz.
Yazılım dünyasında, son yirmi yılda birim testi faaliyetlerini gerçekleştirmenin önemine dair bir kabullenme olmuştur. Artık çoğu şirket birim testi faaliyetlerini bir zorunluluk olarak görmekte ve hemen hemen her yazılımcı bu konunun önemini anlamaktadır. Dolayısıyla artık birim testlerin varlığına odaklanmaktan ziyade “Verimli bir birim testi nasıl yazılır? İyi bir birim testi nasıl olmalıdır?” sorularına cevap aranmaktadır.
Yukarıdaki tanımda da belirtildiği üzere en basit tanımıyla birim testi, yazılımın en küçük işlevsel birimlerinin test edildiği en önemli test seviyelerinden biridir. Burada çift taraflı bir kazanç söz konusudur. Yani bu seviyede hem hatayı tespit etmek kolay olduğu gibi hatayı düzeltmek de kolaydır. Ayrıca bu seviyede düzeltilen bir hata, gelecekteki muhtemel birçok hatayı da önleyecektir. Bu aşamada tespit edilen ve düzeltilen bir hatanın maliyeti ise oldukça ucuz olacaktır.
Konunun kafamızda oturması açısından bir örnek verecek olursak, otomobil üreten bir fabrika düşünelim. Bu otomobilin her bir parçasının otonom robotlar aracılığıyla üretildiğini ve bir işlem dizisi sonrasında otomobilin üretildiğini varsayalım. Burada otomobilin her bir parçasında işlevsel olan her şey bir birime denk gelmektedir. Parçaların kendi içinde veya diğer parçalarla etkileşimi ve uyumluluğu entegrasyon seviyesine denk gelmektedir. Otomobilin tamamı, bir sisteme denk gelmektedir. Otomobilin alıcıları tarafından incelenmesi ve beklentilerin teyit edilmesi ise kabul seviyesine denk gelmektedir. Dolayısıyla bu bağlamda düşünüldüğünde otomobilin parçaları geliştirilirken hatayı tespit etmek ve düzeltmek hem kolay hem de maliyet açısından daha ucuzdur. Otomobil alıcısına ulaşana kadar tespit edilemeyen her hatanın, hem doğrusal olarak tespiti zorlaşmakta, hem de tespit edilse bile düzeltmesi çok ciddi maliyetlere sebep olmaktadır. Bu maliyetler kabul edilse dahi farklı faktörler açısından (itibar, güven, zaman gibi) kayıplara sebep olmaktadır.
Bu kapsamda, hataları en erken aşamalarda yani birim testi seviyesinde tespit etmek için yazmış olduğumuz birim testinin kalitesi çok önemlidir. Bir başka deyişle, birim testlerini ne kadar iyi, verimli ve efektif yazarsak bize geri dönüşü, katma değeri o kadar fazla olacaktır. Yazılım dünyasındaki tecrübeler, yayınlanan araştırma sonuçları ve anketlerden toplanan veriler incelendiğinde, birim test kapsama (coverage) oranı yüksek olsa da, hata yakalama oranlarının o kadar yüksek olmadığı görülmektedir. Birim test seviyesinde yakalanabilecek hataların daha sonraki aşamalarda tespit edildiği, bazen hiç tespit edilemediği ya da doğrudan son kullanıcı tarafından fark edildiği de gözlemlenmektedir. Dolayısıyla yalnızca birim testi yazmanın yeterli olmadığı, asıl odaklanması gerekilen durumun verimli birim testler yazmak olduğu sonucuna ulaşılmaktadır.
2. Birim Testlerinin Amaçları
Birim testlerinin temel amacı, yalnızca mümkün olduğu kadar çok test yapmak değil, sürdürülebilir yazılım projesi büyümesini sağlamaktır. Başka bir deyişle, yazılımın hatasız ve sürdürülebilir bir şekilde büyüdüğünü teyit etmek amacıyla yapılan faaliyetlerdir.
Birim testlerinin diğer amaçları arasında şunlar yer almaktadır:
- Hata tespit etme ve düzeltme (erken aşamada)
- Yazılım kalitesini artırma
- Mevcut fonksiyonelliği koruma ve sürdürülebilir yazılım (yazılımda yapılan değişikliklerin diğer birimleri/bölümleri olumsuz etkileyip etkilemediğinin kontrolü)
- CI/CD süreçlerinin bir parçası olmak
- Maliyet tasarrufu
- Zaman tasarrufu
3. Birim Testlerinin Yapılandırılması
Birim testlerinin doğru yapılandırılması, yazılım geliştirme sürecinin kritik bir parçasıdır. İyi yapılandırılmış birim testleri, kodun davranışını net bir şekilde ifade eder ve olası hataları erkenden tespit etmeye yardımcı olur. Bu aşamada, testlerin tutarlı ve anlaşılır olması, hem mevcut ekibin hem de gelecekteki geliştiricilerin projeyi daha kolay sürdürebilmesini sağlar. Şimdi, bu yapılandırmanın iki temel yöntemini inceleyelim: AAA modeli ve Given-When-Then modeli.
AAA Modeli
Bu model ismini Arrange, Act ve Assert kelimelerinin baş harflerinden alır. AAA modeli, yazılım birim testlerini düzenlemek ve yapılandırmak için kullanılan bir yaklaşımdır. Bir başka deyişle, AAA modeli birim testleri için tek bir yapı sunar. Bir testi üç parçaya bölerek hem okunabilirliği hem sürdürülebilirliği kolaylaştırır.
Bu model, adını aldığı üç ana adımdan oluşur:
- Arrange (Hazırla)
- Adından da anlaşılacağı gibi hazırlık aşamasıdır. Teste başlamadan önce gerekli olan veri ve ortamın hazırlanması ile ilgili adımları içerir. Testin kapsamı oluşturulur, test verileri sağlanır ve ön koşullar belirlenir. Testin içinde kullanılacak nesnelerin oluşturulması ve başlatılması işlemlerini içerir.
- Özetle, yazacağımız testte hangi nesneleri, kullanıcıları, test verilerini kullanacağız ve hangi ön koşullar (örneğin, bu testin koşabilmesi için sistemde kayıtlı en az iki cihaz olmalıdır.) dahilinde bu testleri gerçekleştireceğiz gibi hazırlık faaliyetlerini içeren adımdır.
- Act (Eylem)
- Testin gerçekleştirildiği adımdır. Sınanacak fonksiyon, metod veya işlev çağırılır. Bu adımda ilgili yazılım birimine beklenen girişler sağlanır ve ardından test edilen işlevin gerçekleştirilmesi tetiklenir.
- Örneğin, girdi olarak iki sayı bekleyen bir toplama işlevini test ettiğinizi varsayalım. Bu adımda, toplama işlevi için beklenen girişler iki adet sayıdır. Bu iki sayı girdi olarak işleve verilir ve işlevin gerçekleştirilmesi tetiklenir.
- Assert (Doğrula)
- Test edilen fonksiyondan beklenen sonuçların doğrulandığı adımdır. Bu aşamada testin başarılı veya başarısız olduğuna karar verilir ve bu kararı vermek için beklenen sonuçlar referans alınır. Hatanın nerede oluştuğunu belirleyen adımdır.
- Özetle, adından da anlaşıldığı gibi doğrulama, teyit etme aşamasıdır. Yine toplama işlevinden örnek verecek olursak, toplama işlevinin döndürdüğü sonuçla beklediğimiz sonuç farklı ise bu toplama işlevinin hatalı çalıştığını gösterir/doğrular. İşlevin döndürdüğü sonuçla beklediğimiz sonuç aynı ise bu da işlevin doğru çalıştığını doğrular.
AAA Modelinde Dikkat Edilmesi Gerekenler
- Arrange (Hazırla) adımı ile teste başlanılmalıdır.
- Nasıl ki yazılım geliştirmeye başlamadan önce kapsamlı bir analiz ihtiyacı vardır, bir test geliştirilmeye başlanmadan önce de muhakkak analiz edilmeli ve hazırlık yapılmalıdır. Bu adım ile başlamak, yazılım davranışından beklentilerin özetlenmesine, kapsamın belirlenmesine yardımcı olur ve iyi bir test tasarımına katkıda bulunur.
- Testler mümkün olduğunca basit tutulmalıdır.
- Testlerin tek bir eylemi (act) olmalıdır. Eğer test çok adımlı ve birden fazla eylemi içeren bir şekilde yapılanıyorsa, muhakkak ayrı testlere bölünmelidir. Örneğin, dört işlemin test edilmesi gereken bir test yazmanız gerektiğinde, dört işlemi de ayrı ayrı test eden dört ayrı birim test yazılmalıdır.
- Koşul ifadelerinden kaçınılmalıdır.
- Koşul ifadeleri genellikle aynı anda çok fazla şeyin doğrulanmasını gerektirir. Yani belirli bir koşul gerçekleşirse bir işlem dizisi yap, farklı bir koşul gerçekleşirse farklı bir işlem dizisi yap, gerçekleşmezse farklı bir işlem dizisi yap gibi. Dolayısıyla, aynı anda çok fazla şeyin doğrulanması birim testi mantığına uygun olmadığından, bu tür testleri daha küçük, odaklanmış testlere bölmeniz verimliliği artıracaktır.
- Assertion sayısı önemli bir göstergedir.
- Testten beklediğimiz davranış/sonuç birden fazla ise, bir testte birden fazla assertion’ın bulunması kabul edilebilir olsa da bu durum, üretim kodunda daha iyi bir soyutlamaya ihtiyaç duyulduğunu gösterebilir.
- Testi bölümlendirin.
//Arrange
,//Act
ve//Assert
gibi yorumlar kullanarak testi bölümlere ayırmak için boş satırların kullanılması, ayırt ediciliği artırarak okunabilirliği kolaylaştırır.
- Test adlandırmasına dikkat edin.
- Test adları anlamlı, kolay okunabilir olmalı ve aşağıdaki yönergelere uygun olmalıdır:
- Senaryoyu, programlamaya aşina olmayan ancak problem alanına aşina olan birine açıklıyormuş gibi tanımlayın.
- Özellikle uzun adlarda, test adlarındaki sözcükleri ayırmak için alt çizgi kullanın.
- Katı isimlendirme politikalarından kaçının ve davranışın karmaşıklığını yakalamak için isimlendirmede esnekliğe izin verin.
- Test edilen davranışa odaklı isimler verin.
- Test adları anlamlı, kolay okunabilir olmalı ve aşağıdaki yönergelere uygun olmalıdır:
- Parametreli testler.
- Karmaşık davranış birimlerinin tüm yönlerini yakalamak için çoğu zaman birden fazla test gerekir. İlgili testleri tek bir test yönteminde gruplandırmak, kod miktarını azaltabilir. Ancak parametre sayısı arttıkça okunabilirlik azalabilir. Okunabilirlik ile kod boyutunu dengeleyecek şekilde testler yazılmalıdır.
- Test verisi üreten araçlar kullanın.
- Test verisi üreten araçlar, yazılım testleri için rastgele veya belirli özelliklere sahip veriler oluşturmaya yardımcı olan araçlardır. Bu tarz araçlar, çeşitli senaryolarda test etmeye ve daha geniş bir veri kümesi üzerinde çalışmaya imkan tanır. Ücretsiz test verisi üreten Mockaroo, Faker, SQL Data Generator, RandomUser.me gibi çok sayıda araç vardır.
Aşağıda AAA modeli ile yapılandırılmış bir birim testi örneği yer almaktadır.

Given-When-Then Modeli
AAA modeline benzer şekilde, Given-When-Then modeli, testleri Given (Arrange - Hazırla), When (Act - Eylem) ve Then (Assert - Doğrula) bölümlerine ayıran bir yapı sunar. Bu model, özellikle yazılımcı veya programcı olmayan kişiler için daha okunabilir kabul edilir.
4. İyi Bir Birim Testinin Dört Temel Özelliği
İlk Temel: Regresyonlara Karşı Koruma
Regresyon, yazılıma yeni bir işlevsellik ekledikten sonra bir özelliğin amaçlandığı gibi çalışmamasına neden olan bir yazılım hatasıdır. Kod tabanı (kaynak kod) ne kadar büyürse, yeni bir işlev eklendiğinde bir özelliği bozma ihtimali o kadar artar. İyi bir regresyon koruması olmadan, projenin uzun vadede sürdürülebilirliği tehlikeye girer. Dolayısıyla iyi bir birim testi regresyonlara karşı güçlü bir koruma sağlamalıdır.
Bir testin regresyonlara karşı koruma açısından ne kadar iyi olduğunu değerlendirmek için:
- Test sırasında yürütülen kod miktarı,
- Bu kodun karmaşıklığı ve etki alanı önemlidir.
Örneğin, iş mantığı içermeyen basit bir kod parçasının test edilmesi pek anlamlı değildir. Regresyonlara karşı korumayı maksimize etmek için bir test mümkün olan en fazla kodu kapsamalı ama aynı zamanda karmaşık bir kod yapısına da sahip olmamalıdır.
İkinci Temel: Yeniden Düzenlemeye Karşı Direnç
İyi bir birim testinin ikinci özelliği, testin altındaki uygulama kodunu yeniden düzenleme olmadan sürdürebilmesidir. Yeniden düzenleme, dışsal davranışı değiştirmeksizin kodu değiştirme sürecidir.
Testin yeniden düzenlemeye karşı direnci, testin ne kadar az yanlış pozitif sonuç ürettiği ile ölçülür. İyi birim testleri, kodun yeniden düzenlenmesi sırasında gereksiz yere başarısız olmaz. Testlerin yanlış pozitifler üretmemesi gerekmektedir (işlevsellik doğru olduğunda testin hata vermemesi).
İyi birim testleri, yeniden düzenlemeye direnç göstermeli, yani kod değişiklikleri sonrası hâlâ başarılı olmalıdır. Testler, sistem altında yatan uygulamanın ayrıntılarına değil, sonuçlarına odaklanmalıdır.
Aşağıdaki görsellerde durum daha net anlaşılmaktadır.

Şekil 1. Testler uygulamanın ayrıntılarına değil, sonuçlarına odaklanmalı
Üçüncü Temel: Hızlı Geri Bildirim
Hızlı çalışan testler, geliştirme sürecinin erken aşamalarında geri bildirim sağlar. Sorunların erken tespiti, hataları düzeltme maliyetini en aza indirir. Yavaş testler ise sık sık test yapmaktan kaçınmaya yol açar ve sorunların tespit edilmesini uzatabilir.
Dördüncü Temel: Bakım Kolaylığı
İyi bir birim testi bakımı kolay olmalıdır.
Testte ne kadar az kod satırı varsa test o kadar okunabilir olur. Gerektiğinde küçük bir testi değiştirmek de daha kolaydır. Test kodunun kalitesi, üretim kodu kadar önemlidir. Testi çalıştırmanın zorluğu da (örneğin, test süreç dışı bağımlılıklarla çalışıyorsa) bakım kolaylığını etkiler.
Bir testin bakım kolaylığı şunları içerir:
- Testin anlaşılması ne kadar kolaysa o kadar iyidir.
- Testi çalıştırmak ne kadar kolaysa o kadar iyidir.
Özetle daha küçük ve daha okunabilir testler yazılmalıdır. Dışa bağımlılıklar mümkün olduğu kadar az olmalıdır. Bu sayede bakım kolaylığı tesis edilmelidir.
5. Birim Testlerinin Yazım Tarzları
Üç çeşit birim test yazım tarzı vardır:
- Çıktı tabanlı
- Durum tabanlı
- İletişim tabanlı
“Çıktı tabanlı” test yazım tarzı, genellikle daha kapsamlı ve güvenilir sonuçlar sağladığı için en yüksek kalitede birim test yazma tarzı olarak tercih edilir. “Durum tabanlı” test yazım tarzı ise geniş kullanım alanı ve esneklik sunduğundan dolayı ikinci sırada tercih edilir. “İletişim tabanlı” test yazım tarzı, belirli durumlarda tercih edilebilir, ancak diğer yazım tarzlarına göre daha az yaygındır. Bu üç test yazım tarzı, ihtiyaçlara göre bir arada kullanılabilir.
Çıktı Tabanlı
Çıktı tabanlı yazım tarzı, sisteme bir girdi ve bu girdi sonucunda elde edilen çıktıyı kontrol etmekle ilgilidir. Çıktı tabanlı test yazım tarzı her yerde kullanılamaz. Bu tarz, yalnızca saf fonksiyonel bir şekilde yazılmış kod için uygundur. Bir sisteme girdi sağlayıp, çıktının kontrol edildiği çıktı tabanlı test yazım tarzı, test edilen kodun global veya içsel bir durumu değiştirmediği durumlar için uygundur.

Şekil 2. Çıktı tabanlı birim test yazım tarzı
Durum Tabanlı
Durum tabanlı yazım tarzı, bir işlemin tamamlandıktan sonra sistemin durumunu doğrulamayı içerir; bu durum sistemin, sisteme entegre olan sistemlerin veya dış bağımlılıkların (veri tabanı, dosya sistemi vb.) durumu olabilir.
Durum tabanlı bir teste örnek verecek olursak, bir order
sınıfımız olduğunu varsayalım ve bu sınıf müşterinin yeni bir ürün eklemesine izin versin. Test, yeni bir ürün eklenmesinin tamamlanmasının ardından products
(ürünler) koleksiyonunu doğrular. Yeni bir ürün eklemeye yardımcı olan AddProduct()
metodunun sonucu, siparişin durumundaki değişikliktir (durumlar şöyle olabilir: siparişiniz alındı, siparişiniz hazırlanıyor, siparişiniz üretimde vb.)

Şekil 3. Durum tabanlı birim test yazım tarzı
İletişim Tabanlı
İletişim tabanlı yazım tarzında, test edilen sistemin entegre olduğu sistemlerle olan iletişimlerin doğrulanması için mock (sahte) yapılar kullanılır. İletişim tabanlı bir teste örnek verecek olursak, bir controller
sınıfı, kullanıcıyı selamlayan bir e-posta gönderir. Test, Controller
’ın EmailGateway
entegre sistemine doğru çağrıda bulunup bulunmadığını doğrulamak için EmailGateWayMock
kullanır.
İletişim tabanlı testin fazla kullanılması, yalnızca kodun küçük bir kısmını doğrulayan, geri kalan her şeyde mock (sahte) yapı kullanan yüzeysel testlere yol açar.

Şekil 4. İletişim tabanlı birim test yazım tarzı
6. Birim Testinde Uygulanan İyi Pratikler
Yazılım geliştirme sürecinde önemli bir aşama olan birim testi yazımında iyi pratiklerin kullanımı önemlidir. İyi pratikler ne kadar çok kullanılırsa ve dikkat edilirse ortaya çıkacak ürünün güvenilirliğine ve kalitesine o kadar katkısı olacaktır. Sürekli gelişen yazılım ekosisteminde nasıl ki bir problem için en iyi kod budur diyemiyorsak, ilgili kodun testi için de en iyi test ve en iyi pratikler bunlardır da dememiz mümkün değildir.
Yaptığımız araştırma neticesinde tespit edebildiğimiz veya kaynağından temin ettiğimiz iyi pratikleri aşağıda maddeler halinde listeleyelim.
-
Okunabilir, anlaşılır, basit testler oluşturun.
Basit test senaryolarını yazmak, sürdürülebilirliğini sağlamak ve anlamak daha kolaydır. Örneğin, testlerinizde manuel dizi veya döngüsel karmaşıklık gibi özelliklerin minimum düzeyde olmasına özen gösterin. Sadece testinizi dışarıdan okuyan bir yazılım geliştirme/test uzmanı ilgili test ile hangi özelliğin test edilmeye çalışıldığını, hangi sonuçların beklendiğini kolaylıkla anlayabilsin.Aşağıdaki örnekte ‘Calculator’ sınıfı, dört temel işlemi gerçekleştirebilen basit bir sınıftır. Test sınıfında ise bu sınıfın her bir metodunu test etmek için ayrı test metotları bulunmaktadır. Bu testler, her bir matematiksel işlemi ayrı ayrı test eder ve test senaryolarını anlaşılır bir şekilde ifade eder. Bu sayede her testin neyi test ettiği kolayca anlaşılabilir ve testlerin sürdürülebilmesi daha basit hale gelir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class CalculatorTest {
@Test
public void testAddition() {
// Arrange
Calculator calculator = new Calculator();
// Act
int result = calculator.add(3, 5);
// Assert
assertEquals(8, result);
}
@Test
public void testSubtraction() {
// Arrange
Calculator calculator = new Calculator();
// Act
int result = calculator.subtract(8, 3);
// Assert
assertEquals(5, result);
}
@Test
public void testMultiplication() {
// Arrange
Calculator calculator = new Calculator();
// Act
int result = calculator.multiply(4, 6);
// Assert
assertEquals(24, result);
}
@Test
public void testDivision() {
// Arrange
Calculator calculator = new Calculator();
// Act
double result = calculator.divide(10, 2);
// Assert
assertEquals(5.0, result, 0.0001);
}
}
-
Deterministik testler hazırlayın.
Deterministik bir test, kod değişmedikçe aynı davranışı sergiler. Bu durum, olası sorunları hızlı bir şekilde tespit etmenizi ve çözmenizi sağlar. Testlerin deterministik olması önemlidir çünkü değişken sonuçlar veren testler güvenilir testler değildir. Testlerinizi yazarken mümkün olan en iyi şekilde diğer test senaryolarından, çevresel değerlerden ve dış bağımlılıklardan izole etmelisiniz.
Aşağıdaki örnekte,
SimpleOperation
sınıfı basit bir şekilde iki sayı arasındaki toplama ve çıkarma işlemlerini gerçekleştirebilen bir sınıftır. Her bir test metodu, bu sınıfın bir metodunu test eder. Testlerin deterministik olması için her test metodu, test öncesi bir durumu düzenler (Arrange), ardından bir işlemi gerçekleştirir (Act) ve en sonunda beklenen sonuçla karşılaştırma yapar (Assert). Bu testler deterministik olarak adlandırılabilir, çünkü her zaman aynı giriş değerleriyle aynı çıkış sonuçlarını üretirler. Testlerin bu şekilde deterministik olması, kodun değişmedikçe beklenen davranışı sergilemesini sağlar ve olası hataların hızlı bir şekilde tespit edilmesine yardımcı olur. Bu testler diğer test senaryolarından izole edilmiş durumları kullanarak kodun belirli kısımlarını test etmeye odaklanır.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class SimpleOperationTest {
@Test
public void testAddition() {
// Arrange
SimpleOperation simpleOperation = new SimpleOperation();
// Act
int result = simpleOperation.add(3, 5);
// Assert
assertEquals(8, result);
}
@Test
public void testSubtraction() {
// Arrange
SimpleOperation simpleOperation = new SimpleOperation();
// Act
int result = simpleOperation.subtract(8, 3);
// Assert
assertEquals(5, result);
}
// Diğer test metodları buraya eklenebilir.
}
-
Tek bir kullanım senaryosunu ele alın.
Her birim testi, yalnızca tek bir kullanım durumunu test etmek üzere düzenlenmelidir. Bu hem testin okunabilirliğini ve sürdürülebilirliğini artırırken hem de uygulamanın spesifik olarak hatalı kısmının tespit edilmesine yardımcı olur.
Aşağıdaki örnekte,
UserManagement
sınıfı bir kullanıcı eklemek içinaddUser
metodunu içerir. Test sınıfındakitestAddUser
metodu sadeceaddUser
metodunun doğru çalıştığını teyit etmek için yazılmıştır. Yani bu test sadece kullanıcı eklemeyi ele alır ve başka bir senaryoyu içermez.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserManagementTest {
@Test
public void testAddUser() {
// Arrange
UserManagement userManagement = new UserManagement();
User newUser = new User("John Doe", "john.doe@email.com");
// Act
boolean result = userManagement.addUser(newUser);
// Assert
assertEquals(true, result);
assertEquals(newUser, userManagement.getUserByEmail("john.doe@email.com"));
}
}
-
Testlerinize uyumlu isimler verin.
Birim testlerinde uygulanabilecek standart adlandırma kurallarını uygulayın. Test isminin fazla uzun olmasından kaçının ve test edilen bölümle alakalı isimler kullanın. Uyumlu test adları, hem kodu yazan programcının hem de gelecekte bu kodla çalışacak diğer kişilerin kodu daha kolay anlayabilmesine katkıda bulunur.
İyi bir isimlendirme için testinizin adı üç bölümden oluşmalıdır:
- Test edilen yöntemin adı
- Test edildiği senaryo
- Senaryo çağrıldığında beklenen davranış
Örneğin, bir
ÜrünServisi
sınıfımız olduğunu varsayalım ve bu servisin hizmetlerinden birinin de toplam fiyatı hesaplamak olduğunu düşünelim. İki farklı senaryomuz olduğunu varsayalım:- Bir kampanya olup bir indirim uygulandığında ürünün toplam fiyatının ne olacağı senaryosu
- Hiçbir indirim olmadığında toplam fiyatın ne olacağı senaryosu
Bu senaryoları test eden metot isimlerini şu şekilde verebiliriz:
indirimUygulandigindaToplamFiyatiHesapla
hicbirİndirimOlmadigindaToplamFiyatiHesapla
-
Testler arasındaki karşılıklı bağımlılıktan kaçının.
Birim testleri, bireysel kod birimlerini doğrulamayı amaçlar; bu nedenle testlerin birbirine bağımlı olmaması gerekir. Örneğin, iki farklı matematiksel işlemi gerçekleştiren bir kodu test etmek için iki ayrı test metodu kullanılmalıdır:
toplamaTesti
vecikarmaTesti
. Böylece her iki test de kendi içinde bağımsız olur ve diğerinin sonuçlarına ihtiyaç duymaz.Bu testlerin bağımsız olduğu anlamına gelir. Her bir test, bireysel kod birimini doğrulamayı amaçlar ve başarısızlığı da izole edilir. Yani bir testin başarısızlığı diğer testi etkilemez. Bu pratiğe bağlı kalmak, testlerin bağımsız olmasını sağlayarak, kodunuzda yapılan değişikliklerin sadece ilgili testleri etkilemesini sağlar ve bu da bakım ve geliştirme süreçlerini daha yönetilebilir hale getirir.
-
Aktif API çağrılarından kaçının.
Testlerinize dahil etmeniz gerekmeyen veri tabanlarına veya diğer hizmetlere yönelik API çağrıların, testler yürütülürken etkin olmadığından emin olun. API taslaklarını belirli birimlerle sınırlayarak, beklenen davranış ve yanıtları içeren testleri tercih edin.
Aşağıdaki örnekte,
UserService
sınıfının bir kullanıcı bilgisini almak içinDatabaseConnection
adlı bir bağımlılığı olduğunu varsayalım. Burada gerçek bir veri tabanı çağrısı yapmadan bir yöntemin davranışını doğrulamak için Mockito gibi mocking çerçevelerini (framework) kullanarak, bağımlılıkların davranışını taklit eden sahte nesneler oluşturulmalıdır. Bu şekilde, birim testi gerçek bir veri tabanına aktif çağrı yapmadanUserService
‘in davranışını test etmeye odaklanır. Veri tabanına aktif bir çağrı yapılması, bağlantı testi gibi konular ayrı bir testin konusudur.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UserServiceTest {
@Test
public void testGetUserById() {
// Arrange
DatabaseConnection mockDatabase = mock(DatabaseConnection.class);
when(mockDatabase.getUserById(1)).thenReturn(new User(1, "John Doe"));
UserService userService = new UserService(mockDatabase);
// Act
User user = userService.getUserById(1);
// Assert
verify(mockDatabase, times(1)).getUserById(1);
assertEquals(1, user.getId());
assertEquals("John Doe", user.getName());
}
}
-
Testleri kod incelemelerine (code review) dahil edin.
Test de yazılım geliştirme sürecinin bir parçasıdır. Dolayısıyla bir uygulamanın kaynak kodunda testler de vardır. Bu bağlamda kod inceleme sürecinde uygulama kodu ve testler bir arada değerlendirilmelidir. Testlerin kodla birlikte yazılması, sadece planlanan güncellemeler ve değişiklikler için değil, aynı zamanda hata düzeltmeleri için de kritiktir. Her hata düzeltmesini doğrulayan bir test bulundurmak, her hatanın düzeltilmiş olduğundan emin olmanıza yardımcı olacaktır.
-
Birim testlerini mümkün olduğunca hızlı olacak şekilde tasarlayın.
Yavaş testler süreci aksatır ve sıklıkla kullanılamazlar. Hızlı ve etkili bir test süreci, geliştiricilere daha verimli bir çalışma ortamı sağlar ve hataların daha hızlı tespit edilip düzeltilmesine olanak tanır. Hızlı ve etkili bir test sürecinde aşağıdakilere dikkat edilmelidir:
- Bağımsız Testler: Her test diğerinden bağımsız olmalı. Böylece bu testlerin paralel olarak veya belirli bir sırayla çalışmasına olanak tanır.
- Mocking ve Stubbing: Gereksiz dış bağımlılıkları engelleyerek test süreci hızlandırılabilir. Mocking ve stubbing, testlerin daha hızlı çalışmasına olanak tanır.
- Test Verilerinin İyi Tanımlanması: Test verileri, testin özel bir durumu hedeflemesi için yeterli olmalıdır. Karmaşık veya büyük veri setleri ile test yazmak, test süresini uzatabilir.
- Paralel Test Yürütme: Eğer mümkünse testlerin paralel olarak yürütülmesi sağlanarak çalışma süresi kısaltılmalıdır.
- Hafif Test Çerçevelerinin Kullanımı: Hafif ve hızlı test çerçeveleri seçerek test süreci optimize edilebilir.
-
Test Otomasyonunu Benimseyin
Birim testleri manuel olarak gerçekleştirilebilir, ancak günümüzde mevcut uygulamalar genellikle otomatik bir birim testi yaklaşımını teşvik etmektedir. Otomatikleştirilmiş bir birim testi süreci, ekiplerin kod kapsamı, test çalıştırma sayısı, değiştirilmiş kod kapsamı ve performans gibi çeşitli önemli ölçümleri tartışabilmesine katkıda bulunur. Otomatik birim testler, kod değişiklikleri yapıldığında veya yeni özellikler eklendiğinde hızlı bir şekilde çalıştırılabilir ve uygulamanın beklenen davranışını doğrulayabilir.
-
Yüksek Test Kapsamını Hedefleyin
Elbette ki yüzde yüz test kapsamı ve sıfır hata mümkün değildir. Fakat geliştiriciler, bir yazılım uygulamasını mümkün mertebe en eksiksiz ve kapsamlı şekilde test etmeye odaklanmalıdır.
-
Tekrarlanabilir ve Ölçeklenebilir Birim Testleri Yazın
Test stratejinizin başarılı olması için birim testlerinizi tekrarlanabilir ve ölçeklenebilir hale getirin. Uygulama kodunu yazan herkesin, birim testlerini aynı anda yazmasını sağlamak için düzenli bir uygulama yapısı oluşturun. Davranış ve test odaklı programlama gibi yöntemlerle, uygulama kodunu yazmadan önce testleri oluşturmaya çalışın.
Aşağıdaki örnekte, bir alışveriş sepeti uygulamasının birim testlerini içeren bir sınıf (
ShoppingCartTest
) bulunmaktadır. Her bir test metodu, alışveriş sepetinin farklı davranışlarını kontrol eder: - testAddItem: Bir ürün eklenip eklenmediğini kontrol eder.
- testRemoveItem: Bir ürünün doğru bir şekilde kaldırılıp kaldırılmadığını kontrol eder.
- testCalculateTotalPrice_WithDiscount: Bir ürüne bir indirim uygulandığında toplam fiyatın doğru hesaplanıp hesaplanmadığını kontrol eder.
- testCalculateTotalPrice_WithoutDiscount: Bir ürüne herhangi bir indirim uygulanmadığında toplam fiyatın doğru hesaplanıp hesaplanmadığını kontrol eder.
Aşağıdaki test tekrarlanabilir ve ölçeklenebilirdir çünkü:
- Bağımsızlık: Test, diğer testlerden bağımsız çalışabilir.
- Tekrarlanabilirlik: Her seferinde aynı çıkışı (sonuç) üretir.
- Ölçeklenebilirlik: Yeni testler eklenirse veya kod değişiklikleri yapılırsa, bu testlerle birlikte sorunsuz bir şekilde çalışabilir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class ShoppingCartTest {
@Test
public void testAddItem() {
// Arrange
ShoppingCart cart = new ShoppingCart();
// Act
cart.addItem();
// Assert
assertEquals(1, cart.getItemCount());
}
@Test
public void testRemoveItem() {
// Arrange
ShoppingCart cart = new ShoppingCart();
// Act
cart.addItem();
cart.removeItem();
// Assert
assertEquals(0, cart.getItemCount());
}
@Test
public void testCalculateTotalPrice_WithDiscount() {
// Arrange
ShoppingCart cart = new ShoppingCart();
cart.addItem();
DiscountService discountService = new DiscountService();
cart.setDiscountService(discountService);
// Act
double totalPrice = cart.calculateTotalPrice();
// Assert
assertEquals(9.0, totalPrice, 0.001);
}
@Test
public void testCalculateTotalPrice_WithoutDiscount() {
// Arrange
ShoppingCart cart = new ShoppingCart();
cart.addItem();
// Act
double totalPrice = cart.calculateTotalPrice();
// Assert
assertEquals(10.0, totalPrice, 0.001);
}
}
-
Mantıksal Koşulları Kullanmakta Kaçının.
Testlerinizde
while
,for
,switch
veif
gibi mantıksal koşullardan kaçının. Çünkü bu mantıksal koşulları kullanmak testlerinizin içinde bir hataya sebep olma ihtimalini artırır.
Örneğin, aşağıdaki sınıfı test edelim.
1
2
3
4
5
public class MathOperations {
public static int add(int a, int b) {
return a + b;
}
}
Aşağıdaki test, iki sayının toplamını doğrulayan basit bir ifade içerir. Mantıksal koşul içermeyen, doğrudan amacını belirten ve dolayısıyla hata yapma olasılığı daha düşük olan bir testtir. Bu test MathOperations
sınıfı için uygun bir testtir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MathOperationsTest {
@Test
public void testAddition() {
// Arrange
int operand1 = 3;
int operand2 = 5;
// Act
int result = MathOperations.add(operand1, operand2);
// Assert
assertEquals(8, result);
}
}
Aşağıdaki test ise, bir koşula bağlı olarak iki farklı girişi ele alır, ancak testin karmaşıklığını ve dolayısıyla hata yapma olasılığını artırır. Bu test MathOperations
sınıfı için uygun olmayan bir testtir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MathOperationsTest {
@Test
public void testAdditionWithConditionalLogic() {
// Arrange
int operand1 = 3;
int operand2 = 5;
boolean condition = true;
// Act
int result;
if (condition) {
result = MathOperations.add(operand1, operand2);
} else {
result = MathOperations.add(operand1, operand1);
}
// Assert
assertEquals(8, result);
}
}
-
Altyapı bağımlılıklarından kaçının.
Birim testleri oluştururken altyapı bağımlılıklarını eklememeye özen gösterin. Bu bağımlılıklar testleri hem yavaşlatır hem de daha kırılgan hale getirebilir.
Bir dosya işleme sınıfı (
FileProcessor
) olduğunu düşünelim. Bu sınıf, bir dosyadan veri okuyan ve işleyen basit bir işlevselliğe sahip olsun. Bu sınıfa uygun testi yazmaya çalışalım.Aşağıdaki testte, altyapı bağımlılıklarından kaçınma iyi pratiği gözetilerek, gerçek dosya sistemini kullanmaktan kaçınmak için dosya işlemlerini taklit eden bir mock obje kullanılmıştır. Bu sayede testin hızlı çalışması ve sonuçlarının bağımsız olması hedeflenmiştir çünkü gerçek dosya sistemi kullanılmamıştır.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FileProcessorTest {
@Test
public void testReadLinesFromFile() throws IOException {
// Arrange
String filePath = "dummyPath";
List<String> expectedLines = Arrays.asList("Line 1", "Line 2");
// Mock the file processor's behavior
FileProcessor fileProcessor = Mockito.mock(FileProcessor.class);
when(fileProcessor.readLinesFromFile(filePath)).thenReturn(expectedLines);
// Act
List<String> actualLines = fileProcessor.readLinesFromFile(filePath);
// Assert
assertEquals(expectedLines, actualLines);
}
}
-
Beklenen ve beklenmeyen durum testleri yazın.
Bir yazılımda beklenen durumlar olabileceği gibi beklenmeyen durumlar da olabilir. Dolayısıyla test yazarken her iki konu da ele alınmalıdır. Uygulamanın beklenen durumlara karşı testinin yapıldığı gibi, beklenmeyen durumlara karşı nasıl tepki vereceği de test edilmelidir.
Örneğin, bir hesap yönetim sistemi (
AccountManager
) düşünelim. Bu sistem hesap bakiyelerini kontrol etmeyi sağlayan bir işlevselliğe sahip olsun. Bu sistem için beklenen durumlar (pozitif bir bakiye ile doğru bir şekilde para yatırma ve çekme) olabilir. Beklenmeyen durumlar ise (negatif bir miktarda parayla para yatırma veya hesaptan pozitif bakiyeden daha fazla para çekebilme) olabilir.Tüm durumları test eden bir birim testi aşağıdaki gibi olmalıdır:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class AccountManagerTest {
@Test
public void testDeposit() {
// Arrange
AccountManager accountManager = new AccountManager(100);
// Act
accountManager.deposit(50);
// Assert
assertEquals(150, accountManager.getBalance());
}
@Test
public void testWithdraw() {
// Arrange
AccountManager accountManager = new AccountManager(100);
// Act
accountManager.withdraw(50);
// Assert
assertEquals(50, accountManager.getBalance());
}
@Test
public void testDeposit_NegativeAmount() {
// Arrange
AccountManager accountManager = new AccountManager(100);
// Act and Assert
assertThrows(IllegalArgumentException.class, () -> accountManager.deposit(-50));
}
@Test
public void testWithdraw_NegativeAmount() {
// Arrange
AccountManager accountManager = new AccountManager(100);
// Act and Assert
assertThrows(IllegalArgumentException.class, () -> accountManager.withdraw(-50));
}
@Test
public void testWithdraw_InsufficientFunds() {
// Arrange
AccountManager accountManager = new AccountManager(100);
// Act and Assert
assertThrows(IllegalStateException.class, () -> accountManager.withdraw(150));
}
}
Sonuç olarak beklenen durumları test eden iki test (‘testDeposit ve ‘testWithDraw’) ve beklenmeyen durumları kontrol eden üç test (‘testDeposit_NegativeAmount, ‘testWithdraw_NegativeAmount, ‘testWithdraw_InsufficientFunds) bulunmaktadır. Beklenen durum testleri başarılı olmalıdır ancak beklenmeyen durum testleri ilgili hataları fırlatmalıdır. Bu sayede, uygulamanın beklenmeyen durumlara karşı nasıl tepki vereceği test edilebilir.
-
Test verilerini yönetin.
Birim testleri için kullanılan veriler ayrı olarak yönetilmeli ve testlerin tekrarlanabilirliği sağlanmalıdır. Değişken veri setleri kullanarak farklı senaryoları test etmek önemlidir. Farklı senaryoları test etmek için değişken veri setleri kullanmak, uygulamanın farklı durumlara nasıl tepki vereceğini daha iyi anlamamıza yardımcı olur.
Örneğin, bir matematik işlemleri sınıfımızın olduğunu varsayalım. Bu sınıftaki toplama metodu için birim testi yazalım.
Toplama metodunun farklı senaryolarını da test eden bir birim testi aşağıdaki gibi olmalıdır:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@RunWith(Parameterized.class)
public class MathOperationsTest {
private final int operand1;
private final int operand2;
private final int expectedResult;
public MathOperationsTest(int operand1, int operand2, int expectedResult) {
this.operand1 = operand1;
this.operand2 = operand2;
this.expectedResult = expectedResult;
}
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{1, 1, 2},
{2, 3, 5},
{-1, 1, 0},
{0, 0, 0},
{-5, -3, -8}
});
}
@Test
public void testAdd() {
// Arrange
MathOperations mathOperations = new MathOperations();
// Act
int result = mathOperations.add(operand1, operand2);
// Assert
assertEquals(expectedResult, result);
}
}
Bu testte, MathOperationsTest
sınıfında toplama metodunun farklı senaryolarını test etmek için JUnit’in Parameterized
özelliği kullanılmaktadır. data
metodu, farklı test senaryolarına veri sağlayan bir veri seti döndürür. Her bir test senaryosu için bir test çalıştırılır ve beklenen sonuçlarla gerçek sonuçlar karşılaştırılır.
-
Veri temizliğine ve geri dönüşe (başlangıç durumuna dönüş) dikkat edin.
Birim testleri çalıştırdıktan sonra, kullanılan kaynakları temizlemek ve başlangıç durumuna geri dönmek önemlidir. Bu testlerin tekrar tekrar yürütülebilirliğini artırır.
Örneğin, bir kullanıcı yönetimi sistemi düşünelim, kullanıcıların bir liste halinde tutulduğunu varsayalım.
İlgili sistemi test eden ve kullanılan kaynakları temizleyerek başlangıç durumuna geri dönen bir birim testi aşağıdaki gibi olabilir:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class UserManagerTest {
private UserManager userManager;
@Before
public void setUp() {
userManager = new UserManager();
userManager.addUser("user1");
userManager.addUser("user2");
// İlk durumu hazırla
}
@After
public void tearDown() {
// Testlerden sonra kullanılan kaynakları temizle
userManager = null;
}
@Test
public void testAddUser() {
// Act
userManager.addUser("user3");
// Assert
List<String> users = userManager.getUsers();
assertEquals(3, users.size());
}
@Test
public void testGetUsers() {
// Act
List<String> users = userManager.getUsers();
// Assert
assertEquals(2, users.size());
}
}
Bu testte, setUp
metodu testin başlangıç durumunu hazırlar ve tearDown
metodu testlerin sonunda kullanılan kaynakları temizler. setUp
ve tearDown
metotları JUnit tarafından otomatik olarak çağırılır ve bu sayede her bir testin bağımsız çalışma ortamına sahip olması sağlanır.
-
Performans ve verimlilik kontrolü yapın.
Birim testler sadece doğruluk üzerine değil, aynı zamanda performans ve verimlilik üzerine de odaklanmalıdır. Özellikle büyük projelerde, işlemlerin belirlenmiş süreler içinde tamamlanıp tamamlanmadığını kontrol etmek önemlidir.
Örneğin, bir listenin her bir elemanını iki katına çıkaran bir sınıfımız olduğunu varsayalım. Bu işlemi birçok kez gerçekleştirmemiz gerektiğini düşünelim. Performans kontrolü yapabilmek adına bir birim testi yazalım.
Aşağıda yazdığımız testte,
testMultiplyByTwo_Performance
metodu belirli bir boyuttaki liste üzerinde bir işlemin belirlenen süre içinde tamamlanıp tamamlanmadığını kontrol eder.timeout
parametresi, bu testin maksimum 1 saniye içinde tamamlanmasını sağlar.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ListMultiplierTest {
private static final int LIST_SIZE = 1000000; // 1 milyon elemanlı liste
@Test(timeout = 1000) // Maksimum 1 saniyede tamamlanmalı
public void testMultiplyByTwo_Performance() {
// Arrange
List<Integer> numbers = createLargeList();
// Act
ListMultiplier listMultiplier = new ListMultiplier();
listMultiplier.multiplyByTwo(numbers);
}
private List<Integer> createLargeList() {
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < LIST_SIZE; i++) {
numbers.add(i);
}
return numbers;
}
}
7. Yazılım Dünyasında Birim Testi Yazımına Yönelik Uyguladığımız Ankete Dair Sonuçlar
Verimli bir birim testi nasıl yazılır ve nelere dikkat etmeliyiz gibi sorular referans alınarak başlattığımız çalışmada yazılım ekosisteminin mevcut durumunu da sade bir yaklaşım ile incelemek için her biri farklı sektörlerde, farklı kurumlarda, farklı rollerde, yazılım alanında çalışan katılımcılara bir anket uyguladık.
Bu anket ile kurumların yazılım geliştirme süreçlerinde birim testi alışkanlıklarını kendi odağımızda tespit etmeye çalıştık. Anketteki tüm sorulara yönelik yanıtları burada belirtmemiz mümkün olmadığı için en kritik sorulara gelen yanıtları analiz etmeye çalıştık.
Soru 1) Birim (unit) test yazmak yazılım geliştirme sürecinizin bir parçası olarak kabul edilir mi?
Bu soruya gelen cevap sevindirici. Birim test yazmak ağırlıklı olarak yazılım geliştirme sürecinin bir parçası görülüyor.

Şekil 5. Soru 1 anket cevapları
Soru 2) Projelerinizde birim (unit) testleri hangi aşamada yazıyorsunuz?
Bu soruya gelen cevap ile de tam bir birliğin sağlanamadığını görüyoruz. Hiç birim testi yazmıyoruz diyen katılımcıların olduğu gibi, geliştirme öncesinde/sonrasında/sırasında birim testi yazıldığını söyleyen katılımcılar da mevcut. Elbette ki en ideali geliştirme öncesinde birim testi yazmaktır. Bu mümkün değilse de kesinlikle geliştirme sırasında birim testi yazılmasını tavsiye ediyoruz.

Şekil 6. Soru 2 anket cevapları
Soru 3) Firmanızda birim (unit) testlerle ilgili eğitimler ve bilgilendirme oturumları düzenleniyor mu?
Bu soruya verilen yanıtlar ise üzücü. Katılımcılar ağırlıklı olarak birim testlerle ilgili eğitimlerin ve bilgilendirme oturumlarının hiç düzenlenmediğini belirtiyor. Bilgilendirme oturumları için kurumda/firmada bu konu üzerinde çalışan bir çalışma grubu veya sorumlu kişilerin olmasını tavsiye ediyoruz. Bu konudaki eğitimlerin özellikle yeni başlamış personeller öncelikli olmak üzere tüm personele planlanmasını öneriyoruz.

Şekil 7. Soru 3 anket cevapları
Soru 4) Birim (unit) testlerin, kod kalitesi üzerindeki etkisini nasıl değerlendiriyorsunuz?
Bu soruya verilen yanıtlar sevindirici. Çünkü katılımcılar ağırlıklı olarak birim testlerin kod kalitesi üzerindeki etkisi konusunda farkındalık sahibi.

Şekil 8. Soru 4 anket cevapları
Soru 5) Test odaklı geliştirme (TDD) yaklaşımını ne derece kullanıyorsunuz ve bu yaklaşımın projeniz üzerindeki etkileri nelerdir?
Bu soruya verilen yanıtlar analiz edildiğinde TDD yaklaşımını uygulayan sınırlı sayıda katılımcının olduğu ve ağırlıklı olarak katılımcıların projedeki ihtiyaca binaen bu yaklaşımı tercih ettiği görülmektedir.

Şekil 9. Soru 5 anket cevapları
Soru 6) Projenizde birim (unit) test kapsama(coverage) oranınız nedir?
Ağırlıklı olarak birim(unit) test kapsama oranının %30-%50 arasına sıkıştığını gözlemliyoruz. Ancak %30’ın altında kapsama oranına sahip olan ve bu oranı hiç ölçmeyen katılımcıların sayısı da düşük değil.

Şekil 10. Soru 6 anket cevapları
Soru 7) Birim (unit) testlerinizi kod gözden geçirme (code review) süreçlerine ne sıklıkla dahil ediyorsunuz?
Yanıtlar analiz edildiğinde, ağırlıklı olarak süreçlere dahil edilmediği veya nadiren edildiği görülmektedir.
Birim testi yazımı, yazılım geliştirme sürecinin bir parçası ise, birim testler de kaynak kodun bir parçasıdır. Dolayısıyla uygulama kodundan bir farkı yoktur. Bu bağlamda kod gözden geçirme süreçlerine her zaman uygulama kodu ile beraber dahil edilmesini tavsiye ediyoruz.

Şekil 11. Soru 7 anket cevapları
Soru 8) Birim (unit) testleriniz Sürekli Entegrasyon (CI – Continious Integration) sistemlerine entegre bir şekilde çalışıyor mu?
Yanıtlar incelendiğinde katılımcıların yarısı entegre bir şekilde çalıştığını belirtmiştir. Katılımcıların bir kısmı entegre etmeyi düşünmediğini belirtirken bir kısmı da entegre etmeyi düşündüğünü belirtmiştir. Bir kısmı ise manuel tetiklediğini belirtmiştir. Sonuç olarak katılımcıların yarısı sürekli entegrasyon sistemlerine entegre iken, diğer yarısı ise entegre değildir. Yazımızda vurguladığımız üzere hem süreklilik hem hata tespit verimliliğini artırmak adına hem de otomatikleştirme iyi pratiği referansı altında birim testlerinin sürekli entegrasyon sistemine entegre bir şekilde çalışacak şekilde yapılandırma yapılmasını tavsiye ediyoruz.

Şekil 12. Soru 8 anket cevapları
Soru 9) Birim (unit) testleriniz için hangi aracı kullanıyorsunuz?
Yanıtlar beklentimizi karşılamakta. Çoğunlukla JUnit aracının tercih edildiği Şekil 13’de görülmektedir.

Şekil 13. Soru 9 anket cevapları
Soru 10) Firmanızda birim (unit) testler yardımıyla yakaladığınız hataları işaretliyor musunuz?
Yanıtlar incelendiğinde ağırlıklı olarak işaretlenmediği Şekil 14’de görülmektedir. Birim testlerin etkisinin ölçümü açısından işaretlenmesini tavsiye ediyoruz.

Şekil 14. Soru 10 anket cevapları
Soru 11) Birim testlerinizi yazarken test verilerinizi oluşturma stratejiniz nedir?
Bu konuda verilen yanıtlar analiz edildiğinde test verilerini oluşturmak üzere çeşitli yöntemlerin kullanıldığı ve bir farkındalığın olduğu Şekil 15’de görülmektedir.

Şekil 15. Soru 11 anket cevapları
8. SONUÇ
Sonuç olarak birim testleri, yazılım geliştirme sürecinin kritik bir bileşenidir ve yüksek kaliteli yazılım ürünlerinin geliştirilmesinde önemli bir role sahiptir. İyi tasarlanmış birim testleri, sistemlerin daha sağlam ve sürdürülebilir olmasını sağlar. Bu testler, fonksiyonel hataların erken evrelerde tespit edilmesini, regresyon hatalarının önlenmesini ve kod refaktörünün güvenli bir şekilde yapılmasını mümkün kılar.
Birim testlerinin etkili şekilde kullanılması, yazılımın modülerliğini ve tekrar kullanılabilirliğini artırır, geliştirme sürecindeki hataların düzeltilme maliyetini azaltır. Test Odaklı Geliştirme (TDD) gibi metodolojiler, yazılımın tasarımını ve yapısını optimize ederek geliştiricilere sürekli bir geri bildirim döngüsü sağlar. Ayrıca, birim test çerçevelerinin ve araçlarının stratejik seçimi, test süreçlerinin etkinliğini önemli ölçüde artırabilir. Bu araçlar, geliştirme sürecinin çeşitli aşamalarında kodun bütünlüğünü korumaya yardımcı olurken, Sürekli Entegrasyon (CI) ortamlarında otomatik testlerin rahatlıkla uygulanabilmesini sağlar.
Birim test yazımında karşılaşılan sık hatalardan kaçınmak ve en iyi pratikleri uygulamak, kodun sürekli olarak gözden geçirilmesini ve iyileştirilmesini gerektirir. Geliştiriciler, kodun yeniden kullanılabilirliğini ve okunabilirliğini artıracak şekilde testleri stratejik olarak planlamalı ve uygulamalıdır. Bu yaklaşım, yazılımın yaşam döngüsü boyunca performansı ve güvenilirliği sürdürmek için esastır.
Daha detaylı bilgi için Birim Testleri Kılavuzu dosyasını inceleyebilirsiniz.
Yazımızın teknik gözden geçirmesi için Anıl Özberk ve İbrahim Özcan’a, editör desteği için ise Kübra Ertürk’e teşekkür ederiz.
9. KAYNAKÇA
- Unit Testing Principles, Practices and Patterns, Vladimir Khorikov
- Yazılım Geliştirme Süreçlerinde Birim Test Alışkanlıkları Anketi - Test Tacticians Teknoloji Birliği
- Unit Testing Best Practices - Brightsec
- Unit Testing Best Practices - Microsoft
- What is Unit Testing? - Spiceworks
- Test-Driven Development - Wikipedia
- Test-Driven Development - Test Driven