How To Use Docker To Run PHPUnit Tests In Parallel

Emmanuel Quentin
Emmanuel QuentinNovember 04, 2013
#testing#php#devops

Ahoy sailor !

I've heard you want to know more about all the containers I'm shipping in my boat. Let me tell you the story of Docker, the LinuX Container (LXC) manager that you've dreamt of for a long time.

Docker allows you to run lightweight containers. A container can run any Linux based OS by pulling it from a repository.

The Shipment

Ok now you may think that theses containers are like your well known Virtual Machine, but keep reading young sailor. A container shares his OS core functionnaly with his running host. The copy-on-write mecanism stores only data that differs from his image, hence a container is very lightweight and starts quickly.

An image? Oooh yes, an image is created by a DockerFile that contains a list of instruction telling how to build a container. It's very similar to a VagrantFile: once you've created an image, you can run the container from this image and only the modified data will be stored in the container. This magical behaviour gives the possibility to run hundreds of containers in the same machine.

Like a real container, a Docker's container can be shipped by every Linux OS and can contain anything you need. It will run the same way everywhere.

A Container, For What ?

Now that we know how Docker works, let's make an image that runs a Symfony standard edition and run some tests in parallel.

The treasure to find is a script that retrieves all PHPUnit groups of the application and run them in different containers simultaneously. Consequently, if a test cleans up a database or adds data into a search index, other tests (running in another container) won't be affected because they have they own services.

Let's create a new folder and grab a copy of the last edition of Symfony.

Running On Mac OSX

For now Docker can only run on Linux systems, Mac OSX (based on FreeBSD) does not have a LXC system. This will not frighten a salty dog. We can run a virtual machine thanks to Vagrant to run Docker, so create a file called VagrantFile in your folder:

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "precise64"

  config.vm.box_url = "http://files.vagrantup.com/precise64.box"

  config.vm.network :private_network, ip: "192.168.56.4"

  config.vm.synced_folder ".", "/vagrant", nfs: true
end

This file tells to Vagrant to create a virtual machine based on a Ubuntu precise 64bits (Docker can't run on 32 bits), and mounts the current folder to the /vagrant directory of the guest machine.

Start and log to the machine with:

vagrant up
vagrant ssh

Create The Docker Image

Let's dive into the DockerFile (created in the current folder) :

FROM losinggeneration/debian

# Update apt-get
RUN apt-get -y update

# Install apache + MySQL + PHP54
RUN apt-get -y -f install apache2 php5 mysql-server libapache2-mod-php5 php5-mysql php5-curl curl libicu-dev
RUN apt-get -y -f install php5-intl

# Install phpunit
RUN aptitude install -y -f phpunit

# Configure apache
RUN /bin/echo -e '<VirtualHost *:80>\nDocumentRoot /app/parallelTests/web\n<Directory "/app/parallelTests/web">\nAllowOverride All\n</Directory>\n</VirtualHost>' | tee /etc/apache2/sites-available/default > /dev/null

RUN a2enmod rewrite
RUN service apache2 reload

# Install git
RUN apt-get install -y -f git

# Install composer
RUN curl -sS https://getcomposer.org/installer | php \
        && mv composer.phar /usr/local/bin/composer

# Copy start script
ADD ./install.sh /install.sh

EXPOSE 80

CMD ["/bin/bash", "/install.sh"]

This file tells Docker to install a Debian Wheezy (7.1), update apt & installs a LAMP environment to run Symfony. You should notice the ADD ./install.sh /install.sh statement, it will copy the install.sh file in the container to setup the application.

The last line defines what should be run directly after booting. Here we run a bash (it's quicker than SSH) and the install.sh script.

What the install.sh looks like:

#!/bin/bash
if [ ! -f /app/parallelTests/app/config/parameters.yml ]; then

mysqld_safe &amp;
sleep 10s
mysql -u root -h localhost -e "CREATE USER 'acme'@'localhost' IDENTIFIED BY 'Acme'; CREATE DATABASE acme; GRANT ALL PRIVILEGES ON acme.* TO 'acme'@'localhost'; FLUSH PRIVILEGES;"

# Install dependencies
cd /app/parallelTests &amp;&amp; composer install --no-interaction

# Configure the application
sed -e "s/database_name:     symfony/database_name:     acme/
s/database_user:     root/database_user:     acme/
s/database_password: ~/database_password: Acme/g" /app/parallelTests/app/config/parameters.yml.dist &gt; /app/parallelTests/app/config/parameters.yml

fi

This script checks if the file parameter.yml exists, otherwise it creates a database and a user, installs the dependencies and configures the application.

One Script to Run Them All

Here is the treasure that you are seeking, the script that runs all your tests. Tet me introduce you to scripts.sh:

#!/bin/bash

# Retrieve and parse all groups
groups=$(sudo docker run -v /vagrant:/app/parallelTests/ acme/parallelTests phpunit -c /app/parallelTests/app --list-groups)

parsed=$(echo $groups | sed "s/-/\t/g")
results=$(echo $parsed | awk '{for(i=9;i&lt;=NF;i++) {print $i}}')

# Loop on each group name and run parallel
for i in $results; do
   echo $i
done | parallel sudo docker run -v /vagrant:/app/parallelTests/ acme/parallelTests phpunit -c /app/parallelTests/app --group {}

First of all, we retrieve all PHPUnit groups by running a container with the phpunit --list-group command.

Next the gnu parallel command helps us to run tests for each group in parallel. Each task is launched by one core of your processor. If you've got a large cruiser with 8 core then you can run 8 tests group simultaneously.

The -v option of Docker mounts the current directory (the /vagrant on the previously create Virtual Machine) to the /app/parallelTests/ folder of the container.

Here is the list of Docker's command if you want to know more about it.

I hope this parchment helps you discover the power of Docker.

Godspeed!

Did you like this article? Share it!