Apply Test Driven Development(TDD) to the process of building container images by defining test before writing code and automate the testing process. Iterate through the TDD cycle while developing and running the tests later in continuous integration to ensure robust and reliable container images.
Installation
We create a Gemfile
for installing all required gems.
# Gemfile
gem 'docker-api'
gem 'serverspec'
gem 'rspec'
Then we install them
bundle install
Preparation
Create the directory where the Tests are going to live. Add the folder to .dockerignore
since we wo not want the test to be included within our final container image.
mkdir spec
echo spec >> .dockerignore
spec/spec_helper.rb
require the used gems
require 'serverspec'
require 'docker'
Build the container image from the project's root folder
image = Docker::Image.build_from_dir('.')
Setup serverspec
to run the tests against a container based on the previous build image
set :backend, :docker
set :docker_image, image.id
The test driven workflow
create the Dockerfile
Boilerplate Dockerfile to ensure the image building process is working
# Dockerfile
FROM debian:stable
Write first test
We want to have a script file /entrypoint.sh
in our image
describe file '/entrypoint.sh' do
it { should exist }
end
See the test failing
Run the test to see it fail
bundle exec rspec
F
Failures:
1) File "/entrypoint.sh" is expected to exist
Failure/Error: it { should exist}
expected File "/entrypoint.sh" to exist
# ./spec/dockerfile_spec.rb:10:in `block (2 levels) in <top (required)>'
Finished in 1.94 seconds (files took 0.31723 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/dockerfile_spec.rb:10 # File "/entrypoint.sh" is expected to exist
Fix the test
entrypoint.sh
#!/bin/sh
# Dockerfile
FROM debian:stable
COPY entrypoint.sh /
Run the test again
bundle exec rspec
.
Finished in 1.78 seconds (files took 0.33197 seconds to load)
1 example, 0 failures
run the test inside a GitLab CI Job
Assumed the image was build and pushed to the container registry in a job that has already finished, we do not need to build the image from scratch but pull it from the container registry
update spec_helper
Determine if the tests are running inside GitLab CI. If yes, authenticate against the container registry and pull the image. Otherwise build the image locally
# check if this is a gitlab ci pipeline
def in_gitlab_ci?
ENV['CI'].eql?('true')
end
if in_gitlab_ci?
Docker.authenticate!(
username: ENV['CI_REGISTRY_USER'],
password: ENV['CI_REGISTRY_PASSWORD'],
serveraddress: ENV['CI_REGISTRY']
)
Docker::Image.create(
fromImage: ENV['CI_REGISTRY_IMAGE'],
tag: ENV['IMAGE_TAG'] || 'latest',
)
else
image = Docker::Image.build_from_dir('.')
end
GitLab Job definition
Create a job that runs on a GitLab CI shell runner with Docker installed locally. Install the required Gems and cache them for subsequent job runs. Run the tests and pass the results to GitLab in the JUnit fromat for displaying it in the GUI.
test container image:
artifacts:
reports:
junit: serverspec-junit.xml
tags:
- shell_runner
cache:
key: Gemfile.lock
paths:
- vendor/ruby
script:
- bundle config set --local path 'vendor/ruby'
- bundle install
- bundle exec rspec spec/*_spec.rb
--format documentation
--format RspecJunitFormatter
--out serverspec-junit.xml