Юнит-тесты для образов Docker
Тестирование — важный шаг на всех этапах разработки ПО. Но не все компоненты имеют очевидные, известные и понятные пути тестирования. К примеру, образы Docker либо не тестируют вообще, либо тестируют только на пригодность к запуску. Как протестировать образ Docker так, чтобы убедиться в том, что он выполняет свои задачи?
Юнит‑тестирование (или модульное тестирование) — это процесс в разработке программного обеспечения, позволяющий проверить работоспособность отдельных модулей исходного кода. Такое тестирование привычно применяется в разработке непосредственно программного обеспечения, однако с ходу сложно себе представить юнит‑тестирование образа Docker.
Взглянем на простейший Dockerfile:
FROM busybox:1.32.1 RUN echo 'Hello, World!' > /test.txt
Здесь мы выполняем единственное действие — добавляем файл со строкой Hello, World! в файл /test.txt.
Как можно проверить, что мы достигаем желаемого результата? Можно запустить собранный контейнер и посмотреть, что, во‑первых, нужный файл присутствует, а во‑вторых, его содержимое равно ожидаемому.
$ docker build -t test . [+] Building 7.7s (6/6) FINISHED $ docker run --rm test ls -lha /test.txt -rw-r--r-- 1 root root 14 Feb 20 19:26 /test.txt $ docker run --rm test cat /test.txt Hello, World!
Не слишком удобно, не так ли? К счастью, существует фреймворк terratest. Он позволяет писать тесты на Golang для Docker (и docker-compose) так же, как и для обычного кода!
Взглянем на программную реализацию данного теста:
package docker_test import ( "testing" "github.com/gruntwork-io/terratest/modules/docker" "github.com/stretchr/testify/assert" ) func TestDockerImage(t *testing.T) { // Определяем название образа для тестирования tag := "test" buildOptions := &docker.BuildOptions{ Tags: []string{tag}, } // Собираем образ из Dockerfile’а docker.Build(t, "../", buildOptions) // Фактически выставляем как опции запуск контейнера со следующими командами // Команда, которая вернет 'exists', если файл существует eOpts := &docker.RunOptions{Command: []string{"sh", "-c", "[ -f /test.txt ] && echo exists"}} // Команда, которая вернет содержимое файла cOpts := &docker.RunOptions{Command: []string{"cat", "/test.txt"}} // Запускаем контейнер с проверкой на наличие файла chkExisting := docker.Run(t, tag, eOpts) // Проверяем, что вывод равен желаемому assert.Equal(t, "exists", chkExisting) // Запускаем контейнер с выводом содержимого файла chkContent := docker.Run(t, tag, cOpts) // Проверяем, что вывод равен желаемому assert.Equal(t, "Hello, World!", chkContent) }
Стало ощутимо удобнее! Благодаря полноценному языку программирования мы можем создавать намного более сложные сценарии тестирования, использовать API докер и так далее.