Teszteljünk konténerben
Hogyan lehet hasznunkra a Docker a különböző tesztek írása során
Tavaly, mikor nekiálltam a Docker-es sorozatnak, terveztem még egy negyedik részt is, ami a tesztelésről szólt volna. Aztán annak rendje és módja szerint meg is feledkeztem róla. Egészen mostanáig.
A példák PHP-t használnak a tesztelt alkalmazás nyelveként, de a koncepciók hasonlóan működhetnek bármilyen más nyelven is. A teljes kód pedig szokás szerint megtalálható a példa repóban. Vágjunk is bele.
Unit tesztek
Az ide vonatkozó Docker Compose konfiguráció annyira egyszerű, hogy igazából csak a teljesség kedvéért került bele ebbe a bejegyzésbe is. A nagy részét megtárgyaltuk már a sorozat első részének "Hello Composer" fejezetében.
docker-compose.yml
version: "3"
services:
app:
image: composer:1.3
volumes:
- .:/app
working_dir: /app
A teszteket pedig a következő paranccsal tudjuk futtatni:
$ docker-compose run --rm -T app vendor/bin/phpunit
A könnyed felvezető után ugorjunk is inkább az izgalmasabb részekre.
Integrációs tesztek
A témakör elég tág, így két dolgot is meg fogunk vizsgálni közelebbről. Az adatbázist használó és a külső szolgáltatással kommunikáló kódok tesztjeit.
Adatbázis
Itt, hasonlóan a korábbi "Hello MySQL" részhez, szükségünk lesz egy adatbázis szervere:
docker-compose.yml
# ...
database:
image: mysql:5.7
volumes:
- ./etc/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
environment:
MYSQL_ROOT_PASSWORD: test
MYSQL_USER: test
MYSQL_PASSWORD: test
# ...
Az init.sql
segítségével létrehozunk két adatbázist, az egyiket a fejlesztéshez szeretnénk majd használni, a másikat a tesztek futtatásához. Ezen kívül az init.sql
még beállítja a jogosultságokat és létre is hozza mindkettőben a megfelelő sémát.
Ami ezen a ponton feltűnhet, hogy az adatbázis tartalmának nem csináltunk saját volume-ot. Ennek az oka pedig az, hogy most nem csak egy konfigurációs fájlt fogunk használni. Lesz egy külön konfigurációnk a fejlesztéshez:
docker-compose.dev.yml
# ...
volumes:
mysql:
# ...
database:
volumes:
- mysql:/var/lib/mysql
# ...
Itt Docker volume-ot használunk az adatok tárolására, hogy azok a konténerek leállítása után is megmaradjanak. A Docker Compose-t pedig a következőképpen tudjuk rábírni, hogy mindkét fájlt használja:
$ docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
Mivel az az elgondolás, hogy fejlesztés közben az alkalmazást is aktívan használjuk, ezért a teszteket futtathatjuk exec
segítségével a már futó konténerben:
$ docker-compose -f docker-compose.yml -f docker-compose.dev.yml exec -T app vendor/bin/phpunit
A másik konfigurációnk a build-hez lesz:
docker-compose.build.yml
# ...
database:
tmpfs:
- /var/lib/mysql
# ...
Itt tmpfs-t használunk az adatok tárolására, mivel nem is igazán akarjuk őket tárolni, ellenben szeretnénk ha gyors lenne, a tmpfs pedig memóriában tárolódik.
A build esetén az elvárásaink is kicsit mások, ami a tesztek futtatását illeti. Az alkalmazás még nem fut és szeretnénk azt is megvárni, hogy az adatbázis elinduljon, mielőtt elkezdenénk futtatni a teszteket.
$ docker-compose -f docker-compose.yml -f docker-compose.build.yml up -d database
$ docker-compose -f docker-compose.yml -f docker-compose.build.yml exec -T database bash -c 'while ! mysqladmin ping -hdatabase -u$$MYSQL_USER -p$$MYSQL_PASSWORD --silent; do sleep 1; done'
$ docker-compose -f docker-compose.yml -f docker-compose.build.yml run -T --rm app vendor/bin/phpunit
$ docker-compose -f docker-compose.yml -f docker-compose.build.yml down
- Elindítjuk az adatbázist
- A
mysqladmin ping
parancs segítségével megvárjuk, hogy ténylegesen el is induljon - Lefuttatjuk a tesztjeinket
- Lekapcsolunk mindent.
Mint az a példából is gyönyörűen látszik, kezdenek egyre hosszabbak lenni ezek a parancsok, senki se gépelné be ezeket egynél többször, ha nem muszáj. Ennek kiküszöbölésére továbbra is tudom ajánlani a Makefile
használatát, amire szintén lehet példát találni a kapcsolódó repóban.
API
Egy másik dolog, ami problémákat okozhat integrációs teszteknél, ha valamilyen API-val kommunikálunk. Ennek megoldására felhúzhatunk egy egyszerű service-t, ami mondjuk a PHP beépített webszerverét használja és egy olyan könyvtárszerkezetet szolgál ki, ami leutánozza a tesztekben használt API-t.
docker-compose.yml
# ...
api:
image: php:7.1-alpine
command: php -S 0.0.0.0:80 -t /app/
volumes:
- ./etc/api/:/app/:ro
# ...
Az etc/api/
könyvtár pedig valahogy így nézhet ki:
- v2/
- users/
- 1234/
- index.php
- index.php
- 1234/
- users/
Az index.php
megvalósítása lehet nagyon egyszerű, már-már statikus, de ízlés szerint elbonyolíthatjuk akár input validációval, authentikációval vagy egyéb dolgokkal is.
Funkcionális tesztek
Ez is egy többféleképpen megközelíthető kategória. Előfordulhat, hogy az általunk használt keretrendszer nyújt segítséget, és csak emulálunk http kéréseket (mint például a Silex féle WebTestCase). Az is lehet egy megoldás, hogy valós http kéréseket küldünk az alkalmazásnak és az érkező válaszokat vizsgáljuk. Mindkét eset a Docker Compose konfigurációja szempontjából leginkább a unit tesztek megoldására hajaz.
No de mi van olyankor, ha egy igazi böngészőben szeretnénk automatizáltan kattintgatni? Ebben a Selenium és a Codeception lehet a segítségünkre. Szerencsénkre a Selenium volt olyan kedves, hogy szolgáltasson Docker image-eket is:
docker-compose.yml
# ...
browser:
image: selenium/standalone-chrome
volumes:
- /dev/shm:/dev/shm
# ...
Már csak a Codeception-nek kell megmondani, hogy ezt használja:
tests/acceptance.suite.yml
# ...
- WebDriver:
url: http://web
host: browser
browser: chrome
# ...
A web
itt az alkalmazásunkat futtató service neve, a browser
pedig a fent definiált Selenium service. Eredetileg az alkalmazást itt is app
-nak hívtam, mint a többi példában, de valamilyen rejtélyes oknál fogva úgy nem működött.
Jól látszik, hogy ahogy nő az egy teszttel megfuttatott kód mennyisége, úgy lesz bonyolultabb a hozzá felhúzott teszt infrastruktúra is. A példában nem tértünk rá ki külön, de a funkcionális teszteknél jó eséllyel szükségünk lesz majd az integrációs teszteknél használt módszerekre is, hogy egy megfelelő állapotban lévő alkalmazást tudjunk a böngészőben tesztelni.