Ejemplos de distintos frameworks

3.1 Introducción al ejemplo a testear

Como ejemplo para ilustrar test unitarios, vamos a testear una clase desarrollada para este capítulo: la clase XString. XString es una clase desarrollada para aumentar la expresividad del tipo de datos string de PHP. Está inspirada en un pequeño proyecto escrito en el lenguaje de programación Go, alojado en https://github.com/huandu/xstrings. El código de nuestro ejercicio también está en Github, en la url https://github.com/josgilmo/xstring.

3.2 PHPUnit

Como comentábamos en el capítulo anterior, PHPUnit [1] es el framework más usado para testear proyectos en PHP y por ello lo vamos a usar en esta para testear XString.

3.2.1 Instalación

En la documentación oficial de PHPUnit podemos encontrar varias formas de ser instalado. Nosotros vamos a documentar la que creemos que tiene más ventajas y es la instalación de PHPUnit en nuestro proyecto como dependencia de Composer.

Para instalar PHPUnit con Composer necesitamos añadir la dependencia como podemos ver en el siguiente texto, que debe estar en el fichero composer.json de la raíz de nuestro proyecto:


{
    "require-dev": {
        "phpunit/phpunit": "5.0.*"
    }
}

El comando a ejecutar para instalar esta y el resto de dependencias es:

composer install

3.2.2 Organización de un proyecto PHP

Antes de ver algún ejemplo del código de nuestra clase XString y sus correspondientes tests unitarios necesitamos documentar cuales son las recomendaciones para organizar un proyecto PHP siguiendo PSR-4 [2]. PSR-4 es un estándar documentado por PHP-FIG [2] que determina como debe organizarse el código en namespaces y cómo debe funcionar el autoload de clases.

La estructura básica en la carpeta raíz de un proyecto php debería contener los siguientes ficheros y carpetas:

* src/
* tests/
* composer.json
* phpunit.xml

En la carpeta src/ tendremos el código de la aplicación. En la carpeta test/, tendremos el código de los tests. El fichero phpunit.xml es el fichero que define la configuración que ejecutará los tests de PHPUnit y es cargado por defecto cuando ejecutamos PHPUnit sin ningún otro parámetro.

El contenido de nuestro fichero phpunit.xml es el siguiente:

<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="tests/bootstrap.php" colors="true">
    <testsuites>
        <testsuite name="XStrings Test Suite">
            <directory>tests/XString/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">src/XString/</directory>
        </whitelist>
    </filter>
</phpunit>

En este fichero estamos especificando que, antes de ejcutar los tests, debe incluir el fichero tests/bootstrap.php, cuyo contenido es:

$loader = require __DIR__ . "/../vendor/autoload.php";
$loader->addPsr4('XString\\', __DIR__.'/XString');

Necesitamos cargar este fichero para que en la ejecución de los tests, nuestras clases sepan resolver la localización de los ficheros necesarios mediante el estándar *PSR-4.

Además, en nuestro fichero phpunit.xml estamos creando un Suite de tests, que es la forma de organizar los tests. También estamos especificando, mediante un filter, que los ficheros que vamos a incluir acaban con la extensión php.

Las características básicas para realizar tests unitarios utilizando PHPUnit son:

  • Dada una clase dentro de la carpeta src, crearemos una clase de tests en la carpeta tests con el mismo nombre que pero con el sufijo "Test" donde desarrollaremos los tests. Por ejemplo, para testear la clase Clase.php, crearemos la clase ClaseTest.php.

  • Cada método de testeo que deseemos ejecutar deberá ser método público que comenzará por el prefijo "test".

3.2.3 Ejemplo de código con su correspondiente test unitario

A continuación presentamos un fragmento de código de la clase XString. Para evitar que sea muy extenso hemos incluido solo dos métodos de nuestra clase XString, startWith y endWith.


class XStrings {

    protected $str;

    /**
     * Construct
     *
     * @param string  $str          Str var.
     * @param string  $encoding    Encoding.
     * @param boolean $forceEncode Force encoding.
     *
     * @return void
    */
    public function __construct($str, $encoding = 'UTF-8', $forceEncode = true)
    {
        if (mb_detect_encoding($str) != 'UTF-8' && $forceEncode) {
            $str = mb_convert_encoding($str, 'UTF-8');
        }
        $this->str = $str;
    }

    /**
     * Return true, if the string starts with $prefix
     *
     * @param string $prefix Prefix string.
     *
     * @return boolean
    */
    public function startWith($prefix)
    {
        return mb_substr($this->str, 0, mb_strlen($prefix)) === $prefix;
    }

    /**
     * Return true, if the string ends with $suffix
     *
     * @param string $suffix Suffix string.
     *
     * @return boolean
    */
    public function endWith($suffix)
    {
        return mb_substr($this->str, mb_strlen($this->str) - mb_strlen($suffix), mb_strlen($suffix)) == $suffix;
    }
}

Y a continuación presentamos la clase de testeo con los dos tests necesarios para verificar que la implementación de los métodos startWith y endWidth cumplen la especificación deseada. Como podemos ver, los métodos de testeo se llaman comenzando por el prefijo test como indicábamos anteriormente.


class XStringsTest extends PHPUnit_Framework_TestCase{

    /**
     * Test for startWith method. 
     *
     * @return void
    */
    public function testStartWith()
    {
        $xstring = new XString('hello world');
        $this->assertTrue($xstring->startWith('hello'));
        $this->assertFalse($xstring->startWith('world'));
    }

    /** 
     * Test for endWith method.
     *
     * @return void
    */
    public function testEndWith()
    {
        $xstring = new XString('hello world');
        $this->assertTrue($xstring->endWith('world'));
        $this->assertFalse($xstring->endWith('hello'));
    }
}

Para ejecutar los tests debemos lanzar el siguiente comando desde la carpeta de nuestro proyecto:

bin/vendor/phpunit

Debemos ejecutar este comando porque estamos utilizando la versión de PHPUnit instalada para este proyecto concreto y no le incluimos ningún parámetro al comando porque lo especificamos en el fichero phpunit.xml, tal y como indicamos anteriormente. Instalar y ejecutar una versión de PHPUnit especificada en el fichero Composer nos ofrece algunas ventajas. Una vez publicado el proyecto, cualquier desarrollador puede ejecutar los tests en las condiciones más parecidas en las que se ha desarrollado, manteniendo incluso la versión de PHPUnit. Además, para sistemas de integración continua, como veremos más adelante, tendremos también controlada la versión sobre la que se integra nuestro código.

El resultado de la ejecución con todos los tests es el siguiente:

PHPUnit 5.0 by Sebastian Bergmann and contributors.

................................

Time: 1.09 seconds, Memory: 12.75Mb

OK (32 tests, 40 assertions)

Si seguimos desarrollando y cometemos un error, como hemos hecho intencionadamente, por el que los tests unitarios no pasarían, veríamos un resultado similar a este:

PHPUnit 5.0 by Sebastian Bergmann and contributors.

.F..............................

Time: 1.13 seconds, Memory: 12.75Mb

There was 1 failure:

1) Test\XStringTest::testStartWith
Failed asserting that false is true.

/home/jose/workspace/MyThings/TestingBook/Strings/tests/XString/XStringTest.php:50
phar:///usr/local/bin/phpunit/phpunit/TextUI/Command.php:151
phar:///usr/local/bin/phpunit/phpunit/TextUI/Command.php:103

FAILURES!
Tests: 32, Assertions: 39, Failures: 1.

El ejemplo que hemos tratado para documentar PHPUnit es muy sencillo. En él solo hemos visto dos tipos de aserciones: assertTrue y assertFalse. En la documentación de PHPUnit podemos ver los distintos tipos de aserciones: https://phpunit.de/manual/current/en/appendixes.assertions.html.

3.3 Atoum

Otro framework de testeo en PHP es Atoum [7]. En la página oficial de GitHub se define como simple, moderno e intuitivo para testeo unitario en PHP. Según la especificación Atoum ejecuta cada test en un proceso separado de PHP, lo cual garantiza aislamiento entre tests. Además, promete una interfaz fluida y mayor facilidad de crear Stubs (algo que veremos en el capítulo de tests dobles) gracias a al uso de funciones anónimas y closures.

Otra diferencia notable con respecto a PHPUnit es la sintaxis. Según los autores es más legible y soporta un estilo de testeo con la expresividad requerida por BDD.

3.3.1 ¿Quién utiliza Atoum?

Dado que PHPUnit es el referente en testeo en PHP, pensar en una alternativa y utilizarla en nuestros proyectos puede sonar arriesgado, pero hay algunos proyectos, como algunos de los proyectos de Hoa Project (http://hoa-project.net/En/), utilizan Atoum. Uno de estos proyectos es el propio entorno de testing de la familia Hoa: https://github.com/hoaproject/Test.

Aunque Atoum no tiene la misma difusión y aceptación que PHPUnit, hay un número no despreciable de proyectos que han decidido basar sus test en este framework. Algunos de estos proyectos son:

Aunque ninguno de estos proyectos son grandes proyectos, son ejemplos de como algunos desarrolladores han decidido salir de la comodidad de usar PHPUnit y escoger una alternativa. Al final, lo importante es ejercitar el código que desarrollamos para garantizar su estabilidad en el futuro y mejor comprensión.

3.3.2 Instalación

Al igual que hicimos con PHPUnit, y aunque hay más formas de instalar Atoum, documentaremos Composer como método de instalación, con el siguiente código del fichero composer.json:

{
    "require-dev": {
        "atoum/atoum": "^2.2"
    }
}

Ejecutamos el comando de actualizazión de Composer:

composer install

Para verificar que tenemos Atoum instalado podemos ejecutar:

vendor/bin/atoum -v

Y veremos como respuesta la versión instalada de Atoum:

atoum version 2.2.2 by Frédéric Hardy (./vendor/atoum/atoum)

3.3.3 Organización del proyecto para Atoum

Reutilziaremos la estructura del proyecto XStrings y añadiremos una carpeta nueva para crear los tests unitarios: ./test-atoum/unit, donde crearemos el fichero de testing XStringTest.php.

3.3.4 Ejemplo de código con Atoum

Para ilustrar la sintaxis de Atoum vamos a crear test unitarios para nuestra clase XString, de la misma forma que lo hicimos con PHPUnit. Viendo el código vemos algunas de las diferencias que hay entre la sintaxis de Atoum y PHPUnit, por ejemplo las aserciones. PHPUnit mantiene la forma de "assertXXX", donde XXX es el tipo de aserción. Sin embargo, Atoum tiene otra sintaxis totalmente distinta. En cada asserción realiza una comprobación de tipo de datos, con los métodos "boolean", "string", ... y posteriormente llama a un método para comprobar la relación entre el resultado esperado y el obtenido tras ejercitar el SUT en cuestión. Estas funciones son del tipo: isTrue, isEqualTo, ...

Un ejemplo de código para visualizar estas diferencias podría ser:


<?php

namespace XString\test\units;

require_once 'src/XString/XString.php';

use \mageekguy\atoum;
use XString\XString as XS;

class XString extends atoum\test
{

    /**
     * Test for startWith method. 
     *
     * @return void
    */
    public function testStartWith()
    {
        $xstring = new XS("hello word");
        $this->boolean($xstring->startWith("hello"))->isTrue();
    }

    /** 
     * Test for endWith method.
     *
     * @return void
    */
    public function testEndWith() {
        $xstring = new XS("hello world");
        $this->boolean($xstring->endWith("world"))->isTrue();
    }

}

Para ejercitar nuestros tests ejecutaremos el comando:

vendor/bin/atoum test-atoum/units/XStringTest.php

Y tendremos como resultado:

> PHP path: /usr/bin/php5
> PHP version:
=> PHP 5.5.30-1+deb.sury.org~precise+1 (cli) (built: Oct 4 2015 16:14:34)
=> Copyright (c) 1997-2015 The PHP Group
=> Zend Engine v2.5.0, Copyright (c) 1998-2015 Zend Technologies
=>     with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2015, by Zend Technologies
=>     with Xdebug v2.3.2, Copyright (c) 2002-2015, by Derick Rethans
> XString\test\units\XString...
[SSS_________________________________________________________][3/3]
=> Test duration: 0.08 second.
=> Memory usage: 0.25 Mb.
> Total test duration: 0.08 second.
> Total test memory usage: 0.25 Mb.
> Code coverage value: 70.45%
=> Class XString\XString: 70.45%
==> XString\XString::delete(): 0.00%
==> XString\XString::insert(): 0.00%
==> XString\XString::lastPartition(): 0.00%
> Running duration: 0.25 second.

La forma en la que Atoum nos reporta el resultado de la ejecución de los tests unitarios también es distinta de PHPUnit. Por defecto, Atoum nos indica la cobertura de tests en cada uno de los métodos públicos que contiene la clase a testear. En nuestro ejemplo hemos recortado la salida de la ejecución de los tests, dado que en el momento de esta ejecución teníamos tres métodos testeados y para el resto de métodos nos reportaba un 0.00% de cobertura, como nos indica en los métodos "delete", "insert" y "lastPartition".

results matching ""

    No results matching ""