Continuous integration using Travis CI and Behat
This note begins a series of notes on application development and deployment processes. To start with, I will show how you can relatively easily implement a code quality and functionality checking process using the Travis CI service.
The practice of continuously checking the build for defects is called Continuous Integration.
The question of writing tests will not be addressed in this article - it is the subject of another article. It is assumed that you have already set up Behat, and the tests can already be run locally using Selenium.
To begin with, let's check if there are any rule violations in the code. This will allow you to familiarize yourself with and start using Travis CI. If you don't have any tests, I strongly recommend at least checking the code style.
The justification for the importance of code consistency and style can be found in many places, for example, Steve McConnell's "Code Complete", part 7, section 31.
To do this, we use PHP-CS-Fixer.
You can put the settings in the .php_cs file in the root of the project. Here is my config:
<?php return PhpCsFixer\Config::create() ->setRiskyAllowed(true) ->setCacheFile(__DIR__ . '/.php_cs.cache') ->setRules([ '@Symfony' => true, '@Symfony:risky' => true, '@PHP56Migration' => true, 'combine_consecutive_unsets' => true, 'array_syntax' => ['syntax' => 'short'], 'no_useless_else' => true, 'no_useless_return' => true, 'ordered_class_elements' => true, 'ordered_imports' => true, 'concat_space' => ['spacing' => 'one'], 'psr4' => true, 'strict_comparison' => true, 'strict_param' => true, 'phpdoc_align' => false, 'phpdoc_order' => true, 'phpdoc_separation' => false, 'phpdoc_to_comment' => false, 'no_empty_phpdoc' => false, 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], 'modernize_types_casting' => true, 'no_php4_constructor' => true, 'php_unit_construct' => true, 'php_unit_strict' => true, 'semicolon_after_instruction' => true, 'doctrine_annotation_indentation' => true, 'doctrine_annotation_spaces' => ['around_array_assignments' => false], 'pre_increment' => false, 'yoda_style' => false, ]) ;
Next, add to .travis.yml
language: php php: '7.2' jobs: include: - stage: codestyle-check before_script: - composer global require friendsofphp/php-cs-fixer script: - $HOME/.composer/vendor/bin/php-cs-fixer --diff --dry-run --verbose fix ./src
I immediately highlighted the codestyle-check stage, as we will add a stage later for running tests as well.
The php-cs-fixer package is installed separately because it is not necessary to check the vendor style - this speeds up the check.
Add another stage for running behavioral tests. For this, you need a fully functional application.
We will use docker and docker-compose to simplify managing the interaction between containers.
To begin with, you need to create a Dockerfile for the application. Because of the specific nature of PHP (because PHP itself is not a web server), I created an image that contains PHP and Nginx. I immediately made it into a separate, base image, so that it can be reused for production later on. Supervisor was used to manage running processes. You can familiarize yourself with the image here: https://github.com/harentius/docker-blog-bundle-base
Based on this image, you can easily create a docker-compose.yml file to run behavioral tests:
version: '3.4' services: blog_app: image: harentius/blog-bundle-base container_name: blogbundle_app volumes: - ./:/app links: - mysql mysql: image: mysql container_name: blogbundle_mysql selenium: image: selenium/standalone-chrome links: - blog_app
Since the sources are already cloned in Travis, the directory with the sources (and vendors) is simply mounted (not copied) into the container
Add a stage for behavioral tests to the .travis.yml file:
language: php php: '7.2' cache: directories: - $HOME/.cache/composer jobs: include: - stage: codestyle-check before_script: - composer global require friendsofphp/php-cs-fixer script: - $HOME/.composer/vendor/bin/php-cs-fixer --diff --dry-run --verbose fix ./src - stage: behavioral-tests sudo: required services: - docker before_script: - mkdir ./tests/app/var/db-dumps - composer install --no-interaction - docker-compose -f support/ci/docker-compose.yml --project-directory=. up --build -d # Even after mysql fully started (it can be pinged by 'mysqladmin ping'), # there is still some time during which mysql for some reason is inaccessible by PDO. # Following workaround fixes that - | while ! docker exec -it blogbundle_app php -r "new PDO('mysql:host=mysql', 'root', '1111');" ; do sleep 1; done - docker exec -it blogbundle_app /app/tests/app/bin/install script: - docker exec -it blogbundle_app /app/vendor/bin/behat -c /app/behat.yml.dist
Note a few tricks:
- To speed up subsequent launches, the Composer cache directory is cached
- /bin directory is non-standard because it is not testing a project, but a standalone bundle, for which an application is made to start
- A mechanism for checking mysql availability from the php container was used. The problem is that mysql becomes available for PDO much later than it starts pinging with mysqladmin ping. If someone figures this out and writes the reason in the comments, I will be sincerely grateful. =)