Modern Entegrasyon Testleri: Testcontainers ve Docker

Süleyman Fazıl Yeşil
7 min readApr 24, 2022

--

Kaynak: Testcontainers

İnsan etkilenen bir varlıktır. Çalıştığı yerlerden ve kültürlerden etkilenir, yaşadığı çevreden etkilenir, birlikte çalıştığı kişilerden etkilenir, okuyup izlediklerinden etkilenir. Bu etkilerin toplamına kendisi de bir yorum katar. Böylelikle bakış açısı ve perspektifi, doğru ve yanlışa dair ön kabülleri, problemlere ve olaylara yaklaşım tarzı, çalışma biçimi ve alışkanlıkları şekillenir.

İnsan aynı zamanda etkileyen bir varlıktır. Çalışma biçimini ve alışkanlıklarını gittiği yerlerde de sürdürme eğilimi taşır. Prensip ve pratiklerini uygulamaya çalışır. Böylelikle çevresini etkiler.

Farklı kültürlerden gelen insanların bir arada çalışması ve etkileşimi ise zenginlik yaratır. Farklı bakış açılarının bir arada uyumlu çalışabilmesinin altındaki zemini ise analitik düşünme sağlar. Bu bizim ne işimize yarayacak, faydası ne maliyeti ne diye sormak bizi pragmatik ve fayda odaklı olmaya iter, salt kişisel ezberleri tekrar etmeyi mümkün olduğunca engeller. Her bir şirket yeni bir bağlam demektir ve her bağlam tamamen kendine özgü nitelikler taşır. Evrensel prensipler vardır tabi ki de, fakat pratikler koşullara bağlıdır.

Yakın bir zamanda farklı veri tabanlarının desteklendiği bir uygulama için entegrasyon testlerini yapma ihtiyacı ortaya çıkmıştı. Uygulama belli başlı ilişkisel veri tabanlarını destekliyordu ve aynı zamanda belirli veri tabanları için özelleştirilmiş sorguları da içeriyordu. Şema değişikliği gerektiğinde ilgili DDL sorgusu ekleniyor ve test ediliyordu. Fakat manuel olarak DDL scriptlerini çalıştırmak ve desteklenen veri tabanlarından herhangi birinde hata alınıp alınmadığını kontrol etmek zaman alıyordu.

Çözüm açık ve bilindikti aslında, iyi mühendislik kültürlerinde uygulanagelen bir çözüm. Manuel kontroller yerine otomatik olarak çalışacak testler eklemek. Bir kere yaz bin kere çalıştır.

İhtiyaç belli, çözümün adımları da gayet netti:

  • Desteklenen her bir veri tabanı için boş bir ilişkisel veri tabanı (Oracle/SQLServer/PostgreSQL/MySQL vb.) oluştur
  • Tarihsel olarak tüm DDL scriptlerini çalıştır
  • Hata var mı kontrol et, istersen ek kontroller yap

Çözümün ne olduğu belliydi belli olmasına fakat çözümün nasıl uygulanacağı konusunun da netleştirilmesi gerekiyordu.

Daha önceleri çalıştığım bir firmada bu işler için şirket içinde Devops ekibince geliştirilmiş Docker konteynır tabanlı özel bir altyapı bulunuyordu. Veri tabanı entegrasyonu gerektiren testler için test koşumunun başında veri tabanı ve ilgili diğer Kafka, Redis vb. yapılar konteynır içinde ayağa kadırılıyor, testler koşuyor ve ardından da kapatılıyordu. Mikroservis dönüşümü kapsamında .NET Core henüz piyasada olmadığı ve Java da şirket içinde tercih edilmek istenmediğinden Golang dili tercih edilmişti. O sıralar Golang Türkiye’de bildiğim kadarıyla henüz kullanılmıyordu. Şirkette konteynır altyapıları kullanılmaya başlamıştı, oturmuş bir yaygınlaştırma sistemi, CI/CD süreçleri ve PAAS platformu vardı.

Şimdiyse bu işler için hazır birtakım araçlar bulmak gerekiyordu. Neyse ki testler için kullanılacak altyapılar ve araçlar konusunda yardıma Testcontainers, Docker ve JUnit 5 yetişti. Günümüzün modern uygulama ekosisteminde artık birçok şeyi yapmak kolaylaştı. Neredeyse aklınıza gelen herhangi bir şey için hazır bir araç bulunabiliyor, yeter ki çözülecek bir probleminiz olsun ya da problemleri problem edinmeyi üstünüze vazife edinin.

Test Piramidi ve Entegrasyon Testlerinin Yeri

Her yere uçakla gitmezsiniz, trenle, gemiyle, arabayla, bisikletle veya yürüyerek de gitmezsiniz. Hepsinin bir yeri vardır. Mesafe, zaman, maliyet ve diğer birçok şeye göre karar verirsiniz hangi ulaşım aracını kullanacağınızı. Hepsinin bir artısı eksisi vardır. Seçenekler arasındaki fark önemsizleştikçe tercihler de önemsizleşir, üzerinde konuşmaya ve düşünmeye değer olmaktan çıkarlar.

Bu testler konusunda da böyledir. Farklı tiplerde, farklı kapsamlarda testler yazabiliriz. Fakat her bir test tipinin artısı eksisi, getirisi ve götürüsü olacaktır. Bu sebeple ihtiyaca göre seçim yapmamız gerekir.

Testlerin sınıflandırılması konusu biraz tartışmalı ve nereden baktığınıza göre değişebilir olsa da, anlamayı ve üzerinde konuşmayı kolaylaştıran, belirli prensipler sağlayan ünlü bir sınıflandırma yöntemi var: Mike Cohn’un ortaya attığı Test Piramidi kavramı. Bu yaklaşımda testler; parçaları test eden birim testler, apiyi veya servisi test eden servis testleri ve kullanıcı arayüzünden yapılan ara yüz testleri olmak üzere üç sınıfta ele alınır.

Resim: Test Piramidi, Kaynak: Sevgili Google

Test dublörü (mock, spy, stub, dummy, fake) kullanıp izole testler yazabileceğiniz gibi, uygulamayı bağımlılıklarıyla birlikte de test edebilirsiniz. İçeriden dışarıya doğru çıktıkça kapsam artar, baktığınız kütle büyüdükçe testler yavaşlar, çalışma süreleri, karmaşıklık, kombinasyonlar ve test yazmanın maliyeti artar. Kapsam arttıkça, farklı uygulamalarla entegre ettikçe de dikkat edilmezse kırılganlık da artar. Fakat diğer taraftan uygulama parçalarının bir arada uyumlu çalışıp çalışmadığı konusundaki güvenimiz de artar. Arada dengeyi bulmak, hangi kapsamda ne kadar test yazacağımıza karar vermek de ihtiyaçlarımız çerçevesinde bize düşer.

Yeri gelmişken biri testlere ve önemine diğeri sınıflandırması, detayları ve nasıl yazılması gerektiği konusuna değinen iki makaleyi okumanızı tavsiye ederim.

Testcontainers ve Docker

Testcontainers projesi başlangıçta Java dili için geliştirilmiş olsa da şimdilerde birçok dili destekliyor ve farklı diller için hazır SDK’ları bulunuyor. Kullanıcı kitlesi oldukça geniş.

Kaynak: testcontainers (github.com)

Java için konuşacak olursak, kütüphane oldukça gelişmiş durumda. Bir uygulama mimarisinde yer alabilecek birçok bileşen için hazır yapılar mevcut. Docker Hub veya özel repolardaki Docker imajları kullanılabileceği gibi, testin içinde çalışma anında da (on-the-fly) imaj oluşturup testte o imajdan konteynır yaratabiliyorsunuz. Dockerfile veya docker-compose.yaml dosyalarınız varsa onları da kullanabiliyorsunuz. Konteynır yaşam döngüsündeki birçok şeyi SDK üzerinden yönetebiliyorsunuz. Dışarıdan dosya veya dizin ekleyebiliyor, komut çalıştırabiliyorsunuz, loglara erişebiliyorsunuz.

Özellik bakımından ne kadar zenginse, kullanımı da bir o kadar kolay. Son bölümde yer verilen kod örneği de bu konuda net bir fikir verecektir.

Kaynak: Testcontainers

JUnit 5

Çalıştığım farklı şirketlerde Java ve JUnit 4 ile test yazdıktan sonra uzunca bir süre Golang’da Ginkgo ve Gomega, JS’de de Jasmine ile birim test yazmıştım. JUnit 4 XUnit stiline sahipti. Ginkgo ve Jasmine ise BDD (Behavior Driven Development) stiline sahipti ve kıyas kabul etmeyecek şekilde açık ara çok daha estetikti. BDD stili derken de, testleri uygulama davranışını, hangi durumda nasıl davrandığını normal bir insana tarif eder gibi anlaşılır şekilde yazmayı kastediyoruz.

Golang ve Ginkgo ile BDD stiliyle yazılmış bir test

Yeniden JUnit ve XUnit stiliyle test yazacak olmak canımı sıkıyordu. Farklı bir DSL sunan Spock benzeri bir kütüphane kullanılabilirdi fakat aynı anda tek bir şey öğrenme prensibi gereği ortaya hem birim testleri hem de farklı yeni bir dili öğrenme zorunluluğu koymak işleri zorlaştıracaktı.

Neyse ki Junit5 ile gelen özellikler sayesinde, biraz da editörün yardımıyla kolay bir şekilde BDD stiline yakınsayan tarzda testler yazmak mümkün hale geldi.

JUnit 4 ile test yazmak — ıyykk…
JUnit 4 ve XUnit stiliyle yazılmış bir test
JUnit 5 ve BDD stiliyle yazılmış bir test
JUnit 5 ile daha temiz, okunaklı, anlaşılır ve gruplanmış testler

Testcontainers ile Basit Bir PostgreSQL Entegrasyon Testi

PostgreSQL entegrasyon testi örneğinde Java 8, Maven, Junit 5, Docker ve Testcontainers teknolojileri kullanıldı.

Adım 1: Testcontainers ve JUnit 5 kütüphanelerini ekliyoruz. Kullanılan driver, logger vb. diğer kütüphaneler ikincil önemde olduğundan burada göz ardı ediyoruz.

Adım 2: PostgreSQL konteynırı ayağa kalktığında otomatik çalışacak olan veri tabanı hazırlama scriptini oluşturuyoruz. Bu bir demo uygulama olduğundan, GIANTS isimli bir tablo oluşturup içine iki kayıt ekliyoruz.

Adım 3: PostgreSQL imaj ismi, veri tabanı kullanıcı ismi ve şifresi gibi sabit değişkenlerimizi tanımlıyoruz ve ardından PostgreSQL konteynırını yaratıyoruz. postgres-init.sql veri tabanı hazırlık scriptini konteynırın içine belirtilen dizine kopyalıyoruz ve gerisini konteynıra bırakıyoruz. Peşinden konteynırın çalıştığını teyit amaçlı basit bir test ekliyoruz.

Adım 4: Konteynır loglarında “database system is ready” şeklinde bir log olması gerektiğini belirten bir test yazıyoruz. Hemen peşinden loglarda hata logu olmadığından emin olmak için, hiç “ERROR” metni olmaması gerektiğini belirten bir test ekliyoruz. Bu testler tamamen demo amaçlı, gerçek dünyada ihtiyacımız ne ise o şekilde test ekleyebiliriz.

Adım 5: GIANTS tablosunda iki kayıt olması gerektiğini belirten bir test ekliyoruz ve böylelikle testleri sonlandırıyoruz.

Bitirirken

Görüldüğü gibi bir işi yapmak ancak bu kadar kolay olabilirdi. Docker konteynır altyapısı ve onun üzerinde çalışan Testcontainers kütüphanesi büyük bir iş başarıp bir testin içerisinde sıfırdan bir veri tabanını ayağa kaldırıp hazırlamayı üç beş satır koda indirgemiş durumda.

Yine de bir ikazla bitirmek isterim. Entegrasyon testlerinin çalışması genel olarak daha uzun sürüyor. Gerçek hayatta veri tabanını hazırlamak buradaki kadar kolay olmuyor bu da veri tabanına entegre olan testler için hazırlıkları karmaşık, efor ve zaman alıcı yapıyor. Kapsam arttıkça kombinasyonlar artıyor. İstisnai durumları test etmek zorlaşıyor. Testler her bakımdan daha maliyetli oluyor.

Bu noktada test piramidi bize testlere yaklaşım konusunda sağlam bir perspektif veriyor. Hep kırmızı hapı içmek yerine, hastalığımız neyse onun hapını içmek dileğiyle.

Kaynaklar

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Süleyman Fazıl Yeşil
Süleyman Fazıl Yeşil

No responses yet

Write a response