Build and test PHP applications with Composer and PHPUnit

Just for playing around a bit with php and its eco system as a guy who is used to rely on java mechanisms.

Versions

component version download
Ubuntu 16.04 http://www.ubuntu.com/download/desktop
PHP 7.0.8 http://php.net/downloads.php

The example project:

├── build.sh
├── composer.json
└── src
    ├── main
    │   └── php
    │       ├── classes
    │       │   └── Translator.php
    │       └── index.php
    └── test
        └── php
            └── TranslatorTest.php

There is a minimal „business logic“ in this example project called Translator.

<?php

class Translator{

  public function Translator(){

  }

  public function translateDeToEn($message){
    // some code goes here
    if ($message == 'hallo'){
      return 'hello';
    }
    else {
      return 'not in dictionary';
    }
  }
}

?>

This Translator class is used in index.php, that will be called in CLI mode.

<?php
spl_autoload_register(function ($class) {
    @require_once('classes/' . $class . '.php');
});

$translator = new Translator();
$deMessage = $argv[1];
$enMessage = $translator->translateDeToEn($deMessage);

echo "$enMessage\n";

?>

Basically this is all and this app can be started this way:

christoph@hephaistos:example-php$ /usr/bin/php7.0 src/main/php/index.php hallo
hello
christoph@hephaistos:example-php$ /usr/bin/php7.0 src/main/php/index.php blubb
not in dictionary

But as professional software developers we also want to test our logic implementation. For PHP there is a nice solution called PHPUnit. This is a depencency library that can be included in our application by great Composer framework. Therefor first make sure, that you have „installed“ (i.e. copied) the composer.phar to $COMPOSER_HOME according to this manual: https://getcomposer.org/download/

After successful installation, you can call composer this way:

christoph@hephaistos:example-php$ /usr/bin/php7.0 $COMPOSER_HOME/composer.phar --version
Composer version 1.2.0 2016-07-19 01:28:52

Next step is to create a composer.json (similar to requirements-file in python context or pom.xml in maven context):

{
    "require-dev": {
        "phpunit/phpunit": "5.5.0"
    }
}

and now you can install the dependencies in project-local vendor directory via:

christoph@hephaistos:example-php$ /usr/bin/php7.0 $COMPOSER_HOME/composer.phar update

Composer has loaded now the PHPUnit and all transitive dependencies to $BASE_DIR/vendor. There is also a composer.lock file created. This file represents something like a pip freeze and contains all versions of composer installed transitive dependencies. Keeping this file might be helpful reproducing build if some dependencies are using kind of „latest“ references. For more information see here. The PHPUnit library can be used in test classes:

<?php
spl_autoload_register(function ($class) {
    @require_once('src/main/php/classes/' . $class . '.php');
});

class TranslatorTest extends PHPUnit_Framework_TestCase {

  public function testTranslateDeToEnPositive(){
    $expectedResult = 'hello';

    $translator = new Translator();
    $deMessage = 'hallo';
    $actualResult = $translator->translateDeToEn($deMessage);

    $this->assertEquals($expectedResult, $actualResult);
  }

  public function testTranslateDeToEnNegative(){
    $expectedResult = 'not in dictionary';

    $translator = new Translator();
    $deMessage = 'spongebob';
    $actualResult = $translator->translateDeToEn($deMessage);

    $this->assertEquals($expectedResult, $actualResult);
  }

}

?>

PHPUnit tests can be run by executing following command:

christoph@hephaistos:example-php$ vendor/phpunit/phpunit/phpunit --debug --log-junit=php-unit.xml src/test/php
PHPUnit 5.5.0 by Sebastian Bergmann and contributors.


Starting test 'TranslatorTest::testTranslateDeToEnPositive'.
.
Starting test 'TranslatorTest::testTranslateDeToEnNegative'.
.                                                                  2 / 2 (100%)

Time: 24 ms, Memory: 4.00MB

OK (2 tests, 2 assertions)

This executes all tests that are found in src/test/php and creates a php-unit.xml result in $BASE_DIR:

<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
  <testsuite name="src/test/php" tests="2" assertions="2" failures="0" errors="0" time="0.002224">
    <testsuite name="TranslatorTest" file="/home/christoph/example-php/src/test/php/TranslatorTest.php" tests="2" assertions="2" failures="0" errors="0" time="0.002224">
      <testcase name="testTranslateDeToEnPositive" class="TranslatorTest" file="/home/christoph/example-php/src/test/php/TranslatorTest.php" line="8" assertions="1" time="0.002044"/>
      <testcase name="testTranslateDeToEnNegative" class="TranslatorTest" file="/home/christoph/example-php/src/test/php/TranslatorTest.php" line="18" assertions="1" time="0.000180"/>
    </testsuite>
  </testsuite>
</testsuites>

This all is encapsulated in a build.sh script:

#!/bin/bash
BASE_DIR=$(pwd)
SRC_DIR=$BASE_DIR/src/
TARGET_DIR=$BASE_DIR/target/
VENDOR_DIR=$BASE_DIR/vendor/
PHP_EXE=/usr/bin/php7.0
PHP_SRC=$SRC_DIR/main/php/
PHP_TEST=$SRC_DIR/test/php/
COMPOSER=$COMPOSER_HOME/composer.phar
PHPUNIT=$VENDOR_DIR/phpunit/phpunit/phpunit

# clean
echo ">> CLEAN"
rm -rfv $TARGET_DIR $VENDOR_DIR composer.lock

# init
echo ">> INIT"
$PHP_EXE --version
mkdir $TARGET_DIR

# test-prepare
echo ">> TEST-PREPARE"
$PHP_EXE $COMPOSER update

# test
echo ">> TEST"
$PHPUNIT --debug --log-junit=$TARGET_DIR/tests/php-unit.xml --testdox-html=$TARGET_DIR/tests/testdox.html $PHP_TEST

# package
echo ">> PACKAGE"
cd $PHP_SRC && zip -r $TARGET_DIR/example-php.zip .

# run the app
#$PHP src/main/php/index.php hallo