TestContainers
Entegrasyon testleri yazı serisinin 4. bölümünde open source bir proje olan TestContainers‘ı tanıtmak istiyorum.
Serinin önceki yazılarını okumak isterseniz:
TestContainers, JUnit ve Docker kullanarak entegrasyon testleri yazmanızı sağlayan bir java kütüphanesidir. JUnit class ve method rule’ları ile, testlerden önce container’ları başlatıp testler bitince container’ları temizler.
TestContainers, docker daemon ile haberleşmek için docker-java kütüphanesini kullanır. docker-java
, komut satırından çalıştırdığınız docker client ile yapabileceğiniz herşeyi Java API’ları ile yapabilmenizi sağlar.
Docker container’ları başlatmanın birden fazla yöntemi var. Örneğin mysql database’ini başlatmak için aşağıdaki gibi docker run
komutunu çalıştırabiliriz.
$ docker run --name database -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=tododb \
-e MYSQL_USER=todouser \
-e MYSQL_PASSWORD=todopass \
-d mysql:5.7.21
Ya da docker-compose
komutu ile docker-compose.yml dosyasında tanımlı database
‘i aşağıdaki gibi başlatabiliriz.
$ docker-compose up database
Yukarıdaki iki örneğin TestContainers’taki (kotlin) karşılığı aşağıdaki gibidir:
TestContainers’ın özelleşmiş container sınıflarını kullanarak mysql sunucusunu testlere hazır hale getirmek daha kolay. Ortam değişkenlerini withEnv
metodu ile belirtmek yerine container DSL’i kullanabiliriz.
GenericContainer yerine özelleşmiş MySQLContainer kullanmanin bazı avantajları var.
- MySQLContainer, mysql database’inin bağlantı kabul edip sorgulara cevap vermesini bekler. Bu sayede testler çalışmaya başladığında mysql database’inin kullanıma hazır olur.
- MySQLContainer’ın API’si daha anlaşılır.
withUsername
,withPassword
,withDatabase
gibi methodları sayesinde ortam değişkenleri (ENV) ile mysql database’ini ayarlamamıza gerek kalmıyor. - Mysql’e özel olduğu için mysql’in defaul çalışma portu
3306
otomatik olarakexpose
ediliyor.
H2, hsqldb gibi in-memory ve çok hızlı çalışan database’ler varken neden gerçek bir database sunucusu kullanıp testlerimi yavaşlatayım diye düşünebilirsiniz. Entegrasyon testlerinde gerçek database’ler (MySQL, PostgreSQL, Oracle XE) kullanmanın hem avantajları hem de dezavantajları var.
Avantajları
- Herşeyden önce testler production ortamı ile aynı ayarlara sahip database sunucuları ile çalışmış oluyor.
- H2 ve hsqldb gibi in-memory database’ler production ortamında kullandığımız gerçek database’lerin tüm özelliklerini içermeyebilirler. Örneğin Oracle DB’de kullandığımız
stored procedure
‘leri testler için kullandığımız in-memory database engine için ayrıca kodlamamız gerekebilir. Ya da MySQL’dekiUNIX_TIMESTAMP()
fonksiyonunun H2’de olmaması gibi uygulamanın ihtiyacı olan başka birçok özellik in-memory database’de eksik olabilir.
Dezvantajları
- Production ortamında kullandığımız MySQL, PostgreSQL gibi database’lerin başlatılması ve testler bitince kapatılması in-memory database’ler kadar hızlı olmaz.
TestContainers kütüphanesinin kullanımını göstermek için Kotlin ile basit bir web uygulaması geliştirdim. Uygulamanın kaynak kodlarına kotlin-todo-app github reposundan erişebilirsiniz. Üç katmanlı bu basit web uygulamasında:
- Ön yüzde vuejs kullanıldı.
- Backend SparkJava web framework’u kullanılarak geliştirildi.
- Data storage katmanında ise MySQL ( ya da başka bir relational database) kullanıldı.
Bu uygulamanın end-to-end testlerini TestContainers yardımıyla yapmaya çalışalım. Test setup’ımız aşağıdaki resimdeki gibi olacak.
- MySQL database container başlatacağız.
- Daha önceden docker imajını hazırladığımız todo uygulamamızı başlatacağız. Demo uygulamasının docker imajına https://hub.docker.com/r/ilkinulas/todoapp/ adresinden erişebilirsiniz.
- End-to-end testlerin otomasyonu için de selenium çalıştıran bir container başlatacağız.
TestContainers kütüphanesini kotlin ile kullanmak isterseniz aşağıdaki gibi wrapper container class’larınızı yaratmanız gerekiyor. Bu konuyla ilgili github issue’sunu inceleyebilirsiniz.
Şimdi sırasıyla container’larımızı teste hazırlayalım.
MySQL container’ı oluştururken "mysql:5.7.21"
parametresi kullandım. Çünkü testlerimin her zaman aynı mysql sunucusu versiyonu ile çalışmasını istiyorum. Sadece "mysql"
yazsaydım, TestContainers "mysql:latest"
imajını kullanacaktı. Container imajlarının yeni versiyonları çıkınca sürprizlerle karşılaşmamak için sabit bir versiyon kullanmayı tercih ediyorum. MysqlContainer’i oluştururken kullandığım withNetwork
ve withNetworkAliases
metodlarının ne işe yaradığını birazdan anlatacağım.
Todo Web uygulamasının container’ını da GenericContainer sınıfı yardımıyla başlatıyorum. Demo uygulamam 9000 portundan hizmet verdiği için container’ın bu portu dinlemesini söylüyorum. withEnv
metodu ile de container çalışırken DB_URL
ortam değişkenine istediğim bir değer atıyorum.
Selenium testlerimi çalıştırmak için de bir tane BrowserWebDriverContainer‘a ihtiyacım var. Bu container sınıfı da MysqlContainer gibi özelleşmiş bir sınıf. End-to-end testlerimin hepsinin ./out/
dizinine kaydedilmesi için withRecordingMode
‘nu kullandım. Bu sayede testlerden birisi başarısız olduğunda kaydedilen test videosu hatanın kaynağını bulmaya yardımcı olacak.
Bu üç container’ın birbirlerine erişebilmesi için docker’ın networking altyapısını kullandım. TestContainers’ın @Network rule’u ile aşağıdaki gibi bir network oluşturuyorum.
Tüm container’larin aynı network’ü kullanabilmesi için withNetwork(network)
metodunu kullandım. withNetworkAliases(alias:String)
metodu ile de container’ın network içinde hangi hostname ile erişilmesini istediğimi belirtiyorum. Örneğin mysql container için network alias olarak “database” kullandım. Bu sayede web uygulaması container’ımdan mysql’e jdbc:mysql://database:3306/tododb
URL’ı ile erişebiliyorum. Küçük test network’üm için DNS ayarlarını yapıyorum gibi düşünebilirsiniz.
Son olarak RuleChain ile bu Rule’ların hangi sırayla çalışması gerektiğini belirtiyorum. Önce test network’üm oluşturulacak. Sonra mysql sunucusu başlatılacak. Mysql 3306 portundan bağlantı kabul etmeye başladıktan sonra demo web uygulamam başlayacak. RuleChain’de son sırada e2e testleri çalıştırmak için kullanılan selenium container başlatılacak.
EndToEndTest
sınıfının tamamına bu github linki üzerinden ulaşabilirsiniz.