Golang: Unit Test ve Dependency Injection

Arda Coşar
Modanisa Engineering
6 min readDec 25, 2021

Selamlar,

Unit test yazarken karşılaşabileceğiniz bazı problemler vardır. Bu yazıda test yazmanıza engel olan en can sıkıcı problemlerden birisine, bağımlılığa değineceğiz. Oluşturacağımız örnekte çok fazla konuyu karıştırmadan ve gereksiz ayrıntılara girmeden olabilecek en basit haliyle işin mantığına odaklanmaya çalışacağız

Bu yazıyı net olarak anlayabilmek için struct ve interface yapısını kavramış olmanız gerekir. Eğer bu konularda eksiğiniz olduğunu düşünüyorsanız önce ona odaklanmanızı öneriyorum.

Öncelikle bağımlılığı daha net anlamanız için küçük bir senaryo kuralım ve onun üzerinden ilerleyelim. Örneğimiz kısaca şöyle olsun

- Elimizde bir hayvan barınağı var.

- Kafa karıştırmamak adına barınakta sadece bir hayvan olduğunu varsayıyoruz

- Bu barınaktaki hayvanı veterinerimize götürüp sağlık kontrolüne sokacağız

- Gerekli kontrolleri yaptıktan sonra sonucu ekrana yazdıracağız

Şimdi adım adım ilerleyecek, hata yaptıkça sorgulayacak ve gelmek istediğimiz noktaya varacağız. Kodları tek tek açıklamak için parça parça koyacağım, şimdilik yazmak yerine anlamaya odaklanmanızı istiyorum. Öncelikle barınak yapımızda kullanmak için işimizi görecek basit bir hayvan yapısı kuralım.

Hayvan Yapısı

Şimdi ise hayvanımızın kalacağı barınağımızın yapısını kuralım.

Barınak Yapısı

Örneğimizde kullanabilmek için programımız başlatıldığı anda, yukarıda görebileceğiniz gibi global olarak tanımladığımız barınak1 değişkenimize içindeki hayvan yapısını dolduran fonksiyonu yazalım.

In-Memory Veritabanı

Ve son olarak ileride kullanmak üzere barinak Struct’ımıza ait, barınaktan hayvanı teslim almak için kullanacağımız fonksiyonu yazalım.

Taşıyıcı

Evet, gerekli hazırlıklar yapıldı. Şimdi sağlık kontrolü yapacağımız fonksiyonumuzu ilk haliyle oluşturalım.

Kontrol Fonksiyonu

Artık asıl dersimize geçmemiz için main bloğu içerisinde son dokunuşları yapabiliriz.

Programın Ana Akışı

Özetleyecek olursak:

  • Canlılarımızı temsil etmesi için hayvan adında bir struct oluşturduk
  • İçinde hayvan değişkeni olan, barinak adında bir struct oluşturduk
  • Program başında barinak1 adıyla oluşturduğumuz global değişkeni, işlem yapabilmek için dolu bir barınak haline getirdik
  • Main bloğu içerisinde gerekli program akışını sağladık

Şu an programımızı çalıştırdığımızda barınaktaki hayvanı getirtip sağlık kontrolüne sokuyoruz. Eğer sağlık değişkeni İyi ise “sağlıklı”, değil ise “sağlıksız” değerini döndürüp ekrana yazdırıyoruz. Buraya kadar anladıysak hadi biraz test yazalım.

Unit test dediğimiz şey bir birimi test eden yapıdır. Biz bir fonksiyona unit test yazdığımızda tek bir işi test ediyor olmalıyız, yani bir fonksiyon tek bir işe odaklanmalıdır. Bu yüzden bağımlılıklarımızı doğru yönetmeliyiz. Yapmazsak ne mi olur? Gelin sağlık kontrolü fonksiyonumuz için unit test yazarken ne gibi sıkıntılar yaşadığımızı gözlemleyelim.

Unit Test Denemesi

Her şeyin beklediğimiz gibi olduğu durumda testimiz güzelce çalışacaktır fakat bilirsiniz ki evdeki hesap çarşıya uymaz. Sizce bu test, fonksiyonu mu test ediyor? yoksa sadece sağlıklı değeri gelip gelmediğini mi? Bu test için oldukça problemli bir test diyebiliriz. Şöyle ki problemlerden birisi hayvanımızın sağlık değerinin “İyi” dışında başka bir değer olması testi patlatacaktır. Yazdığımız SagligiKontrolEt() fonksiyonu doğru çalışacak ve “Sağlıksız” değerini döndürecek fakat testimiz fonksiyonun doğru çalıştığını fark edemeyecektir. Bizim bu fonksiyonu doğru test edebilmemiz için barınak1'den gelen değeri yazdığımız teste göre manipüle edebiliyor olmamız gerekiyor.

Unit Test Denemesi 2

Bu bana test yazmam için daha fazla oyun alanı sağlıyor. Yani artık tam tersi durumu da test edebilirim. Tek yapmam gereken sağlık değişkenine gelmesini istediğim değeri yazıp test etmek istediğim fonksiyonun davranışını gözlemlemek. İhtiyacım olmayan değerleri doldurmama gerek bile yok ama siz ihtiyacınıza göre istediğiniz yerlere istediğiniz veriyi yerleştirebilirsiniz.

Hadi gelin testimizi biraz daha kapsamlı yapalım

Unit Test Denemesi 3

Artık Fonksiyonumuzun iki ihtimali de test ediliyor. İşte şimdi biraz daha unit test’e benzedi. Sizce bitti mi? Maalesef bağımlılıklarımız henüz bitmiş değil. Hadi HayvanıGetir() fonksiyonumuzda bir sıkıntı olduğunu varsayalım. Bakalım testimiz nasıl etkileniyor.

Hayvanı Getir Sabote Edildi

Şimdilik kendimiz bir hata dönmeye zorladık. Çünkü örneğimizin karışık olmaması için gerçekten hata çıkarabilecek bir fonksiyon yazmadık. Önemli olan bu fonksiyonda hata olduğunda başımıza ne geleceği…

Yanlış Test Sonucu

Sizce az önce sağlığı doğru ölçen fonksiyonumuz, başka bir fonksiyonu değiştirdiğimiz için yanlış ölçüm yapmalı mı? Bence de hayır. Bu yüzden SagligiKontrolEt() fonksiyonumuzun, bir diğer fonksiyon olan HayvanıGetir() fonksiyonuna olan bağımlılığını tespit edip kurtulmamız gerekiyor. Çünkü biz sadece tek bir iş yapan bir fonksiyonu test etmeye çalışıyoruz.

Kontrol Fonksiyonu

Göreceğiniz üzere SagligiKontrolEt() fonksiyonumuzda HayvaniGetir() fonksiyonu bize sıkıntı çıkarıyor. İşte burada bizi kurtarmak için Bağımlılık Enjeksiyonu devreye giriyor. Bize sıkıntı çıkaran HayvanıGetir() fonksiyonu barınak yapısının bir fonksiyonu olduğu için bizim barınak yapısını taklit eden, istediğimiz gibi yönlendirebileceğimiz bir yapıya ihtiyacımız var.

Bizim oluşturduğumuz taklit barınağı şu an fonksiyonumuza yutturamayız. Çünkü barınak değişkenine içeriden bir bağımlılık var. Bizim bağımlılığımız olan barınak yapımızı, düzeltmek istediğimiz SagligiKontrolEt() fonksiyonuna dışarıdan enjekte etmemiz gerekiyor. İşte bu noktada da interface devreye giriyor.

İnterface’leri konudan sapmamak için uzun uzun anlatmayacağım elbette ancak yinede interface bilmeden yazının burasına kadar gelip yolculuğunu bitirmek istemeyenler için anlaşılır bir açıklama yapmaya çalışacağım.

Şöyle ki interface’i bir meslek olarak düşünün. Mesela ressamlık mesleğini örnek gösterelim. Structları ise ressamlar olarak düşünebilirsiniz. Ressamlar, Ressamlığın gereksinimlerini yerine getirir. Structlar da eğer uymak istiyorlarsa İnterfacelerin gereksinimlerine uyarlar. Yani ressamların hepsi çizim yapıyor ama her birinin MuhteşemÇiz() fonksiyonunun içi farklı şekillenmiştir. Hepsi MuhteşemÇiz() fonksiyonuna sahip olduğu için de birbirinden farklı olan bu insanlara ressam diyebiliriz ve bir odaya sadece ressamlar girebilir dediğimizde o odaya birbirinden farklı işler yapabilen ama aynı interface’e uyan structları alabiliriz.

Şimdi bunu nasıl kullanacağız? Şöyleki içerisinde HayvanıGetir() fonksiyonu bulundurma şartı olan Barınak adında bir interface oluşturup buna uyan 2 adet struct kuracağız.

Barınak Arayüzü

Zaten hali hazırda kurduğumuz barinak yapımız bu interface’in gereksinimlerini karşıladığı için artık Barınak interface’inden parametre isteyen her yerde kullanılabilir durumdadır. Bizim ise istediğimiz gibi şekillendirebileceğimiz taklit bir Barınağa ihtiyacımız var. Onu da şu şekilde yazabiliriz.

Sahte Barınak Yapısı

Farkındaysanız yapının içinde tanımladığım değişken aynı değil. Yapılarınızın içi aynı olmak zorunda değil. Değerleri de istediğiniz gibi verebilirsiniz. Benim Barınak kabul edilmem için HayvanıGetir() fonksiyonuna ihtiyacım vardı ve bunu karşılıyorum. Şu andan itibaren HayvanıGetir() fonksiyonunu istediğim gibi manipüle edebilirim. O yüzden artık SagligiKontrolEt() fonksiyonumuza dönüp bağımlılık enjeksiyonuna uygun bir hale getirebiliriz. Parametre olarak Barınak interface’ine uyan bir argüman isteyeceğiz. Normal akışta buraya gerçek barınak yapımızı, test yaparken ise sahte barınağımızı göndereceğiz.

Kontrol Fonksiyonu Son

Fonksiyonumuz artık interface parametresi istediği için yapılarımızı direkt göndermek yerine & işareti kullanarak onların referanslarını gönderiyoruz. Ana akışı uyarladık, sıra testimize geldi. Burada programın ana akışının aksine, test kodlarımızda sahteBarınak yapımızdan türemiş bir değişkenin referansını göndereceğiz.

Unit Test Son

Artık gerçek HayvanıGetir() fonksiyonunda hata olup olmaması benim SagligiKontrolEt() fonksiyonumun testini etkilemeyecek. Çünkü Gerçek fonksiyonda ne kadar dallanırsa dallansın, ne kadar sıkıntılı bir yoldan geçerse geçsin bir önemi yok. Ben testlerimde direkt olarak istediğim değer gelmiş gibi davranmasını sağlayacağım.

Sonuç olarak bir fonksiyona gerçekten unit test yazılabilmesi için bağımlılıklarının yönetilebilir bir şekilde yazılmış olması gerekiyor. Bu örnekte SOLID’in Dependency Inversion prensibine uymak için kullanabileceğiniz Dependency Injection tekniğini size göstermek istedim. Bundan sonra test edilebilir fonksiyonlar yazabilmeniz dileğiyle…

--

--