A PrestaShop module development automation journey — Part 2

NeoKree
7 min readJul 30, 2018

Install a testing suite

Links: Code in GitLab

Journey map:

Note for the reader:

I wanted to write an article about how to write automated tests for a prestashop module. However in this process I found complicated just to install the testing suite, so I split up the two articles to avoid writing a too long article.

Choosing the tools

For automated testing I have chosen Codeception.
There are other alternatives, like PHPUnit, Behat or PHPSpec…
But in the end, I wanted to keep the things simple and Codeception let you create tests of all types, from unit to acceptance tests. This is the main reason for my choice.

For installing Codeception, we need to use composer , which is the official PHP dependency manager.
If you don’t know what it is, this article should answer all your questions:

Installing Composer

Normally, to install composer, we need to first download it as a php archive (aka .phar ), using a command like this:

curl -sS https://getcomposer.org/installer | php

But this will install composer in our local project for our system PHP version. If you read the first part of this journey, you already know that our development configuration is all stored inside docker containers. So we don’t need to have PHP 5.6 installed on our machine, and everyone will use the exactly same php version. (someone yelled multiple projects?)

So what are the other choices?

The first one is using a docker image. Composer have an official image in docker hub. But there is a problem: It uses PHP 7 and there is not a PHP 5.6 tag. (Actually, the deprecated composer/composer image have it, but it uses an old composer version and it will not be update in the future, so I don’t think it is a mantainable choice.)

The second solution is installing composer directly from one of our prestashop websites. But this would mean we need to enter in the prestashop container using bash, move into the directory module, running the composer command and exit from the container every time we need to do something with composer. Too many commands for something that should be simple.

The third one is creating our custom composer docker image, but with PHP 5.6. It is much more complicated to setup, but after the first time we can just use it. And if another guy want to start develop with us, we can just build the image again in his system and run it.

I will explore the third solution, because I think it is the most scalable with multiple developers working on the same project.

Creating the docker image

We need an image that is exactly the same to the official one but with PHP 5.6, the first thing that came to my mind was to copy paste the dockerfile of the official image and change the FROM command, but after browsing for a while I discovered that the old deprecated composer/composer image was about half smaller, compared with to our builded image. Since I have an iMac with SSD, I would like to save some space if possible.

However, we need to store this dockerfile somewhere. Since we would have more dockerfiles in the future, I choose to create a build folder in the root of our project, under where we will create another subfolder called composer that will store the dockerfile.

This is the final result, with a size of 71 MB:

build/composer/Dockerfile

FROM php:5.6-alpine

ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /tmp

RUN apk add --no-cache --virtual git subversion openssh mercurial tini bash patch \
&& echo "memory_limit=-1" > "$PHP_INI_DIR/conf.d/memory-limit.ini" \
&& echo "date.timezone=${PHP_TIMEZONE:-UTC}" > "$PHP_INI_DIR/conf.d/date_timezone.ini" \
&& apk add --no-cache --virtual .build-deps zlib-dev \
&& docker-php-ext-install zip \
&& runDeps="$( \
scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \
| tr ',' '\n' \
| sort -u \
| awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
)" \
&& apk add --virtual .composer-phpext-rundeps $runDeps \
&& apk del .build-deps \
&& rm -rf /var/cache/apk/* \
&& curl -sS https://getcomposer.org/installer | php \
&& mv composer.phar /usr/bin/composer

WORKDIR /app

CMD ["-"]
ENTRYPOINT ["composer"]

So now we can just build the image

docker build -t neokree/composer ./build/composer/

After the process, we can finally install composer using our docker:

docker run --rm -it neokree/composer init

And composer will start the config generator:

Installing CodeCeption

When the procedure ends, we can finally install the test automation suite:

docker run --rm -it neokree/composer require codeception/codeception --dev

Voilà, we now have installed locally the test suite, only for our development environment.

Following the quick start of the suite, we can now bootstrap it, creating all necessary test directories, using this command:

./vendor/bin/codecept bootstrap

But again, this will use our local PHP version. So we need to use the php:5.6-alpine image to create a container on the fly and use it instead of our local one.

docker run --rm -it -v $PWD:/app -w /app php:5.6-cli-alpine ./vendor/bin/codecept bootstrap

Even if this command works, I think that you are a little upset about the lenght of these command (just as I am), there should be a way to avoid call docker run --rm -it and a lot of other parameters to do something.

Make it easy

To make things easies, we need to wrap the complicated docker commands under a clean interface, like the original command interface we would have if we just installed everything locally. We need a gateway.

There are multiple ways to do this:

  • Aliases
  • Makefile
  • Bash scripting

But we are PHP developers. PHP is a scripting language. Why we cannot use it for write a script that wraps these commands?

Sure, in that case our script will use the default PHP version of the system, but I think this is acceptable. Every linux distribution, or macOS version that I know have it already installed as default. So I choose this path.

First, we need a place where store our scripts. Taking a cue from linux, I create a folder called bin in the root of our project. Inside it we will start to create our binaries:

The composer script

Inside the folder we will create a composer (without extension) file to wrap composer commands. But we should define how we will use it first, so we know what and how to implement it.

Since is a bin folder, I would like to be able to use it directly calling it from php, as well to call it like an application, as well as including in my PATH, like all other system applications. Some examples:

Call from PHP:
php bin/composer install

Call as application:
./bin/composer install

Add to our local PATH and use it:

export PATH=$PWD/bin:$PATH
composer install

Ok now we know what we want, let’s start thinking how to do it.

So the script will:

  • Build the docker image if not already built
  • Pass the composer arguments to our composer docker container
  • Show the composer output

It is not a lot of work, we just need to work a bit with shell_exec and/or system functions to make it work. But since we need to reuse some commands in the next script, we will abstract some functions for the future into a lib/docker.php file.

This is the end result:

bin/composer

#!/usr/bin/env php
<?php
include_once
('lib/docker.php');

$imageName = 'neokree/composer';
$arguments = implode(' ', array_slice($argv, 1));
$projectPath = realpath(__DIR__.'/../');

if (dockerImageDoesNotExist($imageName)) {
print "Composer image not found.\n";
print "Start building it. This may take a while...\n";

$dockerfileFolderPath = $projectPath."/build/composer/";
buildDockerImage($imageName, $dockerfileFolderPath);
}

$returnVal = executeCommandInDockerContainer($arguments, $imageName);

exit($returnVal);

bin/composer/lib/docker.php

<?php

/**
*
@param string $imageName
*
@return bool
*/
function dockerImageDoesNotExist($imageName)
{
return trim(shell_exec("docker image inspect $imageName:latest 2> /dev/null")) === '[]';
}

/**
*
@param string $imageName
*
@param string $dockerfileFolderPath
*/
function buildDockerImage($imageName, $dockerfileFolderPath)
{
shell_exec("docker build -t $imageName $dockerfileFolderPath");
}

/**
*
@param string $command
*
@param string $imageName
*
@return int
*/
function executeCommandInDockerContainer($command, $imageName)
{
$returnVal = 0;
system('docker run --rm -it -v $PWD:/app -w /app '.$imageName.' '.$command, $returnVal);
return $returnVal;
}

One last thing: Remember to add the execution bits to the composer file, otherwise you will be able to use it only as argument to PHP.

As of the previous script, we want the codeception to be exactly like if we installed locally, but undeline we will pass everything into a docker container.

Since this time we don’t have to build nothing, the script will be much simpler:

bin/codecept

#!/usr/bin/env php
<?php
include_once
('lib/docker.php');

$imageName = 'php:5.6-cli-alpine';
$arguments = implode(' ', array_slice($argv, 1));

$command = 'vendor/bin/codecept '.$arguments;
$returnVal = executeCommandInDockerContainer($command, $imageName);

exit($returnVal);

If you noted, this command will use our local codecept binary, installed through composer. So in a new installation we will need to remember to install it first.

This ends Part 2. Thanks for sticking around!

In Part 3, we will start to write automated tests
Stay tuned!

--

--