Unit testing — Writing Dockerfiles like a software developer

Renato Mefi
4 min readFeb 27, 2019

--

This article is part of a series on “Writing Dockerfiles like a software developer”, to find all the other articles click here.

“Oh, you say: Unit tests.. for Docker images? You sure?”

YES

One of the best ways (imho) to achieve confidence in your technology stack is by writing tests, when you cover the bases of what you need the feeling of being able to upgrade versions, rebuilding without fear pays it off, fewer surprises and more trust in the process!

Ok, but how do we do that with Docker?

Testinfra

After all, a Docker container is simply a running Linux system, sysadmins have been testing their provisioning and changes done for a while, this made me think, why not use just one of those tools?

Two of them came to my mind, Serverspec and Testinfra, my choice was then driven by ease of use, at the time (it might have changes) when I first started testing my images, Serverspec still had to use a bigger boilerplate to get my tests running, thus, Testinfra is my choice.

Testinfra leverages on pytest, which then gives a super nice output and much, much more functionality to the tests.

If you’d like to see an implementation of testing Docker with ServerSpec, here is an article by Jesse Adametz. Enjoy it, but come back to see how it’s with Testinfra!

A Dockerfile

In this really simple example, all we want is an Alpine image, and add a custom user.

The image builds fine, and if we want to we can see if our user is there in a really simple way

Ok, show me how to do it with Testinfra now!

Writing a test

We want now to test if the user we have created has all the expected attributes and is present in the system.

To do that we’re going to use the user Testinfra module, with it we can check it all, ids, name, group, etc. Check its documentation

Our test will check for the username, group, home dir and login shell.

As mentioned, Testinfra leverages on pytest, thus we’re going to write a simple pytest, the host property is injected automatically, and it represents a running Docker container in our case, it could be an ssh connection, localhost, or others.

Running a test

So far we have a built image and a test, but Testinfra expects a running container to be able to execute the tests against.

In order to do that, it needs a few things: Where to look for the running container, access to the tests scripts, access to the container itself (Docker socket for instance).

Since installing Testinfra with Docker support can take a little while depending on your knowledge, I’m not going to cover it here, BUT, I’m going to use a Docker image for that, which this one, you can run hassle free.

Cool, now prove the value of this test!

Sure, such a small image right, what could go wrong?

One day you open Twitter and see that Alpine 3.9 is released, awesome right? Let’s update it and ship it right away! Warning, next code sample is a bit bigger, but you understand it all already.

Uhhh, wait, what? My test failed? Yes.

The Docker alpine official image, from 3.8 to 3.9 has changed the default user shell from /sbin/halt to /bin/ash . It might not impact you, but you get the idea right? It could’ve been something that actually could’ve broken your application maybe, in production time.

You can test many things, compiled modules/extensions, external libraries, binaries you download, users permissions, existence of directories, packages installed (and their versions), run custom commands, and much, much more.

Let’s do a simple automation

Later in this article series I’m going to cover how to automate this in a CI server, but for now we can do a minimalist version.

Short disclaimer, I’ve chosen to docker run the images locally and then test because it’s easy and fits the design I’m proposing, but you can let pytest build the Dockerfile for you, see a simple example, or a slightly more complete and opinionated one.

This script will receive the Docker tag or image ID as first parameter, start a container as daemon mode, run the tests against it, in case of success or failure it’ll remove the container using a bash trap. Now we’re covered, let’s see it running

You could adapt the script to build the image for you as well, get the dynamic image ID and run the tests, I chose this way to decouple building and testing for separations of concerns, but feel free to do it as you wish!

Some real usage

It’s been a nice journey so far I hope, if you’re still looking for inspiration on real life images, I cannot open my work code to you, but I can show you what I’ve been doing in open source regarding testing docker images.

You can check my docker-testinfra image, which we used during this article, repo is here, don’t forget to start it!

Ok, that was super simple, Usabilla has recently opened our Docker PHP images, in which I’ve put a lot of those things here, there’s a quite extensive test suite which you can check: https://github.com/usabilla/php-docker-template/tree/master/test/container

I’ve even used it to test a healthcheck script against Alpine and Debian images, to guarantee compatibility, using pytest fixtures and marks to help on that: https://github.com/renatomefi/php-fpm-healthcheck/tree/master/test/testinfra

That’s it

I really hope you’re now convinced to test your Docker images and gain trust in your pipeline, further in the series we’ll see how this ecosystem grows and empowers a CD pipeline.

More

When the next articles are ready I’ll post them on my twitter, also update this index, feel also free to find me on GitHub as well!

--

--

Renato Mefi

ZCE, ZCPE, ZFCE, LPIC-1, LFCS, LFCE Professional. a.k.a.: A guy who loves linux and software development! https://github.com/renatomefi