# Docker TDD

Just about every dev has had the misfortune of working for an organization, or
working on a project, that made little to no use of tests and instrumentation.
For the first while, everything seems fine, until the project grows in
complexity and every little change causes breakage somewhere else - or worse:
world breakage.

I’ve been fortunate lately to be working with an organization that highly values
(A)TDD, and it always makes things a whole lot easier during development. A
comprehensive set of unit/functional/integration/behavioural tests act as a
safety net that can catch a great deal of breakage when you change even the
simplest thing. Given a good set of tests, you shouldn’t have to spend any time
worrying about whether you broke something or not, leaving you to focus on
actual development.

There are plenty of difficult-to-find articles about testing docker containers,
but most of them have their tests written in Ruby. This doesn’t make sense to
me. If I’m running on Debian/Ubuntu, why would I install Ruby and all of that
bloat, when I already have Python installed by default? I couldn’t find a single
doc on testing Docker containers using python. I did find plenty on running
python tests inside docker containers. Not helpful.

So here’s what I came up with:

• [Mamba][mamba] for the test runner
• [docker-py][docker-py] to interact wtih the docker daemon
• [sure][sure] for some fancy testing syntax

We want to build a docker image that runs Bind9 for DNS service. Simple right?

## Writing the Test Skeleton

We want the test to handle everything, including building and deleting the
image. So let’s set that up in a skeleton that we’ll later use for our test
cases. We’ll call this file specs.py:

This skeleton does a few things for us. The before.all bit builds our image
and grabs the image id for later use. If the image can’t be built, then it craps
out and keeps the rest of our tests from running. The after.all bit removes
the image so that we don’t clutter up our local machine with useless docker
images.

We of course create a context for testing our image, which itself has a
before.all section that retrieves the image that was built earlier. We then
test to make sure the image exists. Under normal circumstances, our little check
that we perform when actually building the image should fail first, but it
doesn’t hurt to have something visibly pass.

At any rate, if you run this now, mamba specs.py you’d get the following
error:

Failed to build image. Unable to continue.
500 Server Error: Internal Server Error ("Cannot locate specified Dockerfile: Dockerfile")


Makes sense, as we don’t have a Dockerfile to define our image yet. Let’s create
a basic one:

Now we run the test and it should pass:

## First Test

So our single test case confirms that our image is built as expected. It’s time
to move on to what makes our image special. This is supposed to be a DNS server,
so let’s expose port 53/udp and 53/tcp.

Start by adding a failing test:

The test will fail until we update our dockerfile to:

## Testing the Container

Now we want to test the container itself, while running, to ensure that it
behaves as we expect it to. So let’s set up a new context for testing our
container, and get the context to create and start our container, and tear it
down when we’re done testing. And while we’re at it, we’ll add our querying
test:

If you run this now, you should get an error about result = cli.start(container=self.container_id)
returning None. When all goes according to plan, it won’t, but right now the
to create that entrypoint.sh script before we run our tests, otherwise Docker