Download as pdf or txt
Download as pdf or txt
You are on page 1of 117

Testeo

de código en desarrollos PHP.

Tabla de contenido
1. Introducción 0
2. Agradecimientos 1
3. Capítulo 1. Introducción 2
4. Capítulo 2. Primer acercamiento a tests 3
5. Capítulo 3. Ejemplos de distintos *frameworks* 4
6. Capítulo 4. Aislamiento de tests e interacción con datos 5
7. Capítulo 5. Testeando bases de datos 6
8. Capítulo 6. Test dobles 7
9. Capítulo 7. TDD 8
10. Capítulo 8. BDD 9
11. Capítulo 9. Codeception: Solución todo en uno 10
12. Capítulo 10. Testeando APIs 11
13. Capítulo 11. Integración continua 12
14. Capítulo 12. Test de proyectos existentes 13
15. Capítulo 13. Más Tests 14
16. Conclusiones 15
17. Bibliografía 16

2
Testeo de código en desarrollos PHP.

Introducción
[TODO: Reeditar].

En los últimos años el lenguaje de programación PHP ha experimentado una profesionalización por
parte de la industria, debido principalmente, a que algunas de las empresas más importantes de
internet como Facebook.com están desarrolladas en este lenguaje. Una de las partes del desarrollo en
PHP que más crecimiento ha experimentado ha sido la forma de testear el código, y las distintas
herramientas y metodologías que se han adaptado al ecosistema PHP. Este proyecto trata de presentar
dichas herramientas, las alternativas y los problemas que se encuentran normalmente proyectos PHP.

En nuestra opinión, los acontecimientos que han revolucionado la comunidad PHP han sido un
conjunto de herramientas, buenas prácticas y standards, los cuales han propiciado una comprensión
unánime hacia tener testeado un proyecto PHP, como prueba de ello podemos ver al estudiar el código
de cualquier proyecto PHP en http://github.com. La comunidad detrás de PHP-FIG[1] está motivando
a que los desarrollos en PHP sigan estándares que faciliten la modularización y reutilización de dichos
componentes[2]. Bajo esta premisa de poder compartir código es necesario que el comportamiento de
dichos componentes sea predecible y estable, y para ello la comunicad desarrolla testing[3]s en las
distintas fases del proyecto, y en distintos niveles de testeo (unitarios, funcionales, de integración o de
aceptación).

Para ello existen herramientas como PHPUnit[4] y phpspec[5] que facilitan la tarea del testo. Los
principales frameworks de la comunidad PHP como pueden ser Symfony, Laravel o Silex, tienden ha
implementar este tipo de estrategias, tal y como podemos ver en las separación de componentes
independientes de Symfony.

Este proyecto trata de documentar, analizar las distintas metodologías y herramientas que han surgido
en los últimos años para testear aplicaciones y desarrollos web en PHP, a través de ejemplos prácticos
cuando sea posible.

Introducción 3
Testeo de código en desarrollos PHP.

Agradecimientos
Este proyecto no hubiera sido posible sin el apoyo que he recibido de muchas personas, a las que se lo
quiero agradecer:

A mi hijo de un año Saúl y a su madre, mi chica, Mari Carmen, por apoyarme en todo
incondicionalmente.

A mi toda familia. En especial a mis padres y hermana, por la paciencia que han tenido en
darme la formación y la educación que he recibido. Tengo que dar gracias por el cariño que
recibí durante todo el tiempo que estudié en Málaga, las facilidades que tuve, los buenos
consejos, y la insistencia porque finalizara mis estudios.

A mis compañeros de estudios. Sin ellos no hubiera podido disfrutar y padecer de todos los
momentos que viví estudiando la ingeniería. A los hermanos Sergio y Javier Jimenez Gonzalez,
a Jaime Gálvez Cordero, Emilio Lucena, Juan Antonio Morales, etc...

A mis amigos y compañeros de viaje universitario. Aquellos que compartían conmigo cada
mañana el autobús o el coche desde Torre del Mar a la escuela en Teatinos. De las
conversaciones que mantuvimos aprendí parte de lo que me enseñó la universidad.

Quiero dar las gracias a todos los profesores, docentes, investigadores y resto de personal
universitario. Sin ellos no sentiría lo que siento ahora por las ciencias de la computación, y no
podría trabajar en algo que me gusta, divierte e ilusiona. En especial quiero agradecer a
Francisco Villatoro. Siempre ha estado disponible para ser mi director de proyecto, además de
realizar una gran labor de divulgación científica.

A mis compañeros de trabajo. En especial a los de las dos empresas a las que he trabajado más
tiempo, Salir.com y Trovit SL. Algunos de los compañeros de trabajo a los que quiero
agradecer especialmente son: Jordi Salvat, Diego Campoy, Sergi de Pablos, Xose Pérez, Raül
Perez, Gorka Lopez, Issel Guberna, Carlos Falo, etc, etc.

Agradecimientos 4
Testeo de código en desarrollos PHP.

Capítulo 1. Introducción
Introducción
1.1 Introducción al proyecto
En los últimos años el lenguaje de programación PHP ha experimentado una evolución por parte de la
industria, debido principalmente, a que algunas de las empresas más importantes de internet como
Facebook.com están desarrolladas en este lenguaje.

Una de las partes del desarrollo en PHP que más crecimiento ha experimentado ha sido la forma de
realizar pruebas automáticas del código y las distintas herramientas y metodologías que se han
adaptado en ecosistema PHP. En este proyecto vamos a trata de presentar dichas herramientas, las
alternativas y los problemas que podemos encontrarnos normalmente al desarrollar y especialmente
testear proyectos PHP.

En nuestra opinión, los acontecimientos que han revolucionado la comunidad PHP han sido la
adopción de un conjunto de herramientas, buenas prácticas y estándares por parte de desarrolladores y
empresas. De este conjunto de herramientas y estándares la comunidad a comprendido los beneficios
de tener testeado un proyecto PHP. Como prueba de ello podemos ver al estudiar el código de
cualquier proyecto PHP alojado en GitHub, que la mayor parte tiene un gran conjunto de pruebas
automáticas en sus respectivos repositorios.

La comunidad detrás de PHP-FIG está motivando a que los desarrollos de PHP sigan estándares que
faciliten la modularización y reutilización componentes. Bajo esta premisa de poder compartir código
es necesario que el comportamiento de dichos componentes sea predecible y estable. Para ello la
comunicad desarrolla tests en las distintas fases del proyecto y a distintos niveles (unitarios,
funcionales, de integración o de aceptación).

En este PFC intentaremos dar visibilidad a las distintas herramientas, centrándonos especialmente a
las que están publicadas bajo licencias de código abierto. También estudiaremos las distintas
metodologías que podemos realizar cuando hablamos de testo automático.

Realizar pruebas automáticas no es lo mismo que adquirir los conocimientos de programación


necesarios, pero tanto en la vida profesional, como en la académica o comunitaria (nos referimos a
comunidades de software), entregar con un código de mayor calidad es mejor para todos. Con este
PFC hemos intentado aportar algo de conocimiento sobre este area, a riesgo de que esté cubierto en la
actualidad.

1.2 Contenido de la memoria


Capítulo 1 "Introducción". Es el capítulo actual, en el que damos una pequeña introducción y
objetivos del proyecto.

Capítulo 2 "Primer acercamiento a tests". En este capítulo definiremos algunos conceptos


relacionados con el testeo, identificaremos los distintos tipos de tests y comentaremos algunas
proyectos como Git y Composer, que son dependencia para entender el resto de ecosistema
relacionado a desarrollo en PHP.

Capítulo 3 "Ejemplos de distintos frameworks". Este capítulo lo dedicaremos como

Capítulo 1. Introducción 5
Testeo de código en desarrollos PHP.

introducción práctica para testeo. Para ello crearemos unos tests sencillos con PHPUnit y
Atoum para una clase de manipulación de cadenas de texto.

Capítulo 4 "Aislamiento de tests e interacción con datos". Es este capítulo expondremos la


importancia de testear en una situación controlada y estudiaremos diferentes herramientas
especializadas en crear datos de entradas para testear.

Capítulo 5 "Testeando bases de datos". En este capítulo explicaremos los problemas que
existen al testear código con dependencia de una base de datos. Veremos un ejemplo para
documentar la forma en la que se podría testear códigos con este tipo de dependencias.

Capítulo 6 "Test dobles". En este capítulo analizaremos el problema que existe al testear con
dependencias y analizaremos distintas formas de resolver ese problema, así como distintas
alternativas a herramientas que ayudar a este efecto.

Capítulo 7 "TDD". En este capítulo estudiaremos la metodología de desarrollo guiado por los
tests. Estudiaremos los distintos pasos de la metodología a través de un ejemplo y veremos una
herramienta para desarrollar : Phpspec.

Capítulo 8 "BDD" Este capítulo está dedicado a estudiar el desarrollo guiado por
comportamiento (behavior-driven development), así como la principal librería dedicada de PHP
para ello: Behat. Veremos como utilizar Behat con un ejemplo como test de aceptación, aunque
expondremos las bases para usarlo como herramienta de BDD. Al final del capítulo veremos
ligeramente dos alternativas a Behat, Pho y Peridot.

Capítulo 9 "Codeception: Solución todo en uno". En el capítulo 9 estudiaremos una


herramienta para testeo en PHP con capacidad de generar los tres principales tipos de tests,
unitarios, de integración ty de aceptación. Veremos algunas diferencias entre otras alternativas y
la forma de uso para un ejemplo como test de aceptación.

Capítulo 10 "Testeando API". En este capítulo veremos como testear la API pública de un
servicio online, Flickr.com, siguiendo las dos alternativas a las que podemos optar: mockear o
conexión. Veremos los problemas y ventajas al optar una u otra alternativa.

Capítulo 11 "Integración continua". En este capítulo explicaremos el concepto de la


integración continua. Además desarrollaremos dos ejemplos para ilustrar el proceso de
integración continua, utilizando Jenkins y Travis CI.

Capítulo 12 "Test de proyectos existentes". En este capítulo veremos una herramienta,


Humbug, para análisis de test de otros proyectos. Estudiaremos los resultados de esta
herramienta sobre dos frameworks PHP, Slim y Silex.

Capítulo 13 "Mas Mests". En este capítulo recopilaremos algunas situaciones de testeo no


triviales y expondremos diferentes debates relativos al testeo de proyectos en PHP.

Conclusión. En este capítulo se describen las conclusiones y aprendizajes obtenidos con este
proyecto, así como futuras lineas de trabajo.

Bibliografía. Se describe el material utilizado para realizar este proyecto, tanto libros como
páginas web.

Capítulo 1. Introducción 6
Testeo de código en desarrollos PHP.

Capítulo 2. Primer acercamiento a tests


Primer acercamiento a tests
2.1 ¿Por qué, cuándo y cómo hacer tests?
Testear un proyecto de software es una garantía. Michael Feathers en define legacy code [8] como
aquel fragmento de código que carece de test. Un conjunto de tests relativo a un fragmento de código
expresa el contrato escrito de ese código. Los beneficios de testear están ampliamente extendidos en
prácticamente todas las comunidades de software y son fácilmente contrastables. Por una parte al
tener un conjunto de tests que verifiquen el comportamiento de determinado software, futuros
cambios mantendrán tal funcionamiento, o indicarán, a través de la ruptura de test, que dicho
comportamiento ha cambiado. Esto es lo que llamamos test de regresión.

Además de la estabilidad a la hora de desarrollar, el hecho de seguir patrones de testeo obligan al


desarrollador a pensar en otras buenas prácticas para facilitar la testeabilidad del código. O dicho de
otra forma, un código testeado necesariamente es mejor, según métricas de calidad de código, que
otro no testeado. Algunas de estas recomendaciones o exigencias por parte de la testeabilidad son:

Principio de responsabilidad simple: Esto quiere decir que una clase debe ser responsable de
realizar una única tarea. De esta forma, el código estará mejor estructurado y será más fácil de
testear.

Clases con un número de lineas tendiendo a pequeño. Es más fácil de testear una clase de 200 a
300 lineas con un conjunto de métodos públicos coherentes que no clases con miles de lineas y
múltiples responsabilidades, también llamadas "Clases Dios". Estas "Clases Dios" es un
antipatrón conocido y debe ser evitado.

Métodos pequeños: Testear métodos de 10 a 20 líneas debe ser relativamente sencillo, dado que
la complejidad ciclomática de dichos métodos tiene que ser reducida.

Desacoplamiento: Cuando nos referimos a test unitarios, desacoplar el código hace posible el
testeo, por lo cual algunos entornos de desarrollo facilitan inyectores de dependencias para
aislar las responsabilidades de cada pieza de código facilitando el testeo.

2.2 ¿Cuándo testear?


En función de cuando realizamos los tests de nuestro desarrollo, podemos hablar de tres formas.

2.2.1 Antes

TDD (Test-Driven Development, o en español, Desarrollo guiado por tests) es una metodología que
recomienda realizar los test antes de escribir el código que pasaría dicho test. La comunidad defensora
de hacer TDD asegura que de esta forma se diseña mejor la aplicación y se garantiza una cobertura de
test dado que el test está implementado.

2.2.2 Durante

Mientras se va desarrollando, de forma complementaria al desarrollo en sí, programar y ejecutar los


tests ayudan a mantener la calidad de código como mencionábamos anteriormente y a asegurar el

Capítulo 2. Primer acercamiento a tests 7


Testeo de código en desarrollos PHP.

funcionamiento del software tal y como la especificación determina.

2.2.3 Después

Seguir escribiendo tests tanto para modificaciones futuras como para solución de bugs detectados a
posteriori, son una buena práctica a mantener. Por una parte, a mayor cobertura de tests mayor
confianza se debe tener en que seguirá el correcto funcionamiento. Por otra parte, al detectar un error,
resolverlo y añadir el test que previene que ese bug no vuelva a suceder en el futuro. Esto mejora la
calidad del conjunto de tests y la estabilidad del proyecto.

2.3 ¿Siempre se puede puede testear?


Aunque la respuesta parezca afirmativa, en algunas situaciones puede resultar casi imposible testear.
Estas situaciones normalmente ocurren cuando la calidad de código es muy pobre, sin diseño, apenas
orientación a objetos y malas prácticas a cualquier nivel de código. Frente a este tipo de situaciones
introducir testing suele ser bastante complicado, aunque no imposible. Realizar tests en situaciones así
es análogo a rediseñar y refactorizar el código, con la complejidad que refactorizar un proyecto sin
tests conlleva. La probabilidad de introducir nuevos errores o modificar el comportamiento de la
aplicación en aplicaciones sin tests es bastante alta.

Siempre que se aborde a una refactorización de código, debemos tener un conjunto de tests para que
el resultado de la refactorización sea el mismo que el previo a tal refactorización.

2.4 Tipos de test


Según las dependencias con otros sistemas o partes del paquete o software, los tests se pueden
clasificar de la siguiente forma.

2.4.1 Unitarios

Testean la más pequeña unidad funcional, generalmente un método público o una función. El test
unitario debe de centrarse únicamente en probar el comportamiento de dicha función y además debe
residir en memoria. Para ello, un test unitario no deberá nunca acceder a otra funcionalidad o tener
dependencia de otra parte del sistema, como pueden ser: otra clase del mismo paquete, acceso a red,
acceso a base de datos, uso de ficheros de disco o ejecutar otro proceso. Cualquier tipo de
dependencia con estos u otros sistemas deberían de simularse mediante mocks, stubs,... De este tipo
de objetos hablaremos en los próximos capítulos.

2.4.2 Integración

Un test de integración prueba un conjunto funcionalidades de forma conjunta. Este tipo de test es
responsable de verificar que el resultado de la ejecución del sistema a testear es el esperado. Los tests
de integración se diferencian de los tests unitarios en que pueden acceder a disco, base de datos, red,
etcétera, con el objetivo de encontrar errores que los tests unitarios no pueden detectar por sí solos.
Debido a esto, los tests funcionales requieren de un entorno de tests lo más parecido posible al
entorno de producción.

2.4.3 Test funcionales

Los tests funcionales verifican el correcto funcionamiento de una funcionalidad determinada, dados
unos valores de entradas contra una especificación concreta. Los tests funcionales no son conscientes
de valores intermedios o efectos colaterales en la ejecución, sólo en el resultado de la ejecución. En

Capítulo 2. Primer acercamiento a tests 8


Testeo de código en desarrollos PHP.

desarrollos de aplicaciones web los test funcionales realizan simulaciones de ejecución de


controladores (en patrón de diseño Modelo-Vista-Controllador los controladores son las clases
responsables de ejecutar la lógica de negocio y asignarla a la vista) y devuelven la respuesta de forma
análoga a como lo haría un servidor web como Apache.

2.4.4 Tests de aceptación

En el mundo Agile [44], se considera test de aceptación a los tests que satisfacen las historias de
usuarios definidas como especificación del programa o User Stories. Si el test pasa se puede
considerar que el software cumple con la especificación y que la historia de usuario se puede
considerar como completa. Para realizar este tipo de tests se utilizan simuladores web como puede ser
Selenium. Tambien se necesita que la aplicación sea totalmente operativa y funcional, dado que no
solo se testea la implementación en PHP, sino todo el sistema en conjunto, incluyendo la interacción
con el usuario mediante JavaScript.

Consideramos que estos tipos de tests son los más importantes, aunque hay documentación que habla
de otro tipo de tests en función del nivel de testeo o de la especificación de los tests [9].

2.5 SUT y colaborador


En la literatura escrita sobre tests y más en concreto sobre tests unitarios, se utiliza de forma
extendida el concepto de sistema bajo tests u objeto bajo tests, con la abreviatura SUT (del inglés,
system under test). Nosotros utilizaremos esta misma expresión, utilizando la abreviatura SUT para
hacer referencia a la clase que queremos testear. Se le llama colaborador a cualquier otro objeto que
afecte al sistema bajo test. El concepto de colaborador tendrá mucho más significado cuando
hablemos de test dobles, en el capítulo correspondiente a ello.

2.6 Aserciones o expectativas


En el capítulo 3 ilustraremos esto con ejemplos concretos, pero para dar una mejor visión la pregunta
"en qué consiste un test" necesitamos hablar de la unidad de validación sobre la se trabaja cuando se
testea. Esto son las aserciones. Un tests no es más que la validación de la respuesta o el
comportamiento de nuestro SUT realizado de forma programática.

Cada framework de testeo tiene su propia sintaxis de validación y un conjunto de mecanismos para
definir los distintos tipos de validaciones. Normalmente, se realiza una comparación entre la
devolución o el estado de la ejecución de nuestro SUT y el valor esperado.

Entre los distintos tipos de aserciones podemos encontrar:

Igualdad.
Igualdad estricta (en PHP el operador === es distinto al operador ==, el primero verifica
igualdad de valor y de tipo de datos, mientras que el segundo solo igualdad de el valor).
Relaciones entre Arrays (contiene clave, contiene valor...)
Una clase tiene un atributo.
Un objeto es de un tipo.
Expectativas de excepciones, es decir, la ejecución del SUT en determinadas circunstancias no
devolverá ningún valor, sino que lanzará un excepción.
etc...

2.7 Frameworks de test unitarios

Capítulo 2. Primer acercamiento a tests 9


Testeo de código en desarrollos PHP.

El framework de testeo más conocido en el mundo de desarrollo en PHP es PHPUnit [1]. PHPUnit
pertenece a la familia de xUnit. Originalmente, la familia de frameworks xUnit comienza con SUnit
en 1989, para Smalltalk, desarrollado por Kent Beck. A dicha familia de frameworks pertenecen otros
como JUnit (para Java), RUnit (para R), etc. Dado que PHPUnit es el framework de testeo más
extendido, cuando hablemos de testeo unitario nos referiremos a él, siempre que no hagamos
referencia a otro.

Además de PHPUnit en el mundo PHP han existido algunas alternativas como Atoum [7] y
SimpleTest. Respecto a SimpleTest, en este PFC no haremos referencia dado que el proyecto no ha
sido actualizado desde 2012 y entendemos que deja atrás algunas novedades de PHP. Atoum, pese a
no tener la popularidad de PHPUnit, es un framework moderno y completo al que haremos alguna
mención.

Además de los frameworks clásicos de testeo unitario existen otros orientados ha realizar tests según
la especificación del proyecto a desarrollar. Dentro de estos podemos encontrar a Behat y Phpspec.
Estos dos nacen inspirados de RSpec, desarrollado para Ruby con una gran popularidad entre los
desarrolladores de este lenguaje.

En una posición intermedia a PHPUnit y Behat podemos situar a Codeception. Codeception es otro
framework de testeo PHP, desarrollado sobre PHPUnit, pero con funcionalidades específicas para
realizar tests funcionales y de aceptación. Dado que es una herramienta que integra las distintas
formas de testeo de un proyecto le dedicaremos un capítulo completo.

2.8 Git, Composer y Packagist y estándares PSR


Durante este proyecto haremos menciones constantes a Git y su proyecto web GitHub [10], a
Composer [11] y a los distintos estándares que la comunidad de PHP está intentando implantar. Para
poner en contexto explicaremos que es cada de estos proyectos:

Git Es un sistema de control de versiones distribuido y de código abierto. Utilizando este


proyecto, sus creadores crearon GitHub.com, que es un sitio web especializado en hospedaje de
proyectos de software, con versión gratuita para proyectos de código abierto y otra de pago para
proyectos privados. En los últimos años se ha convertido en el sitio donde los desarrolladores
publican los proyectos de código abierto y la comunidad tiene la oportunidad de contribuir.

Composer y Packagist [12] Composer es un gestor de dependencias para proyectos PHP.


Mediante la definición de un fichero en formato json, llamado normalmente composer.json, un
proyecto puede utilizar otros proyectos PHP. Packagist.org es sistema centralizado donde se
alojan las referencias de estas dependencias, siguen la recomendación de Semantic Versioning.
Esta última es una estandarización sobre cómo debe evolucionar el versionado de proyectos de
software mediante una numeración de la forma MAJOR.MINOR.PATCH. Composer
implementa esta forma estándar permitiendo definir qué versión de un paquete o librería debe
ser incorporado en nuestro proyecto.

PHP-FIG [2]: Es un grupo de desarrolladores PHP que están creando estándares dentro de la
comunidad, algunos de una gran utilidad como PSR-4 (estándar para autoload de clases),
mediante el cual, los proyectos que lo implementan pueden ser reutilizados en otros proyectos
con una especificación de namespaces, que puede ser fácilmente definida en Composer.

Capítulo 2. Primer acercamiento a tests 10


Testeo de código en desarrollos PHP.

Capítulo 3. Ejemplos de distintos


*frameworks*
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/

Capítulo 3. Ejemplos de distintos *frameworks* 11


Testeo de código en desarrollos PHP.

* 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 {

Capítulo 3. Ejemplos de distintos *frameworks* 12


Testeo de código en desarrollos PHP.

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
{
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(
}
}

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{

/**

Capítulo 3. Ejemplos de distintos *frameworks* 13


Testeo de código en desarrollos PHP.

* 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..............................

Capítulo 3. Ejemplos de distintos *frameworks* 14


Testeo de código en desarrollos PHP.

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.ph
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:

https://github.com/ScullWM/Cutator: Clase para crear paginador de PHP.


https://github.com/euskadi31/Robots: Clase para gestionar ficheros robots.txt
https://github.com/rezzza/jobflow: Gestor de procesos en PHP.

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:

Capítulo 3. Ejemplos de distintos *frameworks* 15


Testeo de código en desarrollos PHP.

{
"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()

Capítulo 3. Ejemplos de distintos *frameworks* 16


Testeo de código en desarrollos PHP.

{
$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 Technolo
=> 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".

Capítulo 3. Ejemplos de distintos *frameworks* 17


Testeo de código en desarrollos PHP.

Capítulo 4. Aislamiento de tests e interacción


con datos
Aislamiento de tests e interacción con datos
4.1 Que son fixtures y como se configuran
Una de las tareas que consumen más tiempo a la hora de crear tests es escribir el código que configura
el sistema en un estado conocido. Este estado conocido se le llama fixture.

Supongamos que queremos testear la implementación de una cola LIFO, como en el siguiente
ejemplo. En el ejemplo, nuestra cola está representada por la variable this->queue, que es un array.
En otras situaciones la clase a testear tiene más complicaciones dado que existen otros dependencias.
La situación es más complicada cuando debemos repetir la configuración, es decir, el fixture, para
cada método de testeo.

4.1.1 Métodos setUp y tearDown

PHPUnit nos ofrece los métodos setUp y tearDown para ayudarnos en la tarea de la configuración del
los tests. setUp es un método ejecutado antes de cada test, para inicializar las variables y el entorno en
un estado conocido. De la misma forma tearDown es ejecutado después de cada test, pasen o falle,
para limpiar las variables inicializadas. Con estos métodos podemos evitar duplicar código en los
propios tests para configurar el estado conocido.

class QueueTest extends PHPUnit_Framework_TestCase {

public function setUp(){


$this->queue = Array();
}

public function tearDown(){


unset($this->queue);
}

public function testEmptyQuery() {


$this->assertTrue(empty($this->queue));
}

public function testPush() {


array_push($this->queue, 'element1');
$this->assertEquals('element1', $this->queue[count($this->queue)-
$this->assertFalse(empty($this->queue));
}
}

Si volvemos al ejemplo del capítulo anterior, donde testeabamos la clase XString, podemos introducir
una mejora añadiendo el método setUp he inicializando un atributo de tipo XString para la clase de
test. De esta forma evitaremos tener en cada test el código de inicialización del SUT. Si

Capítulo 4. Aislamiento de tests e interacción con datos 18


Testeo de código en desarrollos PHP.

refactorizamos el ejemplo del apartado anterior con el método setUp y tearDown podría quedar de la
siguiente forma:

class XStringsTest extends PHPUnit_Framework_TestCase{

protected $xstring = null;

public function setUp(){


$this->xstring = new XString('hello world');
}

public function tearDown(){


unset($this->xstring);
}

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

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

4.1.2 Métodos setUpBeforeClass y tearDownAfterClass

Equivalentes a setUp y tearDown existen los métodos setUpBeforeClass y tearDownAfterClass. Estos


métodos, en lugar de ejecutarse antes y después de cada tests, son ejecutados antes y después de la
ejecución de la clase. El objetivo de estos métodos es inicializar y limpiar variables, respectivamente,
de entorno que son reutilizadas entre los tests de una misma clase de testeo. Un ejemplo clásico de
este tipo de asignación es el de la conexión a una base de datos. Dado que inicializar una conexión es
costoso, realizar este tipo de inicializaciones una vez en la ejecución de la clase de test mejora el
tiempo en de ejecución de los test, lo cual puede llegar a ser un problema cuando llegan a durar
mucho tiempo.

El siguiente ejemplo nos mostraría como realizar dicha inicialización y limpiado:

Capítulo 4. Aislamiento de tests e interacción con datos 19


Testeo de código en desarrollos PHP.

class DatabaseTest extends PHPUnit_Framework_TestCase


{
protected static $dbh;

public static function setUpBeforeClass()


{
self::$dbh = new PDO('sqlite::memory:');
}

public static function tearDownAfterClass()


{
self::$dbh = NULL;
}
}

4.1.3 Anotaciones de PHPUnit

Una anotación es una forma especial de añadir meta información a los tests que puede ser añadido en
el código fuente con el fin extender el comportamiento de dichos tests.

En la documentación oficial de PHPUnit [1] podemos ver todas las anotaciones. Algunas de estas
anotaciones las comentaremos en las siguientes secciones, como son @backupGlobals o
@dataProvider.

Aunque no sea propio de este capítulo debemos destacar otro tipo de anotaciones, por su importancia,
como son las que determinan que el test con dicha anotación espera recibir una excepción por parte
del SUT. Un ejemplo del formato de este tipo de test sería:

class ExceptionTest extends PHPUnit_Framework_TestCase


{
/**
* @expectedException InvalidArgumentException
*/
public function testException()
{
}
}

El test testException pasará si el SUT que es ejecutando lanza una excepción del tipo
InvalidArgumentException, en caso el test devolverá un error.

4.1.4 Estados globales

Generalmente testear estados globales es difícil dado que no se puede predecir la situación en la que
se encuentra en la ejecución de un test concreto.

Este tipo de situaciones son las propias a las que producen el uso de clases que implementan el patrón
de diseño Singleton o variables globales.

Para testar el patrón Singleton se recomienda generalmente el uso de inyección de dependencias,


como veremos en próximos capítulos.

En PHP, las variables globales son aquellas propias del entorno y son: GLOBALS, _ENV, _POST,

Capítulo 4. Aislamiento de tests e interacción con datos 20


Testeo de código en desarrollos PHP.

_GET, _COOKIE, _SERVER, _FILES, _REQUEST. El riesgo de modificar el valor de estas variables
es que, al ser compartidas, el cambio de un valor en un test puede romper otro.

Para solventar esta situación PHPUnit ofrece la directiva @backupGlobals. Con esta anotación el
propio framework guarda el estado de las variables globales antes de la ejecución de un test y lo
restaura después.

4.1.5 Anotación @dataProvider

Un método de testeo puede aceptar parámetros arbitrarios.

La anotación dataProvider especifica a un test una función que devolverá los parámetros de entradas
del test. En el siguiente ejemplo podemos ver como la función additionProvider devuelve los datos de
entrada del test testAdd, de la siguiente forma:

class DataTest extends PHPUnit_Framework_TestCase


{
/**
* @dataProvider additionProvider
*/
public function testAdd($a, $b, $expected) {
$this->assertEquals($expected, $a + $b);
}

public function additionProvider() {


return array(
array(0, 0, 0),
array(0, 1, 1),
array(1, 0, 1),
array(1, 1, 3)
);
}
}

4.2 Herramienta de generacion de fixtures automáticas


Dado que la generación de fixtures es un trabajo que puede llegar a ser bastante tedioso y
generalmente el valor de los datos no es tan importante como validar que el código haga lo esperado,
existen algunas herramientas para ayudarnos a generar esta información.

En esta sección veremos tres herramientas: Faker [13], Alice [14] y Samsui [15]. Faker es un
generador de datos, Alice es un conector entre datos y objetos existentes y Samsui es una solución
intermedia, aunque más inmadura que la combinación de las otras dos alternativas.

4.2.1 Faker

Con este componente podemos generar fixtures, ya sean persistidos o generados de forma dinámica.

Faker genera fixtures especificados por distintos proveedores y distintos formatos de forma aleatoria.
Con esta herramienta, crear fixtures para entidades resulta fácil. La implementación de Faker permite
configurar el idioma y el valor de los formatos en función a dicho idioma. Por ejemplo, podríamos
crear un conjunto de elementos utilizando el proveedor de dirección en formato para la localización
de Estados Unidos. Con esta configuración, Faker se encargaría de producir una serie de direcciones

Capítulo 4. Aislamiento de tests e interacción con datos 21


Testeo de código en desarrollos PHP.

ficticias con forma de direcciones americanas.

Ilustremos el uso de este componenete con un ejemplo. Supongamos que estamos desarrollando un
sistema de opiniones de usuarios. Para dicho sistema necesitaremos la clase User y la clase Review.
Un usuario tiene dos parámetros de entrada, username y email, y los métodos públicos addReview y
countReviews.

La clase Review se construye con un title y description, como título y descripción de la opinión. En el
ejemplo podemos ver como utilizamos el componente Faker tanto para crear usuarios como
comentarios.

class User {

protected $username;

protected $email;

protected $reviews = Array();

public function __construct($username, $email) {


$this->username = $username;
$this->email = $username;
}

public function addReview(Review $review) {


$this->reviews[] = $review;
}

public function countReviews() {


return count($this->reviews);
}
}

class Review {

protected $title;
protected $description;

public function __construct($title, $description) {


$this->title = $title;
$this->description = $description;
}
}

El código que testararía la clase User sería:

class UserTest extends \PHPUnit_Framework_TestCase{

public function setUp(){


$faker = Faker\Factory::create();
$faker->addProvider(new Internet($faker));
$this->user = new User($faker->userName, $faker->email);

Capítulo 4. Aislamiento de tests e interacción con datos 22


Testeo de código en desarrollos PHP.

public function testAddReview() {

$faker->addProvider(new Lorem($faker));
for($i=0;$i<=10;$i++) {
$this->user->addReview(new Review($faker->sentence, $faker
}
$this->assertEquals(10, $this->user->countReviews());
}
}

Como podemos ver en el código del método testAddReview, generar una lista con 10 usuarios con
distinta información es trivial si utilizamos Faker. De esta forma nos podemos ahorrar crear datos y
mantenerlos en ficheros en algún formato como csv y json y mantenerlos en un futuro. Dado que el
ejemplo es muy sencillo y apenas tiene de lógica, no estamos viendo otro tipo de ventajas que nos
puede aportar estas herramientas. Por ejemplo, si necesitaramos validar algún tipo de dato, como e-
mail, url, etc. tendríamos un conjunto de datos por lo cual nuestra implementación podría fallar.

En la documentación de Faker [13] podemos ver todas las posibilidades que ofrece a la hora de
generar datos.

4.2.2 Alice

Cuando trabajos con fixtures, normalmente necesitamos algo más de un conjunto de datos. Nuestro
SUT suele ser dependiente de otras partes del sistemas, como pudimos ver en el ejemplo del apartado
dedicado a Faker. La solución por la que optamos en ese ejemplo fue la de crear objetos "Review"
dentro del método de testeo, pero este trabajo también se puede automatizar.

Para esa tarea existe el componente Alice. Alice es una herramienta para crear objetos concretos a
partir de ficheros de configuración (PHP o Yaml). Además, Alice tiene como dependencia Faker, y
puede ser utilizado para generar de forma dinámica fixtures aleatorios.

Podríamos refactorizar el método testAddReview mediante una configuración, en este caso en formato
Yaml, contenido en el fichero "fixture/review.yml":

Review:
review{1..10}:
title: <sentence()>
description: <paragraph()>

Y el siguiente código PHP:

public function testAddReview() {


$loader = new Nelmio\Alice\Loader\Yaml();
$reviews = $loader->load(__DIR__.'/fixtures/reviews.yml');
foreach($reviews as $review) {
$this->user->addReview($review);
}
$this->assertEquals(10, $this->user->countReviews());
}

Mediante el fichero "fixture/review.yml" estamos especificando un rango de objetos en la linea

Capítulo 4. Aislamiento de tests e interacción con datos 23


Testeo de código en desarrollos PHP.

review{1..10} de diez objetos de la clase Review, con atributos generados por Faker. El instanciación
de los objetos con la configuración propia es realizada por el loader de Alice, por lo que simplifica la
configuración de nuestro test. Este ejemplo es poco ilustrativo al ser sencillo, pero nos da una idea de
como funciona este tipo de herramientas y cómo se puede utilizar en ejemplos más complejos.

La combinación de estas dos herramientas nos dan una gran flexibilidad a la hora de generar datos
que puedan ayudar a crear un conjunto de tests para que nuestra aplicación sea robusta.

4.2.3 Samsui

Samsui es una librería diseñada para crear objetos PHP con el objeto de testear aplicaciones. Está
inspirada en las librerías Rosie (de JavaScript) y factory_girl (de Ruby).

Es una solución a los problemas que soluciona Faker y Alice con una única herramienta. Pese a que es
una solución completa, está menos desarrollada que Faker y en el momento de la edición de este PFC
no está adaptada para crear fixtures en distintos idiomas y el conjunto de generadores (tipo de datos a
crear) no es mejor que el de las otras alternativas. Con respecto a la generación de objetos, tampoco
tiene la capacidad de Alice de crear objetos de tipos concretos.

Un ejemplo de código con Samsui sería el siguiente:

use Samsui\Factory;
use Samsui\Generator\Generator;

$factory = new Factory();

// define an object quickly


$factory->define('person')
->sequence('personId')
->attr('firstName', Generator::person()->firstName)
->attr('lastName', Generator::person()->lastName)
->attr('email', function ($i, $o) {
return Generator::email()->emailAddress(
array(
'firstName' => $o->firstName,
'lastName' => $o->lastName,
'domains' => array(
'hotmail.com',
'gmail.com',
'example.com'
)
)
);
})
->attr('createdTime', function () {
return time();
});

Como vemos, la generación de objetos de tipo "person" con Samsui es sencilla, al especificar el valor
de cada atributo del objeto mediante la clase Generator.

4.2.4 Conclusión entre Faker, Alice y Samsui

Capítulo 4. Aislamiento de tests e interacción con datos 24


Testeo de código en desarrollos PHP.

Como comparativa entre las herramientas vistas para la generación automática de fixtures podemos
concretar que la mejor opción, al menos entre las estudiadas, es la combinación Faker y Alice, aunque
Samsui promete en un futuro ser una alternativa válida. Para proyectos con ambitos internacionales
tener una herramienta que nos ayude a crear un conjunto de datos para testing inicial en idiomas que
desconocemos como Faker puede resultar de gran ayuda.

Capítulo 4. Aislamiento de tests e interacción con datos 25


Testeo de código en desarrollos PHP.

Capítulo 5. Testeando bases de datos


Testeando bases de datos
5.1 Formas de testear dependencias con la base de datos
Para realizar test con dependencia de base de datos hay que afrontarlos de tres formas posibles, en
función del tipo de test que consideremos mejor se adapta a nuestras necesidades.

Test unitario, utilizando test dobles. Test tipo de tests lo explicaremos en el capítulo 6, dedicado
a tests dobles, aunque veremos otras situaciones como en el capítulo 10, dedicado a testear API.
Resumiendo: consiste en falsear objetos que simulan el comportamiento de la base de datos,
por lo que testearíamos la lógica de más alto nivel de las clases que afecten a dicha base de
datos. No podríamos considerar un tests de base de datos realmente, aunque en muchas
situaciones este tipo de tests son suficientes.

Integración, con fixture de datos concretos. Este tipo de tests acceden realmente a base de datos,
pero el estado de la base de datos es conocido, dado que antes de la ejecución del conjunto de
tests se realizan tareas para crear y rellenar la base de datos.

Test de aceptación. Estos tests simulan la aplicación en su conjunto. Para estos tests en entornos
web la aplicación será ejecutada por completo, ejecutando un navegador y simulando el
comportamiento de un usuario interactuando con algún mecanismo de simulación de
navegadores web. Aunque el objetivo de estos tests no sea específicamente verificar la
interacción entre la aplicación y la base de datos, como efecto de realizar pruebas sobre todo el
sistema, la base de datos también quedará ejercitada y testeada.

5.2 Dificultades de testear base de datos


Testear fragmentos de código con dependencia a bases de datos suele presentar varios
complicaciones:

La funcionalidad que se quiere testear ejecuta una compleja consulta con entre tablas (join).
La lógica de negocio ejecuta SELECT, INSERT o DELETE.
La inicialización de los datos no es sencilla, requiere mecanismos de inserción previos a la
ejecución de los tests y de limpieza de los datos una vez ejercitados los tests.
La gestión del esquemas de base de datos y tablas, desde el punto de vista de mantenibilidad.
Con esto queremos decir que para mantener funcionales los tests de bases de datos necesitamos
actualizar la base de datos de nuestro SUT con relación a la base de datos de producción.

Los tests que ejecutan sentencias en base de datos deben ser cortos por las siguientes razones:

Mantenibilidad: Realizar pequeños cambios en el código que se ejecuta a producción puede


romper un gran conjunto de tests.
Legibilidad: Pequeños y concisos tests son más legibles y pueden mantenerse mejor en
posteriores desarrollos de funcionalidades o refactorizaciones.

5.3 Estados cuando se testea con dependencia de código de base de


datos

Capítulo 5. Testeando bases de datos 26


Testeo de código en desarrollos PHP.

En el libro xUnit Test Patterns [48] se define estos cuatro pasos:

Configurar el fixture.
Ejercitar el sistema bajo test.
Verificar los resultados.
Limpiar el fixture.

Estos pasos no son exactamente los mismos que debemos ejecutar cuando testeamos bajo
dependencia de la base de datos. Los pasos que debemos considerar son:

Limpiar la base de datos: Para tener un entorno bajo un sistema de control la base de datos debe
de estar en un estado conocido. La forma de comenzar de forma segura es limpiar la base de
datos.
Configurar el fixture: En un test sin dependencia de base de datos, configurar el fixture, como
vimos en el capítulo 4, consiste en la inicialización de los objetos que interactuarán con nuestro
SUT. En un test con bases de datos necesitaremos crear el contenido necesario realizando las
operaciones de inserción sean apropiadas para conocer el estado inicial de la base de datos.
Ejecutar y verificar los resultados, como en cualquier tests.
Ejecutar el proceso de tearDown, para dejar la base de datos en un estado conocido para la
ejecución de los siguientes tests.

5.4 Replicación del entorno de producción


Para ejecutar test en un entorno con bases de datos es imprescindible que el sistema a testear sea lo
más parecido posible al de producción. Para ello, es necesario mantener actualizado el esquema de la
base de datos, así como otro tipo de procedimientos. Crear una copia del esquema de la base de datos
en el entorno donde se ejecutarán los tests es la única forma de realizar esta tarea.

Dependiendo de si lo que deseamos realizar son test de integración o funcionales, mantener la base de
datos de test es más complejo. Para los tests de integración es necesario mantener la estructura de la
base de datos, procedimientos, triggers y otras configuraciones. Para realizar test de aceptación es
necesario tener, al menos, un subconjunto de los datos. Dado que los tests funcionales son, al fin y al
cabo, simulaciones de usuarios reales.

En algunos proyectos esto se resuelve manteniendo una copia parcial de los datos reales. Esta práctica
puede tener algún incumplimiento con leyes sobre privacidad de datos, por lo que una mejor solución
sería crear una simulación de la base de datos, lo más parecida posible a producción. En el capítulo 4
vimos herramientas, como Faker [13], que nos pueden ayudar a generar el conjunto de datos
necesarios para simular un entorno funcional completo.

5.5 Testeando base de datos con PHPUnit


PHPUnit [1] ofrece algunas herramientas integradas para ayudar a testear bases de datos. En este
apartado haremos un resumen sobre los puntos más interesantes, aunque en el ejemplo para este
capítulo no usaremos dichas herramientas.

En primer lugar, PHPUnit nos proporciona una clase distinta a la genérica


PHPUnit_Framework_TestCase para testear con base de datos:
PHPUnit_Extensions_Database_TestCase. Dicha clase es una clase abstracta con los métodos
getConnection y getDataSet. getConnection, como su nombre indica, nos obliga a implementar la
conexión a base de datos sobre la que realizaremos los tests.

Por otra parte, getDataSet es un método para crear el fixture inicial en base de datos sobre el que

Capítulo 5. Testeando bases de datos 27


Testeo de código en desarrollos PHP.

realizaremos los tests. En la propia documentación de PHPUnit recomiendan como buena práctica
crear una implementación de PHPUnit_Extensions_Database_TestCase que sea compartida para
todas las clases de testeo de base de datos, con el objetivo tener en un único lugar la conexión y datos.
Esta última recomendación mantiene el principio DRY (del inglés, don't repeat yourself) cosa que en
software es una buena práctica.

Para preparar el fixture, PHPUnit nos da la posibilidad de importar distintos tipos de ficheros:

XML: En formato simple (sin poder tener valores nulos) o extendido.


Volcado de datos en formato XML de MySQL.
CSV.
YAML.
Array PHP.

PHPUnit ofrece distintas funciones para procesar el fixture, como sustituciones (para reemplazar
cambios de valores de campos), filtros (para seleccionar campos o filas en caso de fixtures grandes),
composiciones (para agregar varios fixture en uno) y aserciones propias de bases de datos
(assertTablesEqual y assertDataSetsEqual).

5.6 Ejemplo de tests con base de datos


Para ver como se testea con dependencia de bases de datos lo mejor es ver un caso práctico. El
siguiente ejemplo es un sistema simplificado de Flickr.com, un sitio web en los que los usuarios
pueden guardar sus fotografías.

Los tests que realizaremos son de integración y no utilizaremos las herramientas propias de PHPUnit.
Por el contrario, utilizaremos una simulación simplificada de DbUnit, incluida en PHPUnit [1],
aunque el concepto es el mismo.

El esquema de la base de datos para nuestro ejemplo sería el siguiente:

CREATE TABLE `User` (


`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `Photo` (


`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`url_photo` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_idx` (`user_id`),
CONSTRAINT `user_idx` FOREIGN KEY (`user_id`) REFERENCES `User` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Dado que utilizaremos PDO con la forma FETCH_CLASS, es decir, asociando a una clase el
resultado de una consulta necesitaremos las clases User y Photo:

Capítulo 5. Testeando bases de datos 28


Testeo de código en desarrollos PHP.

class User {
public $id;
public $username;
public $email;
public $photos = Array();
public function getPhotos() {
return $this->photos;
}
public function addPhoto(Photo $photo) {
$this->photos[] = $photo;
}
public function addPhotos(array $photos) {
$this->photos = array_merge($this->photos, $photos);
}
}

class Photo {
public $id;
public $user_id;
public $url_photo;
}

Para gestionar el acceso a base de datos hemos creado las clases UserRepository y PhotoRepository,
de forma que sean estas clases las responsables de ejecutar las consultas.

El código de la clase UserRepository sería:

class UserRepository
{
protected $db;

public function __construct(PDO $db)


{
$this->db = $db;
}

public function storeUser(User $user)


{
$sql = 'INSERT INTO User(username, email) VALUES (:username, :email)'
$stm = $this->db->prepare($sql);
$stm->execute(array(':username' => $user->username, ':email' =>
$userId = $this->db->lastInsertId();
if (!$userId) {
throw new Exception('User not saved');
}
$user->id = $userId;

if (count($user->getPhotos()>0)) {
$photoRepository = new PhotoRepository($this->db);
foreach($user->getPhotos() as $photo) {
$photoRepository->storePhoto($photo, $user);
}

Capítulo 5. Testeando bases de datos 29


Testeo de código en desarrollos PHP.

return $user;
}

public function removeUser(User $user) {


$sql = 'DELETE FROM User where id=:id';
$stm = $this->db->prepare($sql);
$stm->execute(Array(':id' => $user->id));

public function getById($id)


{
$sql = 'SELECT * FROM User where id = :user_id';
$stm = $this->db->prepare($sql);
$stm->execute(array(':user_id' => $id));
$stm->setFetchMode(PDO::FETCH_CLASS, 'User');
$user = $stm->fetch();

$sql = 'SELECT * FROM Photo where user_id = :user_id';


$stm = $this->db->prepare($sql);
$stm->execute(array(':user_id' => $id));
$stm->setFetchMode(PDO::FETCH_CLASS, 'Photo');
$photos = $stm->fetchAll();
if(is_array($photos) && count($photos)>0) {
$user->addPhotos($photos);
}

return $user;
}
}

Y el código de la clase PhotoRepository sería:

class PhotoRepository {

protected $db;

public function __construct($db) {


$this->db = $db;
}

public function storePhoto(Photo $photo, User $user)


{
$sql = 'INSERT INTO Photo(user_id, url_photo) VALUES (:user_id, :url_p
$stm = $this->db->prepare($sql);
$stm->execute(array(':user_id' => $user->id, ':url_photo' =>
$photoId = $this->db->lastInsertId();
if (!$photoId) {
throw new Exception('Photo not saved');

Capítulo 5. Testeando bases de datos 30


Testeo de código en desarrollos PHP.

}
$photo->id = $photoId;

return $photo;
}
}

Para la ejecución de los tests unitarios hemos decidido utilizar un conjunto de datos generado de
forma dinámica. Para ello, y como vimos en el capítulo 4, utilizado Faker [13] y Alice [14], con el
siguiente fichero YAML:

User:
user{1..10}:
username:
email:

Photo:
photo{1..3}:
url_photo:

Y el código de los tests unitarios:

class UserRepositoryTest extends PHPUnit_Framework_TestCase {

protected static $pdo;

public static function setUpBeforeClass() {


try {
$host = 'mysql:host=localhost;dbname=photostock';
self::$pdo = new PDO($host, 'root', 'PASS');
} catch (\Exception $e) {
$this->markTestSkipped('MySQL conection is not working.');
}
}

public function setUp() {


$loader = new Nelmio\Alice\Loader\Yaml();
$this->data = $loader->load(__DIR__.'/fixtures/users.yml');

$this->userRepository = new UserRepository(self::$pdo);


}

public function tearDown(){


self::$pdo->query("set foreign_key_checks=0");
self::$pdo->query("TRUNCATE User");
self::$pdo->query("TRUNCATE Photo");
self::$pdo->query("set foreign_key_checks=1");
}

public function testStoreUser() {


$user = $this->data['user1'];
$this->assertNull($user->id);

Capítulo 5. Testeando bases de datos 31


Testeo de código en desarrollos PHP.

$user = $this->userRepository->storeUser($user);
$this->assertObjectHasAttribute('id', $user);
}

public function testStoreUserWithPhotos() {

$user = $this->data["user1"];
$this->assertNull($this->data["photo1"]->id);
$this->assertNull($this->data["photo2"]->id);
$this->assertNull($this->data["photo3"]->id);
$user->addPhoto($this->data["photo1"]);
$user->addPhoto($this->data["photo2"]);
$user->addPhoto($this->data["photo3"]);

$user = $this->userRepository->storeUser($this->data['user1']);
$photos = $user->getPhotos();
foreach($photos as $photo) {
$this->assertGreaterThan(0, $photo->id);
}
}

public function testGetUserById(){


$user = $this->userRepository->storeUser($this->data['user1']);
unset($user);
$user = $this->userRepository->getById(1);
$this->assertEquals($this->data['user1']->username, $user->username);
}

public function testGetUserByIdWithPhotos() {


$user = $this->data["user1"];
$user->addPhoto($this->data["photo1"]);
$user->addPhoto($this->data["photo2"]);
$user->addPhoto($this->data["photo3"]);
$user = $this->userRepository->storeUser($this->data['user1']);
unset($user);
$user = $this->userRepository->getById(1);
$this->assertEquals($this->data['user1']->username, $user->username);
$this->assertEquals(3, count($user->getPhotos()));
}
}

Como comparativa con DbUnit, nos hemos tomado varias libertades:

La conexión de la base de datos la hemos creado en el método setUp.


La carga del fixture lo hemos puesto en memoria, con instancias de objetos User y Photo en
lugar de la configuración de la base de datos.
El proceso propio de tearDown lo hemos ejecutado manualmente, al ejecutar las
correspondientes sentencias SQL TRUCATE.

El motivo por el que hemos decidido no seguir la forma normal de testar con dependencias con bases
de datos es porque la documentación extendida y algunas funcionalidades fueron introducidas en
PHPUnit 3.5. Antes de dicha versión se podían testear código dependiente de base de datos, pero
requería algunos procesos. Por ejemplo, para la carga del fixture inicial se puede ejecutar un el

Capítulo 5. Testeando bases de datos 32


Testeo de código en desarrollos PHP.

comando "mysql load data" en el método setUp de cada test, y el truncate tal y como hacemos en
nuestro ejemplo.

Las versiones posteriores PHPUnit 3.4 facilitan estas tareas, pero los conceptos de mantener la base
de datos en un estado bajo control y los mecanismos para ello, siguen siendo análogos.

En este ejemplo hemos decidido realizar un test de integración y ejercitar el código que accede
directamente a la base de datos. Testear una pieza de código con dependencias de otros sistemas como
base de datos se puede realizar de otra forma, utilizando tests dobles, como mencionábamos al
principio del capítulo. Dado que este capítulo se centraba especialmente en tests de base de datos
hemos decido no hacer test con tests dobles y ejercitar las consultas directamente.

Capítulo 5. Testeando bases de datos 33


Testeo de código en desarrollos PHP.

Capítulo 6. Test dobles


Test dobles
6.1 Definición de Double tests
En determinadas circunstancias, el código que estamos testeando tiene dependencias. Esta
circunstancia lo hace complejo o imposible testear de forma aislada. Estas situaciones son llamadas
test dobles, del inglés Double Tests. Para crear tests independientes y aislados hay que realizar algún
tipo de tratamiento especial, generando elementos que simulen el comportamiento de las partes que
rompen la ejecución aislada de esos tests.

De una forma gráfica, si queremos testear el método doSomething del siguiente fragmento de código:

class ClassToTest {

public function doSomething() {


$otraClase = new ClassNoToTest();
$otraClase->doSomethingNotToTest();
/**
* Code
* ...
*/
}
}

Viendo este sencillo ejemplo podemos entender que testear el comportamiento aislado del método
doSomething, no es posible al tener la dependencia del comportamiento del método
doSomethingNotToTest de la clase ClassNoToTest. Para solventar este tipo de situaciones se utilizan
distintos formas de simular o controlar el comportamiento de la clase ClassNoToTest. Para tener una
cobertura de test completa hay que testear cada clase de forma aislada, por lo tanto necesitaremos
crear los mecanismos necesarios para la clase ClassNoToTest de nuestro ejemplo.

6.2 Dummy, Fake, Stubs y Mocks


Los tests dobles se pueden clasificar en función de las capacidades que tienen los objetos creados en
nuestro SUT y, como se indica en capítulo 8 del libro "PHPUnit Essentials" [3], pueden ser alguno de
estos tipos: dummy, fake, stub y mock. A nivel práctico y como veremos en los próximos ejemplos, las
distintas librerías que existen para ayudar a crear este tipo de objetos no tienen una forma distinta de
crearlos. Por ejemplo, para Prophecy todos estos objetos son creados con el método prophesize e
instanciados con el método reveal. Aunque los mecanismos son comunes es importante entender la
diferencia entre los distintos tipos de tests dobles, para comprender qué es lo que estamos testeando
realmente.

Aunque en este mismo capítulo explicaremos con más detalle Prophecy, en esta sección utilizaremos
esta librería de PHP dedicada a crear este tipo de objetos.

6.2.1 Dummy

Capítulo 6. Test dobles 34


Testeo de código en desarrollos PHP.

Dummy son un tipo de objetos utilizados en test dobles que no tienen comportamiento alguno, dado
que no queremos tener efectos laterales. Son usados para cumplir las restricciones de argumentos de
parámetros tipados. Un ejemplo de uso de dummy sería el siguiente, utilizando Prophecy:

public function testwithDummy() {


$dummy = $this->prophesize("DummyClass");
$classForTesting = new MyClass($dummy->reveal());
}

El método prophesize de PHPUnit 4.6 nos devuelve un objeto de tipo Prophet sin ningún tipo de
configuración, que es lo que nos interesa en este tipo de testeo. Como veremos cuando hablemos de
Prophecy, el objeto reveal devuelve la instancia de DummyClass, que es necesaria para instanciar un
objeto de la clase MyClass. El código del constructor de la clase MyClass, por el que es necesario
pasar un objeto del tipo DummyClass sería el siguiente:

class MyClass {

protected $obj;

public function __construct( DummyClass $var) {


$this->obj = $var;
}
}

6.2.2 Fake

En determinadas circunstancias, necesitamos controlar las llamadas a los objetos con dependencias
dentro de nuestro SUT. Para ello, es necesario otro tipo de objetos, los cuales, además de especificar
la clase, se le especifica una configuración determinada para llamadas a métodos concretos. Para este
tipo de situaciones tampoco tenemos dependencia con el resultado de los métodos declarados, pero sí
necesitamos tenerlos especificados dado que el SUT hará llamada a dichos métodos. Un ejemplo de
este tipo de tests objetos sería:

public function testWithFakeObject() {


$fakeLogger = $this->prophesize("Logger");
$fakeLogger->log(Argument::any());
$fakeLogger->info(Argument::any());
$controler = new Controller($fakeLogger->reveal());
}

En este ejemplo también hemos usado Prophecy. La instancia fakeLogger es un objeto que en la
aplicación en sí no devuelve resultado a cada llamada a los métodos log o info, pero sabemos que
nuestra clase Controller ejecuta en determinadas circunstancias. Al querer romper la dependencia con
la clase Logger real, hemos creado un objeto Fake el cual no hará nada, pero permitirá que la
ejecución del SUT (en este caso Controller) sea posible.

6.2.3 Stubs

La práctica de reemplazar un objeto con un test doble que devuelve una salida configurada se llama
stubbing. Introduciendo en el SUT este objeto con usa salida predeterminada, tendremos control sobre
la pieza de código que producía la indirección y por lo tanto podremos considerar que estamos

Capítulo 6. Test dobles 35


Testeo de código en desarrollos PHP.

realizando testeo de forma aislada.

Un ejemplo de objetos Stubs sería el siguiente, dada la clase de ejemplo Controller:

class Controller {

protected $response;

public function __construct(\SolrResponse $response) {


$this->response = $response;
}

public function doController() {


if($this->response->getHttpStatus==200) {
// Do somehing to test

return true;
}

return false;
}
}

Podríamos crear un test en el que rompanmos la dependencia con el objeto response inicializando la
clase Controller con un objeto stub de la siguiente forma:

public function testWithStub() {


$stub = $this->prophesize("\SolrResponse");
$stub->getHttpStatus()->willReturn(200);

$controller = new Controller($stub->reveal());


$this->assertTrue($controller->doController());
}

Como se puede ver en la implementación de nuestra clase de ejemplo Controller, la dependencia del
método "getHttpStatus" del objeto this->response la configuramos y dejamos bajo control con nuestro
objeto stub, al cual le hemos indicado que devuelva el código de estado 200, con la configuración
willReturn(200). Así el objeto colaborador SolrResponse queda en una situación conocida, lo que nos
permitirá predecir el comportamiento del método doController.

6.2.4 Mocks

La práctica de reemplazar un objeto con un test doble que verifica expectativas, por ejemplo, que un
método concreto del objeto colaborador ha sido llamado, se llama mock.

Utilizar mocks tiene sentido cuando hay una dependencia conocida con el comportamiento de los
elementos que crean la indirección. En la documentación de PHPUnit [1] podemos ver un ejemplo
ilustrativo al testear el patrón de diseños observador. El código de las clases Subject y Observer sería
el siguiente:

class Subject

Capítulo 6. Test dobles 36


Testeo de código en desarrollos PHP.

{
protected $observers = array();

protected $name;

public function __construct($name) {


$this->name = $name;
}

public function getName() {


return $this->name;
}

public function attach(Observer $observer) {


$this->observers[] = $observer;
}

public function doSomething() {


// Do something.
// Notify observers that we did something.
$this->notify('something');
}

public function doSomethingBad() {


foreach ($this->observers as $observer) {
$observer->reportError(42, 'Something bad happened', $this
}
}

protected function notify($argument) {


foreach ($this->observers as $observer) {
$observer->update($argument);
}
}

// Other methods.
}

class Observer
{
public function update($argument) {
// Do something.
}

public function reportError($errorCode, $errorMessage, Subject $subject


// Do something
}

// Other methods.
}

El código para testear la clase Subject tendría la siguiente forma:

Capítulo 6. Test dobles 37


Testeo de código en desarrollos PHP.

class SubjectTests extends \PHPUnit_Framework_TestCase {

public function testObserversAreUpdated()


{
$observer = $this->prophesize('Observer');
$observer->update("somethig")->shouldBeCalledTimes(1);

// Create a Subject object and attach the mocked


// Observer object to it.
$subject = new Subject('My subject');
$subject->attach($observer->reveal());

$subject->doSomething();
}
}

El test del ejemplo anterior pasará dado que hemos definido que el método update será llamado una
vez y es exactamente lo que ocurrirá si seguimos la traza de ejecución de la llamada doSomething()
del objeto subject.

Si en una refactorización futura de la clase Subject eliminamos la llamada update de la clase Observer
el test anterior fallaría, dado que no se cumpliría la expectativa de llamar una vez el método *update".

6.2.5 Mocks no son stubs

Aunque en todos los frameworks de tests utilizamos la misma nomenclatura para construir stubs y
mocks, los dos tipos de objetos son muy distintos. Por un lado, los stubs son objetos que se controlan
la devolución de los objetos colaboradores de nuestro SUT, mientras que los mocks testean el
comportamiento de dichos colaboradores. Por otro lado, los dos tipos de objetos se utilizan con una
filosofía totalmente distinta de diseño y testeo. Esto puede llevar a debate sobre cuando es necesario
utilizar mocks y stubs, y cuando podemos utilizar instancias de las clases colaboradoras dentro del
SUT concreto. Este debate, con muchos más matices y detalles lo expone Martin Fowler en su
artículo "Mocks Aren't Stubs" [19] . Es un debate que, en nuestra experiencia profesional, se repite
constantemente.

De la misma forma que existe el debate sobre testear todo con mocks, o no usar nada, un punto
intermedio parece razonable. Además de que el uso de estos frameworks de testeo ayudan a romper
dependencias entre el SUT y los colaboradores que interactúan con sistemas externos, como llamadas
a API por el protocolo HTTP, llamadas a disco, etc...

6.3 Mocks parciales (Partials Mocks)


En algunos de los frameworks que estudiaremos con más detalles existe la posibilidad de crear mocks
parciales. Algunos de estos frameworks son Mockery y Phake. Estos mocks parciales son, como su
nombre dan a pensar, una especificación parcial de la clase falseada, llamando a la implementación
original para el resto de métodos no especificados. Este concepto no está extendido en toda la
literatura sobre testeo.

En la documentación de Phake, cuando explican la definición de mocks parciales y la forma de uso


recomiendan no usar este tipo de mocks para código nuevo, recomendando solo utilizarlo cuando
existe una base de código antiguo.

Capítulo 6. Test dobles 38


Testeo de código en desarrollos PHP.

6.4 ¿Mockear un método dentro de la clase testeada?


Mockear o modificar el comportamiento de la clase a testear suele ser un indicador de que estamos
violando el principio de responsabilidad simple, es decir, una clase debería tener una única
responsabilidad. Sin embargo, hay situaciones en las que testear un método puede llegar a ser difícil
debido a que hay una dependencia con un sistema externo, por ejemplo, que es fácil de extraer.

Pese a ser un indicador de mala práctica, este problema se puede resolver mediante mocks parciales.
Estos objetos, como comentábamos anteriormente, heredan directamente de la clase mockeada, pero
lo que el comportamiento de todos los métodos públicos pueden ser configurados después de la
creación. Vamos a explicar este concepto con un ejemplo. Supongamos que tenemos la clase
DoSearch que realiza una búsqueda en alguna página en internet, cuyo código sería:

class DoSearch {

protected static $url = 'http://exampledomain.com?q=';

protected $searchTerm;

public function __construct($searchTerm)


{
$this-> searchTerm = $searchTerm;
}

public function compile() { /* ... */ }

public function fetch()


{
$url = urlencode($this->url . $this->searchTerm);

return file_get_contents($url);
}
}

Dado que queremos hacer el test unitario de esta clase, nos encontramos con el problema de que
testear el método fetch crea una dependencia exterior no deseada. Utilizando Mockery podemos
resolver este problema mediante la creación de un mock parcial. Con Mockery lo podemos hacer de
dos formas:

Mediante un mock parcial normal:

$mock = Mockery::mock('DoSearch[fetch]', ['text']);


$mock->shouldReceive('fetch')->once()->andReturn('stub');

Este ejemplo creará un objeto que hereda directamente de DoSearch solo con el método "fetch" con el
comportamiento establecido.

Mediante un mock parcial pasivo:

$mock = Mockery::mock('DoSearch')->makePartial();

Capítulo 6. Test dobles 39


Testeo de código en desarrollos PHP.

$mock->fetch(); // llama al método real

$mock = Mockery::mock('TwitterSearch')->makePartial();
$mock->shouldReceive('fetch')->once()->andReturn('foo');
$mock->fetch(); // devuelve foo.

En el ejemplo mostramos como creando un mock llamando al método makePartial el objeto se


comporta como lo haría mediante la sentencia new DoSearch, sin embargo, después de configurarlo el
comportamiento quedaría sobreescrito.

Para resolver el problema de mockear el método fetch en los tests de la clase DoSearch sólo bastaría
con crear un objeto mockeado parcialmente en el método setUp de nuestro tests, para PHPUnit.

6.5 Alternativas: PHPUnit, Prophecy, Mockery, Phake, AspectMock


Para gestionar la creación de test doble existen bastantes alternativas. Aunque en secciones anteriores
hemos visto ejemplos con Prophecy [6] y Mockery [16], en esta sección vamos a ver algunos aspectos
de estas y otras opciones. Las librerías de creación y gestión de tests dobles que vamos a abordar son:
los propios mecanismos de PHPUnit y otras librerías especializadas a este efecto como son Prophecy,
Mockery, Phake [17] y AspectMock [18].

Para ver la expresividad y las principales diferencias que existen entre estas librerías hemos creado
una extensión de Monolog [21] para poder almacenar logs en el sistema de indexación Apache Solr.
Este ejemplo es solo ilustrativo y no está pensado para utilizarlo en un entorno en producción.

Monolog es un una librería especializada en el envío de logs a distintos sistemas de almacenamiento


con distintos formatos. Por defecto Monolog tiene implementando una serie de, lo que en el
vocabulario de la propia librería, llaman Handlers", que son clases que gestionan el envío a estos
sitemas de almacenamiento. Algunos de estos sistemas de almacenamiento son bases de datos
(CouchDB, DynamoDB, MongoDB), sistema de ficheros, email, streams, sistemas de índices
(ElasticSearch*), etc.

Dado que de forma original Monolog no soporta Apache Solr, nos parece un ejercicio interesante
implementar la lógica necesaria para enviar logs a este sistema de búsquedas y testearlo utilizando test
dobles.

En las siguientes secciones mostraremos como quedaría un tests unitario para las clases SolrHandler
y SolrFormatter de nuestra extensión de Monolog. El código de las clases a testear sería:

namespace MonologExtended\Handler;
use MonologExtended\Formatter\SolrFormatter;

use Monolog\Logger;
use Solarium\Client;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Formatter\FormatterInterface;

use Solarium\QueryType\Update\Query\Document\DocumentInterface;
use Solarium\Core\Query\QueryInterface;
use Solarium\QueryType\Update\Query\Document\Document;

/**
* SolrHandler.

Capítulo 6. Test dobles 40


Testeo de código en desarrollos PHP.

*/
class SolrHandler extends AbstractProcessingHandler
{
/**
* Client.
*
* @var client.
*/
protected $client;

/**
* Options.
*
* @var options.
*/
protected $options;

/**
* Constructor
*
* @param array $options Options for configure the Solr connection.
* @param integer $level Level of logging.
* @param boolean $bubble Bubble option.
*
* @return void
*/
public function __construct(array $options, $level = Logger::DEBUG,
{
$this->client = new \Solarium\Client($options);

parent::__construct($level, $bubble);
$this->options = $options;
}

/**
* Setter for Client property.
*
* @param Client $client Client to make the request.
*
* @return void
*/
public function setClient(Client $client)
{
$this->client = $client;
}

/**
* Set formmater.
*
* @param FormatterInterface $formatter Formater object.
*
* @return FormatterInterface

Capítulo 6. Test dobles 41


Testeo de código en desarrollos PHP.

* @throws \InvalidArgumentException Throws the exception


* if the SolrHandler is not compatible with SolrFormatter.
*/
public function setFormatter(FormatterInterface $formatter)
{
if ($formatter instanceof SolrFormatter) {
return parent::setFormatter($formatter);
}
$msg = 'SolrHandler is only compatible with SolrFormatter';
throw new \InvalidArgumentException($msg);
}

/**
* Getter options.
*
* @return array
*/
public function getOptions()
{
return $this->options;
}

/**
* Getter for default formatter.
*
* @return FormatterInterface
*/
protected function getDefaultFormatter()
{
return new SolrFormatter($this->client);
}

/**
* Handle a collection of records.
*
* @param array $records Array of records.
*
* @return void
*/
public function handleBatch(array $records)
{
$documents = $this->getFormatter()->formatBatch($records);
$this->bulkSend($documents);
}

/**
* Write record
*
* @param array $record Record.
*
* @return void
*/
protected function write(array $record)

Capítulo 6. Test dobles 42


Testeo de código en desarrollos PHP.

{
$this->bulkSend(array($record));
}

/**
* Send a bulk of documents to Solr.
*
* @param array $documents Array of documents.
*
* @return void
* @throws \RuntimeException Something wrong happen sending data to solr.
*/
protected function bulkSend(array $documents)
{
try {
$update = $this->client->createUpdate();
foreach ($documents as $document) {
$update->addDocument($document['formatted']);
$update->addCommit();
}
$result = $this->client->update($update);
} catch (\Exception $e) {
if (empty($this->options['ignore_errors'])) {
throw new \RuntimeException('Error sending messages to Solr'
}
}
}
}

namespace MonologExtended\Formatter;

use Monolog\Formatter\NormalizerFormatter;

/**
* SolrFormatter. Extension of Monolog.
*/
class SolrFormatter extends NormalizerFormatter
{
/**
* Index for sending the messages.
*
* @var index
*/
protected $index;

/**
* Type.
*
* @var type
*/
protected $type;

Capítulo 6. Test dobles 43


Testeo de código en desarrollos PHP.

/**
* Client.
*
* @var client
*/
protected $client;

/**
* Constructor.
*
* @param mixed $client Client.
*/
public function __construct($client)
{
parent::__construct(\DateTime::ISO8601);

$this->client = $client;
}

/**
* format function
*
* @param array $record Record to be formatted.
*
* @return Document
*/
public function format(array $record)
{
$record = parent::format($record);

return $this->getDocument($record);
}

/**
* Format a collection of records
*
* @param array $records Records to be formatted.
*
* @return array
*/
public function formatBatch(array $records)
{
$docs = array();
foreach ($records as $key => $record) {
$records[$key]['formatted'] = $this->getDocument($record);
}

return $records;
}

/**
* Getter index.
*

Capítulo 6. Test dobles 44


Testeo de código en desarrollos PHP.

* @return string
*/
public function getIndex()
{
return $this->index;
}

/**
* Getter type.
*
* @return string
*/
public function getType()
{
return $this->type;
}

/**
* Get document function
*
* @param mixed $record Record to make a SolrDocument.
*
* @return Document
* @throws \RuntimeException The SolrDocument couldn't be created.
**/
protected function getDocument($record)
{
try {
$update = $this->client->createUpdate();
$doc = $update->createDocument();
$doc->id = uniqid();

$doc->description = $record['message'];
$doc->title = $record['level_name'];

return $doc;
} catch (\Exception $e) {
throw new \RuntimeException("Can't create an Solr document"
}
}
}

Dado que este ejemplo lo vamos a basar en PHPUnit, el método setUp que compartirán todos los tests
con un mensaje de error de ejemplo para registrar en Apache Solr sería:

/**
* setUp method.
*
* @return void
*/
public function setUp()
{

Capítulo 6. Test dobles 45


Testeo de código en desarrollos PHP.

$this->msg = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => ['foo' => 7, 'bar',
'class' => new \stdClass()],
'datetime' => new \DateTime('@0'),
'extra' => array(),
'message' => 'Logging an error',
);
}

6.5.1 PHPUnit

PHPUnit contiene herramientas suficientes para generar los distintos tipos de objetos colaboradores
(Dummy, Fake, Stub y Mocks), pero con algunas desventajas respecto a las otras librerías. Entra las
ventajas a destacar es que es la más flexible, dado que se puede crear objetos y métodos no
implementados en las clases a testear. Dentro de las desventajas habría que destacar la sintaxis. Como
veremos en el siguiente fragmento de código, en el que testeamos las distintas formas de enviar un log
con nuestra extensión de Monolog, las funciones para gestionar mocks de PHPUnit son incómodas de
escribir:

/**
* test creating a Log using Mocks of PHPUnit
*
* @return void
*/
public function testAddLogWithPHPUnitMock()
{

$client = $this->getMockBuilder('Solarium\Client')
->setMethods(array('addDocuments', 'createUpdate', 'update'
->disableOriginalConstructor()
->getMock();

$updateMock = $this->getMockBuilder("Solarium\QueryType\Update\Query\Q
->setMethods(array('addDocument', 'addCommit', 'createDocument'
->disableOriginalConstructor()
->getMock();

$updateMock->expects($this->any())
->method('addDocument');

$className = "Solarium\QueryType\Update\Query\Document\Document"
$documentMock = $this->getMockBuilder($className)
->disableOriginalConstructor()
->getMock();

$updateMock->expects($this->any())
->method('createDocument')
->willReturn($documentMock);

Capítulo 6. Test dobles 46


Testeo de código en desarrollos PHP.

$client->expects($this->any())
->method('createUpdate')
->willReturn($updateMock);

$solrHandler = new SolrHandler(array());


$solrHandler->setClient($client);
$solrHandler->handle($this->msg);
$solrHandler->handleBatch(array($this->msg));
}

En PHPUnit, una vez creado un objeto colaborador es necesario indicarle los métodos que van a ser
sobreescritos mediante el método setMethods. Además, es necesario indicar al framework que no
utilice el constructor propio de la clase colaboradora.

6.5.2 Prophecy

Desde la versión 4.5 de PHPUnit, Prophecy [6] está incluido por defecto. Prophecy es un flexible y
completo framework para la creación y gestión de mocks en PHP. Una de las características que más
llama la atención de Prophecy es la forma de expresar los tests. Como se puede ver en la tabla 6.1,
"Diferencias entre distintos framewors de mocks", Prophecy es estricto en el sentido de que no se
puede falsear nada que no esté implementado en la clase a configurar. Esto se debe a que sigue la
filosofía de RSpec (framework de testeo en el lenguaje de programación Ruby). Otra de las ventajas
es la posibilidad de uso de espías.

Los espías son manipulaciones de tests dobles que permiten almacenar los comportamientos de los
objetos colaboradores y ser consultados sin necesidad de ser especificados en la creación de los tests.
Prophecy graba todas las llamadas de los objetos falseados, por lo cual realizar comprobaciones tras
la ejecución del objeto bajo test, es relativamente sencillo.

Extraído de la documentación de Prophecy podemos ver un ejemplo de código:

$em = $prophet->prophesize('Doctrine\ORM\EntityManager');
$controller->createUser($em->reveal());
$em->flush()->shouldHaveBeenCalled();

El tests que verificaría el funcionamiento de nuestra extensión de Monolog podría tener la siguiente
forma:

/**
* test creating a Log using Mocks of Prophecy
*
* @return void
*/
public function testAddLogWithProphecy()
{
$documentProphecy = $this->prophesize(Document::class);

$updateProphecy = $this->prophesize(Query::class);
$updateProphecy->addDocument(Argument::any())->shouldBeCalled();
$updateProphecy->addCommit()->shouldBeCalled();
$updateProphecy->createDocument()->shouldBeCalled();
$updateProphecy->createDocument()->willReturn($documentProphecy

Capítulo 6. Test dobles 47


Testeo de código en desarrollos PHP.

$clientProphecy = $this->prophesize("Solarium\Client");
$clientProphecy->createUpdate()->willReturn($updateProphecy->reveal())

$clientProphecy->createUpdate()->shouldBeCalled();
$clientProphecy->update($updateProphecy)->shouldBeCalled();

$solrHandler = new SolrHandler(array());


$solrHandler->setClient($clientProphecy->reveal());
$solrHandler->handle($this->msg);
$solrHandler->handleBatch(array($this->msg));
}

Como podemos ver, la creación de los objetos colaboradores se realizan con el método prophesize, sin
necesidad de especificarle ningún método ni otro tipo de configuraciones. El comportamiento viene
definido por las expectativas que creamos con la llamada a cada método existente en la clase. Por
ejemplo, la llamada al método shouldBeCalled indica que el método precedente debería ejecutarse
para que ese test sea considerado como válido. De la misma forma, willReturn especificará la salida
que tendrá el método que le precede.

6.5.3 Mockery

Otra alternativa para trabajar con tests dobles es Mockery. Dentro de la comunidad PHP es una opción
bastante utilizada y extendida, pese a que el hecho de que Prophecy esté incluido dentro de PHPUnit,
como indicábamos anteriormente.

La sintaxis de Mockery es tan cómoda como la de Prophecy y es más flexible, pero el


comportamiento es distinto. El autor de Behat, Konstantin Kudryashov, explica su blog [20] la
diferencia conceptual entre Mockery y Prophecy. Resumiendo, la principal diferencia es que Mockery
asocia a la estructura del test el comportamiento del mismo, mientras que Prophecy lo asocia al paso
de mensaje.

Siguiendo con el mismo ejemplo de nuestra extensión de Monolog, el código que validaría nuestra el
envío de mensajes a Apache Solr por parte de Monolog podría ser el siguiente:

/**
* test creating a Log using Mocks of Mockery
*
* @return void
*/
public function testAddLogWithMockery()
{
$mockStrDocument = "Solarium\QueryType\Update\Query\Document\Document[
$documentMockery = \Mockery::mock($mockStr);

$methods = "addDocument,addCommit,createDocument";
$mockStrUpdate = "Solarium\QueryType\Update\Query\Query[$methods]"
$updateMockery = \Mockery::mock($mockStrUpdate );
$updateMockery->shouldReceive('addDocument')->atLeast(1);
$updateMockery->shouldReceive('addCommit')->atLeast(1);
$updateMockery->shouldReceive('createDocument')
->atLeast(1)
->andReturn($documentMockery);

Capítulo 6. Test dobles 48


Testeo de código en desarrollos PHP.

$clientMockery = \Mockery::mock(Client::class);
$clientMockery->shouldReceive('createUpdate')->andReturn($updateMocker
$clientMockery->shouldReceive('update')->with($updateMockery)->atLeast

$solrHandler = new SolrHandler(array());


$solrHandler->setClient($clientMockery);
$solrHandler->handle($this->msg);
$solrHandler->handleBatch(array($this->msg));

Mockery, de forma similar a PHPUnit necesita que le indiquen los métodos a configurar como vemos
en la creación del mock asignado a la variable updateMockery, por ejemplo.

6.5.4 Phake

Phake es otro framework que trata de proveer mocks y stubs para la ejecución de test dobles. Está
inspirado en Mockito (framework de test para Java). En la documentación oficial matiza que la
ventaja de Phake frente a PHPUnit, PHPMock y SimpleTest es que de forma nativa implementa el
concepto de espía. La verdad es que esta no es una ventaja significativa, dado que tanto Prophecy
como Mockery también lo implementan y en PHPUnit se puede llegar a conseguir.

Nuestro ejemplo de para las clases SolrHandler y SolrFormatter tendría la forma:

/**
* test creating a Log using Mocks of Phake
*
* @return void
*/
public function testAddLogWithPhake() {

$document = \Phake::mock(Document::class);
$update = \Phake::mock(Query::class);

\Phake::when($update)->createDocument()->thenReturn($document
$update->addDocument($document);
$update->addCommit();

\Phake::verify($update)->addDocument($document);
\Phake::verify($update)->addCommit();

$client = \Phake::mock(Client::class);
\Phake::when($client)->createUpdate()->thenReturn($update);
$client->update($update);

$solrHandler = new SolrHandler(array());


$solrHandler->setClient($client);
$solrHandler->handle($this->msg);
$solrHandler->handleBatch(array($this->msg));

Capítulo 6. Test dobles 49


Testeo de código en desarrollos PHP.

6.5.5 AspectMock

AspectMock es otra herramienta para generar mocks en PHP, desarrollada por la comunidad detrás de
Codeception.

A diferencia de las otras librerías analizadas, AspectMock permite la redefinición de funciones y


clases de forma dinámica, es decir, en tiempo de ejecución. Las funcionales que mencionan en la
documentación son:

Creación de test dobles para métodos estáticos.


Creación de test dobles para métodos de clases llamadas desde cualquier sitio.
Redefinición de métodos en tiempo de ejecución.
Sintaxis simple y fácil de recordar.

De estas funcionalidades vamos a destacar el punto de redefinición de métodos y funciones en tiempo


de ejecución. En la documentación encontramos un ejemplo para redefinir la función "time" de PHP,
la cual devuelve la hora del sistema. El ejemplo sería el siguiente:

namespace demo;
test::func('demo', 'time', 'now');
$this->assertEquals('now', time());

Testear funcionalidades que dependan de la función time suele tener una complejidad adicional, dado
el valor devuelto por esta función siempre es distinto. Aunque el ejemplo aprovecha la forma en la
que funcionan los namespaces en PHP, con este framework de test dobles tenemos la posibilidad de
alterar en tiempo de ejecución el comportamiento de la función time.

Características PHPUnit Prophecy Mockery Phake


Instancias de clases no existentes. X
Posibilidad de testear métodos mágicos X X X
Interfaz legible (Fluent Interface) X X X
Mocks Parciales X X
Espías X X X

: Diferencias entre distintos frameworks de mocks

6.6 Mocks especiales


Algunas de las dependencias que podemos encontrar en nuestro SUT van más alla de otras piezas de
código, como son elementos a más bajo nivel, por ejemplo el sistema de fichero, o el propio
comportamiento de las funciones del lenguajes. Para trabajar con estas dependencias en los SUT
existen herramientas que nos ayudan a simular o alterar el comportamiento, permitiéndonos dejar en
un estado conocido y repetible.

Para resolver las dependencias con el sistema de fichero hay librerías como vfsStream [22].

6.6.1 Testeando el sistema de ficheros: vfsStream

Algunas funcionalidades que necesitamos cumplir cuando desarrollamos software tiene dependencias
de otras partes del sistema, como del sistema de ficheros. Esto genera otro punto en el que testear un
fragmento de código aislado es complicado por dicha dependencia. Un ejemplo clásico podría ser el

Capítulo 6. Test dobles 50


Testeo de código en desarrollos PHP.

de una clase que necesita crear una carpeta para almacenar ficheros. Una posible solución podría ser
la de asignar en los métodos setUp y tearDown dicha necesidad. En el caso de setUp, podríamos
verificar que no existe la carpeta y borrarla en el caso de que existiera. En el caso de tearDown, el
efecto contrario, borrar la carpeta. La idea principal es que cada test deje el sistema sin alteraciones
tras su ejecución. El problema de esta solución es que se sigue accediendo a disco y dejando cierta
incertidumbre en la ejecución del test. Además, podemos encontrar problemas adicionales si el disco
tiene algún problema, hay modificaciones no deseadas en el disco, etc...

Para solventar este tipo de situaciones existen librerías como vfsStream. Esta herramienta es un
simulador del sistema de ficheros, el cual ejecuta en memoria todo lo relativo al disco.

Un ejemplo sería, dada la clase FileSystemCache:

class FileSystemCache {

private $dir;

public function __construct($dir) {


$this->dir = $dir;
}

public function store($key, $data) {


if (!file_exists($this->dir)) {
mkdir($this->dir, 0700, true);
}
file_put_contents($this->dir . '/' . $key, serialize($data));
}
}

El test unitario utilizando vfsStream sería:

use org\bovigo\vfs\vfsStream;

class FileSystemCacheWithVfsStreamTest extends \PHPUnit_Framework_TestCase


{
private $root;
public function setUp() {
$this->root = vfsStream::setup();
}
/**
* @test
*/
public function createsDirectoryIfNotExists() {
$cache = new FileSystemCache($this->root->url() . '/cache');
$cache->store('example', ['bar' => 303]);
$this->assertTrue($this->root->hasChild('cache'));
}
/**
* @test
*/
public function storesDataInFile() {
$cache = new FileSystemCache($this->root->url() . '/cache');

Capítulo 6. Test dobles 51


Testeo de código en desarrollos PHP.

$cache->store('example', ['bar' => 303]);


$this->assertTrue($this->root->hasChild('cache/example'));
$this->assertEquals(
['bar' => 303],
unserialize($this->root->getChild('cache/example')->getContent
);
}
}

Frente al test unitario utilizando únicamente PHPUnit:

class FileSystemCacheWithoutVfsStreamTest extends \PHPUnit_Framework_TestCase


{
/**
* ensure that the directory and file are not present from previous run
*/
private function clean() {
if (file_exists(__DIR__ . '/cache/example')) {
unlink(__DIR__ . '/cache/example');
}
if (file_exists(__DIR__ . '/cache')) {
rmdir(__DIR__ . '/cache');
}
}

public function setUp() {


$this->clean();
}

public function tearDown() {


$this->clean();
}

/**
* @test
*/
public function createsDirectoryIfNotExists() {
$cache = new FileSystemCache(__DIR__ . '/cache');
$cache->store('example', ['bar' => 303]);
$this->assertFileExists(__DIR__ . '/cache');
}

/**
* @test
*/
public function storesDataInFile() {
$cache = new FileSystemCache(__DIR__ . '/cache');
$cache->store('example', ['bar' => 303]);
$this->assertEquals(
['bar' => 303],
unserialize(file_get_contents(__DIR__ . '/cache/example'
);

Capítulo 6. Test dobles 52


Testeo de código en desarrollos PHP.

}
}

En el ejemplo, obtenido de la documentación de vfsStream hemos creado la clase FileSystemCache,


que no es más que una sencilla caché en disco. Para documentar la diferencia del uso de vfsStream
hemos creado dos clases de testeo, FileSystemCacheWithVfsStreamTest y
FileSystemCacheWithoutVfsStreamTest. En estas clases queda reflejada lo que comentábamos cuando
hacíamos referencia a la necesidad de implementar los métodos setUp y tearDown en los tests sin
vfsStream, y como no es necesario en la clase de test con vfsStream.

Capítulo 6. Test dobles 53


Testeo de código en desarrollos PHP.

Capítulo 7. TDD
Desarrollo guiado por tests (TDD)
7.1 Qué es el TDD
Las siglas TDD (en inlgés Test-driven development), se puede traducir como "desarrollo guiado por
tests". Lo que quiere significar es que el desarrollo del software debe estar guiado por los tests, o
dicho de otra forma, para desarrollar o refactorizar un fragmento de código, debemos escribir los tests
que verifiquen ese comportamiento antes que el código que lo implementa. El concepto Red-Green
testing, viene de que, para crear la funcionalidad final, primero debemos crear un test que no estará
resuelto (Red), y tras la implementación de una solución, el test deberá pasar correctamente (Green).

El desarrollo en TDD está asociado siempre al desarrollo de test unitarios, no de test funcionales o de
integración. El motivo es porque, como veremos a continuación, siguiendo la metodología TDD se
intenta testear el mínimo código posible que completaría una parte de la especificación del problema.

7.1.1 Pasos a seguir

Creación de un test.

Dada una especificación a cumplimentar, creamos un test unitario que debe cumplir la
implementación a desarrollar.

Ejecución del test comprobando que el test falla.

Para cada creación de un nuevo test, ejecutamos el conjunto de tests y verificamos que la
implementación que estamos añadiendo no está resuelta por el estado actual del código, dado que los
test producen el error correspondiente.

Escribir código para pasar el test.

Implementamos la funcionalidad, con el mínimo código posible que pase el test. De esta forma,
vamos desarrollando paso a paso una funcionalidad completa, pero con la intención de contemplar
todos los escenarios posibles.

Ejecutar el test, comprobando que pasa el test.

Ejecutamos el conjunto de tests para ver que el código que hemos implementado se comporta de la
forma esperada.

Refactorizar el código

Este paso es importante para, una vez estar seguros de que tenemos una solución para nuestro
problema, debemos dejar el código de la forma más legible y eficiente posible.

Repetir

El proceso debe repetirse por cada iteración del desarrollo.

Los defensores del desarrollo por TDD defiende que siguiendo estos pasos la calidad del código

Capítulo 7. TDD 54
Testeo de código en desarrollos PHP.

mejor, la cobertura de tests está garantizada y los diseños arquitecturales más meditados. Los
retractores de esta metodología rechazan la necesidad de seguir todos estos pasos, de una forma tan
metódica.

7.2 Ejemplo: FizzBuzz


Para ilustrar de una forma más comprensible como sería el procedimiento, vamos a realizar la
implementación de un juego llamado FizzBuzz. FizzBuzz es un juego de niños con las siguientes
reglas: Un grupo de niños en un circulo comienzan a contar del uno hasta N, de forma consecutiva, es
decir, el primer niño dice "uno", el segundo "dos", etc... con las excepciones de que, si el número es
divisor de tres, el niño debe decir "Fizz", si el número es divisor de cinco, el niño debe decir "Buzz" y
si es divisor de tres y cinco el niño debe decir "FizzBuzz!".

Para nuestro caso, dado un número devolveremos la cadena "fizzbuzz" desde el uno hasta el número
indicado, sustituyendo "Fizz" por "F" y "Buzz" por "B".

Por ejemplo, nuestro FizzBuzz de 10 sería la cadena de textos "1 2 F 4 B F 7 8 F B".

Para simplificar nuestro ejemplo, solo en el último test realizaremos la refactorización que hay que
realizar siguiendo los pasos TDD.

El primer test que debemos deberíamos pasar sería la cadena FizzBuzz(1), cuyo resultado debería ser
"1". El test podría ser el siguiente:

class FizzBuzzTest extends PHPUnit_Framework_TestCase {

public function setUp() {


$this->fizzbuzz = new FizzBuzz();
}

public function testOne() {


$this->assertEquals("1", $this->fizzbuzz->solve(1));
}
}

Omitimos la devolución del error dado que en este punto, ni siquiera existe la clase FizzBuzz en
nuestro repositorio de código.

Y la implementación que resuelve el test:

class FizzBuzz {

public function solve($number) {


return $number;
}

Como se puede apreciar, para pasar este test no hemos intentado resolver el problema completo, sino
que, como nos propone el procedimiento, hemos implementado el mínimo necesario para resolver el
test. En este caso una solución puede ser devolver el mismo valor que el valor de entrada.

Capítulo 7. TDD 55
Testeo de código en desarrollos PHP.

El siguiente paso será crear un nuevo test, que nos exija añadir funcionalidad a nuestro código. El test
FizzBuzz(2) no pasará, dado que la mínima implementación anterior está preparada para dar el
resultado requerido por la implementación. El test sería:

public function testTwo() {


$this->assertEquals("12", $this->fizzbuzz->solve(2));
}

Y la respuesta obtenida por PHPUnit:

There was 1 failure:

1) FizzBuzzTest::testTwo
Failed asserting that 2 matches expected '12'.

/home/jose/workspace/MyThings/TestingBook/TddTesting/test/FizzBuzzTest.php:16

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

Como esperábamos, el test no ha pasado. A partir de ahora solo mostraremos la implementación del
método solve. La implementación que pasaría el tests podría ser:

public function solve($number) {


$str = "";
for($i=1;$i<=$number;$i++) {
$str.=$i;
}

return $str;
}

Siguiendo el procedimiento, ahora añadiríamos un nuevo test para verificar la resolución del
problema para un múltiplo de tres. El test sería:

public function testThree() {


$this->assertEquals("12F", $this->fizzbuzz->solve(3));
}

Y como esperábamos una vez más el test. En la implementación anterior el código no resolvía el caso
de que el número de entrada fuera múltiplo de tres. El error obtenido es:

1) FizzBuzzTest::testThree
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'12F'
+'123'

Y la implementación que pasaría el test:

Capítulo 7. TDD 56
Testeo de código en desarrollos PHP.

public function solve($number) {


$str = "";
for($i=1;$i<=$number;$i++) {
if($i%3==0) {
$str.="F";
} else {
$str.=$i;
}
}

return $str;
}

En este paso necesitamos añadir la el resultado correcto para números múltiplos de cinco. El test sería
el siguiente:

public function testFive() {


$this->assertEquals("12F4B", $this->fizzbuzz->solve(5));
}

Volvemos a ejecutar los tests y obtenemos el resultado siguiente:

There was 1 failure:

1) FizzBuzzTest::testFive
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'12F4B'
+'12F45'

Y resolvemos implementación requerida por el tests modificando nuestro método "solve" de la


siguiente forma:

public function solve($number) {


$str = "";
for($i=1;$i<=$number;$i++) {
if($i%3==0) {
$str.="F";
} elseif($i%5==0) {
$str.="B";
} else {
$str.=$i;
}
}

return $str;
}

Capítulo 7. TDD 57
Testeo de código en desarrollos PHP.

Y en último lugar, necesitamos implementar el caso en el que un número sea múltiplo de tres y de
cinco, para ello, creamos el tests para verificar el resultado para el número dieciséis.

public function testSixteen() {


$this->assertEquals("12F4BF78FB11F1314FB16", $this->fizzbuzz->solve(
}

El error obtenido por PHPUnit, como era de esperar, es el siguiente:

Resultado del último test:

1) FizzBuzzTest::testSixteen
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'12F4BF78FB11F1314FB16'
+'12F4BF78FB11F1314F16'

Y la implementación que resuelve todos los casos anteriores podría ser:

public function solve($number) {


$str = "";
for($i=1;$i<=$number;$i++) {
if ($i%3 == 0 && $i%5==0) {
$str .= "FB";
} elseif($i%3==0) {
$str.="F";
} elseif($i%5==0) {
$str.="B";
} else {
$str.=$i;
}
}

return $str;
}

Dado que el procedimiento TDD contempla el paso de refactorización hemos refactorizado el modoso
solve de la siguiente manera:

public function solve($number) {


$str = "";
for($i=1;$i<=$number;$i++) {
$fizz = "";
$buzz = "";
if ($i%3 == 0) $fizz = "F";
if ($i%5 == 0) $buzz = "B";
if($fizz =="" and $buzz=="") {
$str.=$i;
} else {
$str.=$fizz.$buzz;

Capítulo 7. TDD 58
Testeo de código en desarrollos PHP.

}
}

return $str;
}

Tras la refactorización hemos vuelvo a ejecutar los tests y obtenido el resultado positivo que
esperábamos.

Incluso si quisiéramos refactorizar nuestro método solve, para permitir que sea configurable, es decir,
poder poner otros números primos como a los que se deba decir otra palabra, con nuestros conjunto
de test podríamos hacerlo garantizando que el comportamiento se mantendrá para las entradas
definidas. Una refactorización de este tipo podría ser de la siguiente forma:

public function solve($number, $primes=[3=>"F",5=>"B"]) {

$solution = "";
for($i=1;$i<=$number; $i++) {
$str = [];
foreach($primes as $key => $value) {
if($i%$key==0) {
$str[] = $value;
}
}

if(count($str)>=1) {
$solution.= implode("", $str);
} else {
$solution.= $i;
}
}

return $solution;
}

Esta implementación del método solve pasaría todos los test creados anteriormente y permitiría
distintas opciones en el segundo parámetro de entrada permitiendo más flexibilidad.

Nuestro ejemplo quizás sea un poco exagerado, pero el procedimiento que define TDD es
prácticamente este. Otro punto considerable es la cantidad de código de testeo que se necesita
desarrollar para seguir la metodología. Para el ejemplo anterior, posiblemente utilizando el último test
tendríamos una cobertura de código casi completa, con el requirimiento funcional, aunque no se
habría resuelto el problema considerando todos los casos.

Para algunos desarrolladores es excesivo el tiempo que hay que emplear para desarrollar algo. Sin
embargo, el nivel de detalle al que se puede llegar desarrollando la solución de un problema es muy
detallada.

7.3 Ventajas de desarrollar bajo TDD


Trabajar siguiendo la metodología TDD nos puede aportar beneficios en distintos aspectos a la hora
de desarrollar.

Capítulo 7. TDD 59
Testeo de código en desarrollos PHP.

Introducimos el hábito de testear el código de forma automática.

Pensar en el test primero obliga a pensar mejor el interfaz de la aplicación. Este pensamiento en
el interfaz nos ayuda a ver como la clase será usada y a mantener separada dicha interfaz de la
implementación.

El procedimiento de tener resultados es más corto, debido al propio ciclo de TDD.

Siguiendo la metodología TDD se crea una detallada especificación.

Se minimiza el tiempo decicado de depurar el código. Si es necesaria esta depuración sería más
sencilla ya que el resto de la aplicación también está testeada.

Siguiendo TDD somos avisados antes de qué refactorización rompió el estado verde de los
tests.

Permite que el diseño sea más adaptable a los cambios del entendimiento del problema.

Se consigue un detallado nivel de test de regresión.

Cobertura del 90% del código casi garantizada.

Hay que destacar que trabajar con TDD no son todo ventajas. Algunas desventajas es que seguir la
metodología de forma disciplinada no es fácil de aprender. Los desarrolladores que siguen esta
metodología dicen que durante los primeros meses de seguirla, perdieron productividad.

7.4 Framewoks expecíficos de TDD: Phpspec


Phpspec [23] es una herramienta que puede ayudar a escribir código de forma limpia siguiendo BDD
o Behavior driven development, en español, desarrollo guiado por comportamiento.

Como veremos en la siguiente sección, BDD es una técnica a nivel de historias de usuarios y a nivel
de la especificación. Phpspec es usada para desarrollar los tests a nivel de especificación o SpecBDD.
En realidad, no hay diferencia entre SpecBDD y TDD, por ello hemos incluido Phpspec dentro de la
sección de TDD y no en la sección de BDD.

El valor añadido de usar una herramienta de especificación en lugar de una herramienta de testeo es
por el lenguaje en sí. Los desarrolladores que optaron por testear su código siguiendo TDD, pronto se
fijaron en la importancia de desarrollar fijándose en el comportamiento y el diseño del código. Por
ello, BDD y herramientas que siguen la filosofía SpecBDD se han centrado en eliminar el vocabulario
de testeo.

7.4.1 Diferencias entre SpecBDD y StoreBDD

Herramientas que basan la filosofía de StoryBDD, como Behat, que también veremos en la próxima
sección, ayudan a entender y clarificar el dominio. Estas herramientas especifican las funcionalidades
de forma narrativa, sus necesidades y cuales son los objetivos. Mientras tanto, con SpecBDD y
herramientas como Phpscpec nos centramos en la implementación.

7.4.2 Instalación de Phpspec

Para instalar Phpspec en nuestro proyecto a través de Composer necesitamos añadir dependencia
correspondiente:

Capítulo 7. TDD 60
Testeo de código en desarrollos PHP.

{
"require-dev":{
"phpspec/phpspec": "~2.3"
}
}

Después de añadir las lineas anteriores en nuestro fichero composer.json necesitamos ejecutar el
comando composer install, para instalar las dependencias.

7.4.3 Trabajando con Phpspec

En esta sección vamos a desarrollar un ejemplo utilizando Phpspec. Hay que tener en cuenta que
Phpspec no es solo una herramienta de testeo, sino también de desarrollo, por lo que la el uso es
distinto que el que podemos dar a otros frameworks como PHPUnit.

Supongamos que estamos desarrollando una web en la que usuarios pueden tener un álbum de fotos,
como podría ser Flickr.com. Para dicho sistema necesitaríamos la clase User y la clase Image. Lo
primero que tendríamos que hacer siguiendo Phpspec es crear la especificación de la clase usuario.
Para ello, desde nuestro terminal ejecutaríamos el siguiente comando:

bin/phpspec desc User


Specification for User created in spec.

Esta ejecución crearía la clase php/UserSpec.php con el siguiente código:

namespace spec;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class UserSpec extends ObjectBehavior


{
function it_is_initializable()
{
$this->shouldHaveType('User');
}
}

Con esta clase estamos creando una especificación, es decir, un test, sobre una clase User. Hay que
tener en cuenta, que en este punto todavía no hemos creado la clase User, por lo que la ejecución de
los tests no pasaría. En este momento podemos ver algunas particularidades de Phpspec, por ejemplo:

Para Phpspec cada método público que comience por it_ es un test, en lugar del prefijo test
necesario en PHPUnit o Atoum.
La clase a testear, en nuestro ejemplo UserSpec es una instancia del SUT User. Esto le da
sentido al punto anterior, dado que para clase de especificación estamos definiendo el
comportamiento, no testeando su implementación.
Los parámetros de entrada de cada tests o especificación (métodos it_) son mockeados
directamente por el framework, como veremos más adelante.

Para continuar con el ejemplo ejecutaremos los tests desde nuestro terminal con el siguiente comando:

Capítulo 7. TDD 61
Testeo de código en desarrollos PHP.

bin/phpspec run

Dado que la clase User no existe todavía, esta ejecución nos devolverá la siguiente pregunta:

Do you want me to create `User` for you? [Y/n]

Después de pulsar Y e intro, la clase User.php será creada en la ruta especificada por nuestro
namespace con el código:

class User
{
}

En este punto, si ejecutamos los tests de nuevo veremos que tenemos una respuesta positiva.

El siguiente paso es desarrollar la funcionalidad que permite a un usuario añadir una imagen. Para
ello, en lugar de implementar el método que nos aportaría esta funcionalidad, el método addImage,
crearemos un método nuevo en nuestra clase UserSpec que nos aporte dicha funcionalidad.

El código que especificaría el comportamiento de este método sería:

function it_add_image(Image $image)


{
$this->addImage($image);

$this->shouldHaveCount(1);
}

A continuación volvemos a ejecutar los tests escribiendo en el terminal:

bin/phpspec run

Como el método addImage ni siquiera existe en nuestra clase User, esta ejecución nos presentaría la
siguiente pregunta:

Do you want me to create `User::addImage()` for you? [Y/n]

A continuación pulsamos "Y" y nuestra clase User se modificará con la definición del método que
pasaría el test sin implementar, con la forma:

public function addImage($argument1)


{
// TODO: write logic here
}

Para pasar el test, la implementación que deberíamos realizar es sencilla. El método addImage
quedaría de la siguiente forma:

public function addImage($image)


{
$this->images[] = $image;

Capítulo 7. TDD 62
Testeo de código en desarrollos PHP.

La siguiente funcionalidad a implementar podría ser añadir más de una imagen a la vez, por ejemplo
creando un método addImages dentro del la clase User que tenga como parámetro de entrada un array
de imágenes.

El test podría ser:

function it_add_several_images(Image $img1, Image $img2) {


$this->addImages([$img1, $img2]);

$this->getImages()->shouldHaveCount(2);
}

Ejecutamos nuevamente el comando de tests de Phpspec:

bin/phpspec run

Y Phpspec nos volverá a preguntar por añadir el método addImages y nos dejará la responsabilidad de
implementar dicho método. Pasando directamente a la implementación del método generado por
Phpspec, una solución podría ser:

public function addImages($images)


{
$this->images = array_merge($this->images, $images);
}

En el caso anterior no hemos contemplado la posibilidad que algún elemento del array que pasamos
como parámetro al método addImages sea del tipo Image. Para tener en cuenta esta consideración
podemos añadir el siguiente test:

function it_should_be_instance_of_images(Image $img1, \stdClass $obj)


$this->shouldThrow('\InvalidArgumentException')->duringAddImages([
}

En este test estamos especificando que el método addImage lance una excepción si alguno de los
elementos del array no es de la clase Image. Tras ejecutar el test obtenemos como resultado la
siguiente salida en el terminal:

User
32 X it should be instance of images
expected to get exception, none got.

Para pasar el test una implementación válida podría ser la siguiente:

public function addImages($images)


{
foreach($images as $image) {
if(!$image instanceof Image) {
throw new InvalidArgumentException("All element of the array has t
}

Capítulo 7. TDD 63
Testeo de código en desarrollos PHP.

}
$this->images = array_merge($this->images, $images);
}

El resto del proceso es iterar de la misma forma, hasta acabar con la implementación deseada. Como
vemos, el proceso guiado con Phpspec es prácticamente el mismo que detallamos en la sección de
TDD, siendo la propia herramienta la que crea las clases y métodos cuando no existen y dejándonos la
responsabilidad de implementar dichos métodos, para pasar los tests especificados. En el ejemplo solo
hemos dado visibilidad a una pequeña parte de las posibilidades del framework. Algunas de las
posibilidades que, por la simplicidad del ejemplo, nos estamos saltando son:

Especificación de identidad: Especifica la relación de igualdad de valor y tipo de PHP ( === ).

Especificaciones de comparaciones (Comparison Matcher). Especifica la relación de igualdad


de valor, sin considerar el tipo, tal y como lo hace PHP con el operador ==. Por ejemplo, "5" ==
5, es true.

Especificación de tipo (Type Matcher): Con este tipo de métodos se puede concretar el tipo de
un objeto, la devolución de un método, si es una instancia de una clase o si implementa un
interfaz.

Especificación de excepciones (Throw Matcher): Especificamos los casos o situaciones por las
que devolvemos una excepción, como hemos visto en el ejemplo al introducir un argumento
incorrecto.

Identificación del estado de objeto (ObjectState Matcher). Testea el valor del estado de un
objeto llamando métodos del propio objeto. Estos métodos deberán comenzar con is o has y
devuelven true o false.

Identificador de contador (Count Matcher). Testea sobre el número de elementos de la


devolución de un método. El valor devuelto por el método puede ser un array o un objeto que
implemente el interfaz \Countable.

Identificador de typo (Scalar Matcher). Especifica el tipo de datos del valor devuelto por el
método.

Identificación de elemento o elementos en un array (ArrayContain Matcher).

Estos pueden ser los "Matcher", las formas de determinar el comportamiento de una implementación
más comunes, pero hay otras: ArrayKeyWithValue, ArrayKey, StringStart, StringEnd, StringRegex.
Además, se pueden extender otros Matcher utilizando un Inline Matcher. En la documentación de
Phpspec [23] podemos ver este ejemplo de Inline Matcher, en el que se determina un valor por
defecto para el atributo username la clase Movie:

namespace spec;

use PhpSpec\ObjectBehavior;
use PhpSpec\Matcher\InlineMatcher;

class MovieSpec extends ObjectBehavior


{
function it_should_have_some_specific_options_by_default()
{
$this->getOptions()->shouldHaveKey('username');

Capítulo 7. TDD 64
Testeo de código en desarrollos PHP.

$this->getOptions()->shouldHaveValue('diegoholiveira');
}

public function getMatchers()


{
return [
'haveKey' => function ($subject, $key) {
return array_key_exists($key, $subject);
},
'haveValue' => function ($subject, $value) {
return in_array($value, $subject);
},
];
}
}

Capítulo 7. TDD 65
Testeo de código en desarrollos PHP.

Capítulo 8. BDD
Desarrollo guiado por comportamiento (BDD)
8.1 Definición y principios
El desarrollo guiado por comportamiento, del ingĺes BDD (behavior-driven development) es una
forma especializada de Lógica de Hoare aplicada a TDD la cual se centra en la especificación del
comportamiento del software utilizando unidades del lenguaje de dominio de la situación.

De forma análoga a TDD, BDD se centra en los pasos:

Definición del test.


Implementación de la lógica que cumple el test.
Verificación de la ejecución del test, y el cumplimiento de la especificación.

El desarrollo guiado por comportamiento especifica que tests de unidades de software deben
cumplimentar unidades de comportamiento deseado.

Extrayendo de desarrollo ágil el "comportamiento deseado" en este caso consiste en los


requerimientos definidos por el negocio, esto es, el comportamiento deseado que tiene valor para el
negocio, sea cual sea la entidad de software responsable que esté bajo construcción.

Estos comportamientos deseados deberían estar definidas como historias de usuario en un lenguaje
común entre desarrolladores y el resto del equipo involucrado en el proyecto.

Para ello BDD elige utilizar un formato semi-formal para la especificación de comportamiento que
está tomado de las especificaciones de la historia de usuario desde el análisis y diseño de la
aplicación.

BDD especifica que los analistas de negocio y desarrolladores deben colaborar en esta materia y
deben especificar el comportamiento en términos de historias de usuario, que debe quedar reflejada de
forma expresa en un documento específico. Cada historia de usuario debe, de alguna manera, seguir la
siguiente estructura:

Título La historia debe tener un claro, título explícito.

Narrativa

Una breve descripción introductoria que especifica:

Quien (que negocio o función del proyecto) es el responsable o los grupos de interés principal
de la historia (el actor que se deriva beneficio de la historia).
El efecto que el actor quiere que la historia tenga.
El valor que se le asigna a la historia de usuario.

Los criterios de aceptación o escenarios

Una descripción de cada caso específico de la narración. Este escenario tiene la siguiente estructura:

Se inicia mediante la especificación de la condición inicial que se supone que es cierto en el

Capítulo 8. BDD 66
Testeo de código en desarrollos PHP.

comienzo del escenario. Esto puede consistir en una sola cláusula, o en varias cláusulas.
Los eventos que desencadenan el inicio del escenario.
Por último, se establece el resultado esperado, en una o más cláusulas.

8.2 Behat
Para el lenguaje PHP existe un proyecto de código libre llamado Behat [24] que nos da las
herramientas necesarias para poder realizar este tipo de desarrollos guiados por comportamiento. Para
poder interpretar las historias de usuarios de forma automática, Behat interpreta el lenguaje Gherkin.
Gherkin es un lenguaje específico de dominio que permite describir comportamiento de software sin
necesidad de que el comportamiento esté desarrollado.

El ejemplo que vamos a desarrollar en esta sección es, a posteriori, un conjunto de tests para verificar
el comportamiento del sistema de blogs más extendido en la comunidad PHP: WordPress. Hemos
decidido realizar este ejemplo por dos motivos: simplicidad y funcionalidad. Aunque en nuestro
ejemplo no vamos a especificar funcionalidades nuevas, que es el principal objetivo de Behat,
también puede ser utilizado como framework para tests de aceptación y ser extendido posteriormente
para crear test en BDD. Con esto queremos matizar la posibilidad de crear test de regresión en
proyectos con código no testeado para la posterior creación de nuevas funcionalidades siguiendo
BDD.

Para nuestro ejemplo, además de Behat necesitamos otro proyecto, también de código abierto,
llamado Mink. Este proyecto es un conector entre Behat y los distintos simuladores o conectores con
los navegadores web. Dado que lo que deseamos testear en nuestro caso es una proyecto web (un blog
basado en Wordpress como test de aceptación), necesitamos de esta herramienta para realizar las
distintas interacciones con la el blog. Además de Mink, y como simulador web, utilizaremos
Selenium.

Actualmente hay otras alternativas a Selenium con distintas funcionalidades, en función de si son
emuladores de navegadores o clientes HTTP. A continuación presentamos la tabla 8.1, en la que
detallamos de los diferentes conectores que hay disponibles para la versión actual de Mink (1.7.0).

Funcionalidad BrowserKit/Goutte Selenium2 Zombie Selenium Sahi


Navegación entre páginas Sí Sí Sí Sí Sí
Manipulación formularios Sí Sí Sí Sí Sí
Autenticación HTTP auth Sí No Sí No No
Manipulación de ventanas No Sí No Sí Sí
Manipulación de iFrames No Sí No Sí No
Acceso a cabeceras de petición Sí No Sí No No
Acceso a cabeceras de respuesta Sí No Sí No No
Manipulación de cookies Sí Sí Sí Sí Sí
Acceso a códigos de respuesta Sí No Sí No No
Manipulación de ratón No Sí Sí Sí Sí
Coger y arrastrar No Sí No Sí Sí
Acciones de teclado No Sí Sí Sí Sí
Visibilidad de elementos No Sí No Sí Sí
Evaluación Javascript No Sí Sí Sí Sí
Tamaño de ventanas No Sí No No No
Maximización de ventanas No Sí No Sí No

Capítulo 8. BDD 67
Testeo de código en desarrollos PHP.

: Diferencias entre simuladores de navegadores.

8.2.1 Consideraciones sobre testeo BDD

Testear funcionalidades web utilizando Behat u otras alternativas como puede ser utilizar Selenium
directamente viene asociado con algunas dificulades. Uno de los problemas que nos encontramos es el
rendimiento. Testear una aplicación web abriendo un navegador, realizando un conjunto de acciones,
cerrando el navegador y repetir el proceso por cada test puede llegar a ser lento. En algunos proyectos
estos procesos pueden llegar a tardar varios minutos, incluso en situaciones extremas horas. Para este
problema la forma de solucionarlo es utilizando herramientas de integración continua, como veremos
en el capítulo dedicado a ello.

Otro problema asociado al testeo con este tipo de herramientas es el de mantenibilidad. Generalmente
los tests funcionales o BDD están ligados a la estructura de los documentos HTML que genera la
aplicación web para ser renderizado por el navegador. Esto quiere decir, que cada vez que hay un
cambio en el documento HTML que esté asociado a un tests concreto, dicho test puede dejar de
funcionar.

Otro problema viene asociado con el estado en el que la ejecución de un test deja la aplicación. A
diferencia de los tests unitarios o tests de integración, los tests funcionales pueden realizar acciones de
escritura en bases de datos, disco, etc. Esta situación dejar la aplicación de forma que no pueda
ejecutar los tests sin producir un error. Pongamos por ejemplo un registro de usuario, en el que dicho
usuario solo puede existir una vez con un mail determinado. Para la primera ejecución de los tests,
funcionarían correctamente, pero para la segunda, si no realizamos alguna manipulación de la base de
datos el tests no pasaría, dado que el sistema determinaría que ya existe un usuario con el mail.

Posibles soluciones a esta situación puede ser destruir la base de datos completa y recrearla con cada
ejecución del test, o usar herramientas de gestión de migraciones de bases de datos.

Otra situación complicada para testeos con Behat es el envío de mails a usuarios. Enviar mails a
cuentas de usuario, o de testing, cada vez que se ejecutan los tests no es lo más conveniente. Para
evitar este envío de mails es conveniente utilizar librerías como FakeSMTP.

La activación de cuentas de usuario a través del envío de mail es otra situación complicada que nos
podemos encontrar cuando testeamos una aplicación web. Las soluciones posibles para este problema
son, o modificar la base de datos directamente o enviando un mail y extendiendo el test funcional para
que se conecte por SMPT, lea el mail y active la cuenta.

8.2.2 Ejemplo de testeo usando Behat

Para ilustrar la forma en la que se usa Behat y ver los problemas anteriormente mencionados vamos a
testear tres funcionalidades básicas de un blog Wordpress. Para que nuestro ejemplo sea
independiente de la instalación, vamos a utilizar Docker como herramienta de contenerización de
aplicaciones. Docker no entra en la temática de teste, pero para nuestro ejemplo nos ayudará a tener
un blog Wordpress con las mismas condiciones para poder ejecutar el mismo test.

En pocas palabras, Docker es una herramienta capaz de crear contenedores de aplicación,


ejecutándose en el sistema operativo (necesariamente Linux), pero bajo un entorno aislado del resto
del sistema operativo. Para más información recomendamos visitar la web oficial:
http://www.docker.com

Para simplificar la instalación de nuestro blog WordPress, Docker tiene una aplicación llamada
Docker Compose, con la cual, mediante la especificación de las necesidades de aplicación en un
documento Yaml y la ejecución del comando correspondiente tenemos un blog Wordpress instalado en

Capítulo 8. BDD 68
Testeo de código en desarrollos PHP.

un contenedor, aislado del resto del sistema operativo.

Para poder ejecutar este ejemplo suponemos que Docker y Docker Compose están instalados
correctamente en la máquina siguiendo las instrucciones de la página oficial.

En nuestro ejemplo necesitamos el siguiente documento Yaml:

wordpress:
image: wordpress
links:
- db:mysql
ports:
- 8080:80

db:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: example

Y ejecutar el siguiente comando:

docker composer up -d

Una vez realizados estos pasos, podemos visitar http://localhost:8080 y veremos la página de
instalación de nuestro blog WordPress. Para poder ejecutar los tests necesitamos realizar la
instalación, que consiste en rellenar los campos Site Title, Username, Password y Email. Con esto
podemos ejecutar los tests de Behat.

Hemos decidido tres escenarios para este ejemplo. El proceso de login, la creación de un post y la
eliminación del post insertado. Como veremos a continuación, dado que para Behat cada escenario
debe ser independiente la ejecución del login se repetirá en los tres escenarios. Como habíamos
indicando anteriormente, los test se especifican en lenguaje Gherkin en un fichero formato Yaml.

Para poder ejecutar los tests necesitamos dos cosas más, el fichero de configuración propio de Behat,
que debe llamarse behat.yml (también en formato Yaml). En nuestro caso su contenido debería ser:

# behat.yml
default:
extensions:
Behat\MinkExtension\Extension:
base_url: http://localhost:8080
goutte: ~
selenium2: ~

En nuestra configuración le estamos diciendo que nuestro blog tiene como url base
http://localhost:8080, que es la que utilizamos para configurar con Docker nuestro blog. Además,
estamos indicando que utilizaremos los clientes HTTP Goute y el simulador de navegadores web
Selenium.

Nuestro fichero composer.json, el cual contendrá todas las dependencias para poder ejecutar Behat,
deberá contener:

Capítulo 8. BDD 69
Testeo de código en desarrollos PHP.

{
"require-dev": {
"behat/behat": "2.4.*@stable",
"behat/mink": "1.4.*@stable",
"behat/mink-extension": "*",
"behat/mink-goutte-driver": "*",
"behat/mink-selenium2-driver": "*"
},
"config": {
"bin-dir": "bin/"
}
}

Además de instalar las dependencias indicadas en composer.json necesitamos tener descargado y en


ejecución el ejecutable de Selenium en la máquina que estamos realizando el test. "Selenium" puede
descargarse de su página oficial: http://www.seleniumhq.org/download/. La versión con la que hemos
realizado estas pruebas ha sido "Selenium Server Standalone 2.45.0" y el comando que hemos
ejecutado en nuestra máquina ha sido:

java -jar selenium-server-standalone-2.45.0.jar

En este punto tenemos un blog Wordpress instalado y ejecutándose y un proyecto PHP capaz de
ejecutar Behat y conectarse con Selenium. Lo último que necesitamos es el código Gherkin para
ejecutar los tres escenarios de login, escritura de un post y eliminación del mismo. La especificación
podría ser la siguiente:

#language: es
Característica: Login

Como administrador del blog Wordpress.


Necesito logarme como editor.

@javascript
Escenario: Logarme en mi propio blog
Dado estoy en "/wp-login.php"
Cuando relleno "user_login" con "jose"
Cuando relleno "user_pass" con "testing"
Cuando presiono "wp-submit"
Entonces la respuesta debe contener "Dashboard"

@javascript
Escenario: Escribo un nuevo post
Dado estoy en "/wp-login.php"
Cuando relleno "user_login" con "jose"
Cuando relleno "user_pass" con "testing"
Cuando presiono "wp-submit"
Dado estoy en "/wp-admin/post-new.php"
Entonces espero 1 segundo
Cuando relleno "post_title" con "Post de ejemplo"
Cuando relleno "content" con "Contenido de un post de ejemplo"
Cuando presiono "publish"
Entonces la respuesta debe contener "Post published"

Capítulo 8. BDD 70
Testeo de código en desarrollos PHP.

@javascript
Escenario: Borro el post introducido anteriormente.
Dado estoy en "/wp-login.php"
Cuando relleno "user_login" con "jose"
Cuando relleno "user_pass" con "testing"
Cuando presiono "wp-submit"
Dado estoy en "/wp-admin/edit.php?post_type=post"
Entonces marco "post[]"
Entonces la casilla de selección "post[]" debe estar marcada
Entonces selecciono "Move to Trash" de "action"
Entonces presiono "doaction"
Entonces la respuesta debe contener "1 post moved to the Trash"
Dado estoy en "/wp-admin/edit.php?post_status=trash&post_type=post"
Entonces marco "post[]"
Entonces la casilla de selección "post[]" debe estar marcada
Entonces selecciono "Delete Permanently" de "action"
Entonces presiono "doaction"
Entonces la respuesta debe contener "1 post permanently deleted"

Por convenio de Behat, este fichero debe situarse en la carpeta /features del proyecto donde queramos
ejecutar los tests.

Si nos fijamos en el código, es perfectamente comprensible por un humano, dado que las sentencias
de ejecución son sencillas y directas. Además, la implementación de Gherkin en PHP soporta varios
idiomas, no solo inglés, por lo que para nuestro ejemplo hemos podido escribir las sentencias de
nuestros tests en castellano.

Behat es fácil de extender. Behat funciona por patrones: esto es, para cada formato de linea, Behat
ejecuta una función propia. Por ejemplo, para una sentencia del tipo 'Dado estoy en "/wp-login.php"',
Behat ejecutará una expresión regular que, si encaja, ejecutará una función interna correspondiente a
esa expresión regular. En este caso le indicará a Selenium que debe abrir la página dentro del dominio
base_url indicada entre comillas. Debido a ese funcionamiento, el sistema es fácilmente extensible.

Para ilustrar esta particularidad de Behat en nuestro ejemplo hemos introducido la sentencia 'Entonces
espero 1 segundo', que no está en el sistema por defecto. Cuando ejecutamos Behat por primera vez la
aplicación nos advierte de que esa sentencia no existe y nos propone crearla dentro de la clase propia
para este efecto: FeatureContext. Para poder detener la ejecución n segundos, hemos implementado el
método esperoSegundo, dentro de la clase FeatureContext de la siguiente forma:

<?php

use Behat\MinkExtension\Context\MinkContext;

/**
* Features context.
*/
class FeatureContext extends MinkContext
{
/**
* Initializes context.
* Every scenario gets it's own context object.
*
* @param array $parameters context parameters (set them up through behat.

Capítulo 8. BDD 71
Testeo de código en desarrollos PHP.

*/
public function __construct(array $parameters)
{
// Initialize your context here
}

/**
* @Given /^espero (\d+) segundos?$/
*/
public function esperoSegundo($arg1)
{
sleep($arg1);
}
}

8.3 Alternativas a Behat


Además de Behat existen otros frameworks de testing que siguen la filosofía BDD. Algunos de ellos
son Pho y Peridot. En esta sección daremos una breve introducción a estos frameworks, dado que no
tienen ni todas las funcionalidades que podemos encontrar con Behat, ni la aceptación por parte de la
comunidad de desarrollo.

8.3.1 Pho

Pho [25] es un framework de testeo BDD para PHP, inspirado en Jasmine (framework para
JavaScritp) y RSpec (framework para Ruby). Las funcionalidades son testeadas con una sintaxis
similar a los dos frameworks del que se inspira y posee de forma nativa la funcionalidad "watch", la
cual permite relanzar un tests al mismo tiempo que se está desarrollando.

Pho expone un lenguaje de dominio específico basado en un reducido conjunto de funciones:


describe, context, in, before, after, beforeEach, afterEach y expect, cuyos significados son:

describe y context: Son intercambiables entre sí, aunque normalmente se utiliza context para
indicar grupos. Se usan pasando un string como parámetro y una función con el test a realizar.

it: Define el test a realizar, es decir, las condiciones previas y los valores esperados.

expect: Define el valor que a devolver el test y las condiciones o relaciones para llegar a ese
valor.

before, after, beforeEach, afterEach: Son análogos a los métodos setUp, tearDown y
setUpBeforeClass y tearDownBeforeClass de PHPUnit.

Con este vocabulario, Pho puede expresar los conceptos básicos que hay bajo TDD, con una sintaxis
que recuerda más a JavaScript como veremos en el siguiente ejemplo, tomado de la propia
documentación de Pho:

describe('A suite', function() {


it('contains specs with expectations', function() {
expect(true)->toBe(true);
});

it('can have specs that fail', function() {

Capítulo 8. BDD 72
Testeo de código en desarrollos PHP.

expect(false)->not()->toBe(false);
});

it('can have incomplete specs');


});

Como vemos, "describe" y context "reciben" como segundo parámetro una funcion, así como la
función "it".

Al igual que vimos con Phpspec, Pho contiene un gran número de "Expectations y Matchers", los
cuales pueden ser vistos en la documentación. En resumen, Pho contiene:

Matching para tipo de datos.


Matching para instancias.
Matching de igualdad estricta.
Matcihng para igual no estricta.
Matching de longitud.
Matching de inclusión.
Matching de expresión regular.
Matching numéricos.
Matching de excepciones.
Matching definido por usuario, el cual implementando el interfaz MatcherInterface, permite que
un usuario pueda defirnir su propio matching.

Pho no contiene ningún mecanismo para trabajar con tests dobles, por lo que en la misma
documentación recomiendan utilizar herramientas existentes como Prophecy o Mockery.

8.3.2 Peridot

Peridot [26] es otro framework de testeo BDD para proyectos en PHP. Los características que definen
a Peridot según la documentación oficial son:

Natural: Escribir tests con la sintaxis describe-it resulta de forma natural. Fácil y claramente se
pueden definir descripciones de como se debe comportar el código bajo test en un lenguaje con
sentido.

Extensible: Peridot es dirigido por eventos, por lo que escribir plugins o extensiones es
sencillo. Los eventos y "scopes" de Peridot permiten añadir tests a clases auxiliares,
frameworks, etc.

Rápido: Peridot es ligero. Ejecutar un conjunto de tests utilizando Peridot es más rápido que
PHPUnit o Phpspec. Además, Peridot permite la ejecución concurrente de los tests de forma
nativa.

Para ver ligeramente algún ejemplo de cómo testear con Peridot, veamos un test obtenido de la
documentación de Peridot:

describe('ArrayObject', function() {
beforeEach(function() {
$this->arrayObject = new ArrayObject(['one', 'two', 'three']);
});

describe('->count()', function() {

Capítulo 8. BDD 73
Testeo de código en desarrollos PHP.

it("should return the number of items", function() {


$count = $this->arrayObject->count();
assert($count === 3, "expected 3");
});
});
});

El resultado de la ejecución del tests anterior sería:

ArrayObject
->count()
Ok should return the number of items

1 passing (19 ms)

Sin entrar en mucho detalle, vemos que Peridot tiene una sintaxis muy parecida a Pho. Ambos
proyectos son una alternativa a Behat, aunque dada la madurez de este último no le vemos la misma
utilidad para integrarlo en un proyecto que acabe con código en producción.

Capítulo 8. BDD 74
Testeo de código en desarrollos PHP.

Capítulo 9. Codeception: Solución todo en uno


Codeception: Solución todo en uno
9.1 Introducción a Codeception
Codeception [27] es una herramienta que cubre los tres tipos de test, unitarios, funcionales y de
aceptación. Está desarrollada sobre PHPUnit, por lo que mantiene todas las funcionalidades y
compatibilidades con él. Al estar desarrollado sobre PHPUnit, Codeception añade algunos
procedimientos a problemas comunes en los distintos tipos de test unitarios.

Codeception está compuesto por Actors. Los Actors son clases autogeneradas de tres posibles tipos:
UnitTester, FunctionalTester y AcceptanceTester. Cada una de estas clases tiene métodos predefinidos
que intentan resolver problemas comunes de testeo. Estas clases pueden ser configuradas mediante
ficheros de configuración en formato Yaml. Entre algunos de los problemas que resuelven está el de
rellenado de una base de datos tras la ejecución de cada test, el uso del patrón PageObject [47] para
test de aceptación y funcionales, etc.

La parte fuerte de Codeception es que es un framework pensado para cubrir los distintos tipos de test
dentro de la misma herramienta. Para ello, y a diferencia de otros frameworks, la propia herramienta
crea la estructura de carpetas donde alojar los distintos tipos de tests ejecutado el commando:

vendor/bin/codecept bootstrap

La estructura de carpetas tendría la forma forma:

tests/acceptance/
tests/functional/
tests/unit/

Dentro de las distintas herramientas para testear Codeception es la que de forma más explícita aborda
el problema de distintos tipos de testeo de forma conjunta. Si pensamos en el resto de herramientas
que hay en el mercado, PHPUnit está enfocada a realizar test unitario, aunque se puedan realizar test
de cualquier tipo utilizando librerías complementarias. Con Phpspec [23] podemos realizar test
unitarios en formato BDD, pero no podemos realizar tests de integración o aceptación. Behat [24] está
enfocado principalmente a tests BDD de aceptación. Atoum [7], al igual que PHPUnit, está también
orientado para realizar tests unitarios.

9.2 Particularidades de Codeception


Codeception permite compartir implementación entre los distintos tests de integración y
funcionales. Behat carece de esta funcionalidad, la cual es útil en situaciones como por ejemplo,
al testear una web y necesitamos realizar el proceso de login en cada ejecución. Este
mecanismo está implementado mediante los objetos StepObjects propios de Codeception.
Además, también permite la posibilidad de compartir una sesión entre distintos escenarios, por
lo que podríamos incluso ahorrarnos algunas tareas como la del proceso de logado. Esta
posibilidad, dependiendo de las funcionalidades a testear, podría llevar a falsos positivos, dado
que algunas implementaciones dependen del valor de la cookie.

Implementación del patrón PageObject, el cual nos permite asociar a atributos de un objeto

Capítulo 9. Codeception: Solución todo en uno 75


Testeo de código en desarrollos PHP.

definiciones del documento HTML mediante XPath para ser usados en los tests. Comparando
con Behat, esto debía ser indicado en la especificación Gherkins o implementando en la clase
FeatureContext, después de interpretar el fichero Gherkins.

Codeception permite la definición de escenarios dentro de los tests unitarios. Esta funcionalidad
no está implementada en PHPUnit y mejora la legibilidad de los tests, al permitir agrupar
aserciones dentro de una misma especificación a cumplimentar.

Codecepcion soporta posee módulos específicos para realizar test funcionales en los
frameworks más comunes (Symfony2, Laravel 4 y 5, Zend 1.x y 2, Yii1, Yii2 y Phalcon). Esto
nos permite crear test funcionales solo especificandole en la configuración con qué framework
estamos trabajando y Codeception se encargará de utilizar las dependencias propias del
framework para ejecutar los controladores a testear.

Codeception contiene funciones predefinidas para resolver problemas genéricos de testeo,


mientras que Behat ofrece mecanismos para implementar estas soluciones, pero vienen pocas
predefinidas como por ejemplo, mecanismos de rellenado de base de datos.

9.3 Carencias comparados con otras herramientas


Como herramienta para TDD, Phpspec es una herramienta más potente, dado que el desarrollo
está mejor guiado por la propia herramienta.

Codeception no soporta la especificación de test en formato Gherkins.

Codeception no tiene WebDriver para Sahi.

9.4 Instalación
Aunque existen otros métodos de instalación y uso de Codeception, recomendamos el mismo método
que hemos utilizado a lo largo de este PFC a través de Composer. Para ello debemos incluir la
dependencia de Codeception en nuestro fichero composer.json de la forma:

{
"require": {
"codeception/codeception": "^2.1"
}
}

9.5 Ejemplo con Codeception


Para documentar la expresividad y el modo de uso de Codeception vamos a repetir el ejercicio que
realizamos en el capítulo 8 y testear algunas funcionalidades de un blog en WordPress. Por lo que en
este ejercicio solo veremos el caso de uso de tests de aceptación.

En esta sección reutilizaremos el contenedor de Docker para WordPress del capítulo 8.

Para crear nuestros tests usando Codeception crearemos una carpeta CodeceptionTesting, incluiremos
el fichero composer.json con su dependencia y ejecutaremos el comando:

vendor/bin/codecept bootstrap

Este comando nos creará todas las configuraciones por defecto para poder realizar el test deseado.

Capítulo 9. Codeception: Solución todo en uno 76


Testeo de código en desarrollos PHP.

Dado que vamos a realizar sólo tests de aceptación con Selenium, editaremos el fichero
tests/acceptance.suite.yml y cambiaremos PhpBrowser, que viene por defecto, por WebDriver, he
indicaremos la url donde responderá nuestro WordPress:

class_name: AcceptanceTester
modules:
enabled:
- WebDriver:
url: http://localhost:8080/
- \Helper\Acceptance

Dado que vamos a testear tres escenarios, vamos a crear las tres clases para ello utilizando el
comando generate:cetp de la forma:

vendor/bin/codecept generate:cept acceptance WordpressLogin

vendor/bin/codecept generate:cept acceptance WordpressAddPost

vendor/bin/codecept generate:cept acceptance WordpressDeletePost

La ejecución de estos tres commandos no habrán creado los ficheros:

tests/acceptance/WordpressAddPostCept.php
tests/acceptance/WordpressDeletePostCept.php
tests/acceptance/WordpressLoginCept.php

Para implementar el primer escenario editaremos el fichero WordpressLoginCept.php y pondremos el


siguiente código:

$I = new AcceptanceTester($scenario);
$I->wantTo("Login to my blog");
$I->amOnPage("/wp-login.php");

$I->fillField("#user_login", "jose");
$I->fillField("#user_pass", "testing");
$I->click("wp-submit");
$I->see("Dashboard");

Dado que por convenio Codeception recomienda utilizar la variable "I" para crear un escenario
concreto, la lectura del código es muy intuitiva, cercana al lenguaje natural, salvando el hecho de que
sigue siendo código PHP. De hecho, si ejecutáramos el comando "vendor/bin/codecept
generate:scenarios acceptance" podríamos ver una descripción en inglés simple de nuestros tests de
aceptación.

Considerando que hemos arrancado nuestra configuración Docker de WordPress y tenemos en


ejecución Selenium, si lanzamos los tests con el comando:

vendor/bin/codecept run

Veremos el siguiente resultado:

Codeception PHP Testing Framework v2.1.3


Powered by PHPUnit 4.8.13 by Sebastian Bergmann and contributors.

Capítulo 9. Codeception: Solución todo en uno 77


Testeo de código en desarrollos PHP.

Acceptance Tests (3) ---------------------------------------------------------


Perform actions and see result (WordpressAddPostCept)
Perform actions and see result (WordpressDeletePostCept)
Login to my blog (WordpressLoginCept)

Unit Tests (0) ------------------------------

Functional Tests (0) ------------------------

Time: 13.99 seconds, Memory: 13.25Mb

Por el momento solo tenemos implementación para el proceso de login y como podemos ver ha
pasado correctamente. También podemos ver la configuración de los otros conjunto de tests y como
no hemos creado ningún tests, nos lo indica en las secciones correspondientes.

Para testear el escenario en el que un usuario escribe un post en el blog, vamos a realizar una pequeña
refactorización y trasladaremos la lógica del proceso de login a un StepObject. Para ello creamos una
de estas clases ejecutando el comando:

vendor/bin/codecept generate:stepobject acceptance Login

Esta ejecución creará el fichero ./tests/_support/Step/Acceptance/Login.php, el cual editaremos y


podremos la lógica del test de login, quedando el código de la siguiente forma:

namespace Step\Acceptance;

class Login extends \AcceptanceTester


{

public function login($user, $password)


{
$I = $this;
$I->wantTo('Login to my blog');
$I->amOnPage('/wp-login.php');

$I->fillField('#user_login', $user);
$I->fillField('#user_pass', $password);
$I->click('wp-submit');
}
}

Ahora podemos refactorizar nuestro test de aceptación para el proceso login, utilizar la clase que
hemos creado como StepObject. El código para esta refactorización sería:

use Step\Acceptance\Login;

$I = new Login($scenario);
$I->login('jose', 'testing');
$I->see('Dashboard');

Con esto, ya podemos reutilizar el proceso de login en los demás tests.

Capítulo 9. Codeception: Solución todo en uno 78


Testeo de código en desarrollos PHP.

El código necesario para testear la inserción de un nuevo post sería:

use Step\Acceptance\Login;

$I = new Login($scenario);
$I->login('jose', 'testing');

$I->wantTo('Add a post in my blog');


$I->amOnPage('/wp-admin/post-new.php');
$I->fillField("post_title", "Post de ejemplo");
$I->waitForElement('#content', 30);
$I->fillField(["name" => "content"], "Contenido de un post de ejemplo"
$I->click('publish');

$I->see("Post published");

Y de la misma forma, el código para testear la eliminación del post recién insertado sería:

use Step\Acceptance\Login;

$I = new Login($scenario);
$I->login('jose', 'testing');

$I->wantTo('Delete just added post');


$I->amOnPage('/wp-admin/edit.php?post_type=post');
$I->checkOption(['name'=>'post[]']);
$I->selectOption("action", "Move to Trash");
$I->click("#doaction2");
$I->see("1 post moved to the Trash");

$I->amOnPage('/wp-admin/edit.php?post_status=trash&post_type=post');
$I->checkOption(['name'=>'post[]']);
$I->selectOption("action", "Delete Permanently");
$I->click("#doaction2");
$I->see("1 post permanently deleted");

Como podemos ver, la sintaxis del código para testear tests de aceptación utilizando Codeception es
muy legible, incluso para una persona no dedicada a la programación, pero no es tan intuitivo como la
forma de escribir este mismo tipo de test con el conjunto Behat y Gherkins. Por otra parte, la
modularidad del código que nos permite Codeception, con posibilidades como las que nos ofrece el
proceso implementado por la clase Login es una ventaja considerable. Ambas herramientas tienen
ventajas e inconvenientes a ser tomados en cuenta para elegirlos, aunque no son incompatibles entre
sí.

Capítulo 9. Codeception: Solución todo en uno 79


Testeo de código en desarrollos PHP.

Capítulo 10. Testeando APIs


Testeando API
10.1 Definición de API
A lo largo de los últimos años, una de las formas de comunicarse distintos proyectos ha sido mediante
un API (Interfaz de programa de aplicación, del inglés Application Program Interface).

En el mundo web, una API no es más que una forma de permitir que un entorno distinto de la
aplicación pueda acceder a los datos a través de un lenguaje común. Este tipo de comunicación
normalmente de desarrolla sobre el protocolo HTTP, dado que es el más extendido en internet.

Hay varias tecnologías para permitir dicha comunicación, como por ejemplo SOAP (en inglés, Simple
Object Access Protocol) o REST (en inglés, Representational State Transfer). Dado que el tipo de API
más extendido, tanto para aplicaciones móviles como para entornos web, es REST, este capítulo lo
centraremos en este tipo.

Algunos ejemplos claros de estos servicios pueden ser pasarelas de pagos como PayPal, almacenaje
de ficheros como Amazon S3, o integración de datos de servicios de analítica como Google Analytics.
Dado que es muy común tener este tipo de integraciones, es conveniente tener estas piezas de código
bajo test.

10.2 Formas de testear un API


Testear una API pública se puede afrontar de varias formas, dependiendo de las necesidades del
proyecto. Una aproximación puede ser mediante tests dobles. Para ello crearíamos objetos mocks que
simulen el comportamiento del API y ejercitaríamos nuestro código frente a una simulación.

Otra forma de realizar estos test es ejercitando API de forma completa. Estos tests son de integración
o funcionales y no se pueden considerar como tests unitarios, dado que no podríamos controlar el
estado de la aplicación y tienen varias complejidades. Es necesario que la máquina que ejecuta los
tests tenga conexión a internet, para conectarse con el servicio sobre el se realizarán los tests.

A continuación veremos un ejemplo de una implementación de una clase que pueda conectarse a la
API pública de un proyecto y sus correspondientes tests, tanto funcionales como unitarios.

10.3 Testeando la API de Flickr.com


Uno de los servicios de gestión de fotografías más extendidos en internet es Flickr.com. Supongamos
que queremos realizar una página web que permite al usuario importar sus fotografías de Flickr.com.
Para realizar esta página web necesitamos poder acceder a la API pública de Flickr.com. La
documentación oficial de la API de Flickr.com [28] nos muestra todas las posibles interaccionares que
podemos realizar con este servicio online.

En nuestro ejemplo vamos a implementar una clase llamada "Flickr" que sea capaz de conectarse con
Flickr.com y nos permita interactuar.

Para poder utilizar la API de Flickr.com es necesario tener una cuenta de usuario y registrar una

Capítulo 10. Testeando APIs 80


Testeo de código en desarrollos PHP.

aplicación para conseguir las claves de acceso. En el caso de Flickr.com, y generalmente a cualquier
API que permita autenticarse a través del protocolo Oauth [29], necesitaremos una clave de usuario y
una clave secreta.

El nuestra implementación de la API de Flickr.com hemos utilizado tres librerías de código abierto:

Guzzle [30], como cliente HTTP.

Config [31], para gestionar las configuraciones de nuestra aplicación.

oauth1-client [32], como cliente del protocolo Oauth 1.0, usado Flickr.com para permitir que
aplicaciones de terceros accedan a los datos de usuarios.

Con estas tres librerías solucionamos los distintos problemas que nos encontramos y nos dejan con
una sencilla clase para poder gestionar los métodos básicos de la API de Flickr.com. El código de
nuestra clase de Flickr.com sería el siguiente:

namespace Flickr;

use Noodlehaus\Config;
use Guzzle\Service\Client as GuzzleClient;

class Flickr
{
protected $client;

protected $config;

private $tokenCredentials;

const URL_FLICKR = 'https://api.flickr.com/services/rest/?method='

public function __construct($configFile, $client = null, $server = null)


{
if(!is_null($client)) {
$this->client = $client;
} else {
$this->client = new GuzzleClient();
}

$this->config = new Config($configFile);


if(!is_null($server)) {
$this->server = $server;
} else {
$this->server = new \Flickr\Oauth1\Flickr(array(
'identifier' => $this->config->get('flickrKey'),
'secret' => $this->config->get('secretFlickr'),
'callback_uri' => $this->config->get('callbackUriFlickr'
));
}
}

public function setTokenCredentials($token) {

Capítulo 10. Testeando APIs 81


Testeo de código en desarrollos PHP.

$this->tokenCredentials = $token;
}

public function callMethod($method, $params, $isWithOauth = false)


{
$url = $this->buildQuery($method, $params, $isWithOauth);
if ($isWithOauth) {
$headers = $this->server->getHeaders($this->tokenCredentials,
"POST",
$url,
$params);
$response = $this->client->post($url, $headers)->send();
} else {
$response = $this->client->get($url)->send();
}

$result = json_decode($response->getBody());

return $result;
}

public function getTemporalCredentialsToken() {

$temporaryCredentials = $this->server->getTemporaryCredentials();

return $temporaryCredentials;
}

public function getTokenCredentials($tempCredential, $oauthToken,

$this->tokenCredentials = $this->server
->getTokenCredentials($tempCredential

return $this->tokenCredentials;;
}

protected function buildQuery($method, $params, $isWithOauth)


{
$defaultOptions = array(
'format' => 'json',
'nojsoncallback' => 1,
);

if ($isWithOauth && empty($this->tokenCredentials)) {


throw new \Exception("Not authenticated");
}

if($isWithOauth) {
$ouathParams = ['auth_token'=> $this->tokenCredentials->getIdentif
'api_sig' => $this->tokenCredentials->getSecret()]

Capítulo 10. Testeando APIs 82


Testeo de código en desarrollos PHP.

$options = array_merge($params, $defaultOptions);

$optionsUrl = http_build_query($options);

return self::URL_FLICKR.
$method.
'&api_key='.
$this->config->get('flickrKey').
'&'.$optionsUrl;
}
}

Además, necesitamos un fichero de configuración donde tengamos guardadas las distintas claves
necesarias para acceder a Flickr.com, tal y como lo especificamos en la propia clase Flickr y es leído
por la librería hassankhan/config. El contenido de este fichero de configuración debería deter la
forma:

flickrKey: CHANGE_WITH_YOUR_KEY
secretFlickr: CHANGE_WITH_YOUR_SECRET
callbackUriFlickr: http://dev.local

Donde, en caso de utilizar esta librería para conectarse con una cuenta de aplicación de Flickr.com,
deberíamos de sustituir CHANGE_WITH_YOUR_KEY, CHANGE_WITH_YOUR_SECRET y
CHANGE_WITH_YOUR_SECRET por los valores correspondientes.

Esta pequeña clase no implementa todos los métodos de Flickr.com, dado que hay algunos métodos
especiales como la carga de fotos que necesitan una acción especial en Flickr.com, tal y como lo
indican en la documentación oficial https://up.flickr.com/services/upload/.

Hay dos métodos que merezca la pena comentar. El método __construct, que inicializa las
dependencias en el caso de que no vengan como parámetros. Esta es una buena práctica si queremos
que nuestra clase pueda ser gestionada por un inyector de dependencias y pueda ser fácilmente
testeable, como veremos a continuación.

El siguiente método a comentar es callMethod. Este método es el que realza la petición HTTP a
Flickr.com*, utilizando el método de llamada. Los distintos métodos que soporta Flickr son muy
variados y permiten tanto acceder como modificar distintos elementos en la cuenta de usuario.

Dado que nuestro método es genérico, vamos a realizar dos tests distintos: Vamos a testear los
métodos de la API Flickr.com flickr.photos.search y flickr.galleries.create. Realizaremos dos veces el
mismo test. Un test realizando la ejecución de la API y otra vez mockeando todas las dependencias y
utilizando un fixture, como si tuviéramos el resultado de la ejecución de la API. De esta forma
daremos visibilidad a las dos posibles aproximaciones que podemos encontrar cuando testeamos un
API pública de un servicio online.

10.3.1 Test sin autentificación

La implementación del test funcional para una búsqueda en Flickr.com sería:

/**
* Test a query of Flickr without mocks.
*/

Capítulo 10. Testeando APIs 83


Testeo de código en desarrollos PHP.

public function testQueryDirect()


{
$flickr = new Flickr('config.yml');
$photoCats = $flickr->callMethod('flickr.photos.search', array('text'

$this->assertEquals(100, count($photoCats->photos->photo));
}

Este test inicializa nuestra implementación del cliente de la API de Flickr.com y realiza una búsqueda
con el texto "cats". Como esta búsqueda es muy popular, verificamos que tenemos 100 fotos, que es
el número de fotos que devuelve Flickr.com por defecto.

Este test tiene varios problemas y ventajas: Si la máquina en la que se ejecuta no puede conectarse a
Flickr.com por algún motivo, el test fallará. Además, al realizar una conexión HTTP y una búsqueda
en los sistemas de Flickr.com, será lento en ejecución. Si quisiéramos verificar el contenido de alguna
foto, el test fallaría con el paso del tiempo, dado que los resultados para una búsqueda en Flickr.com
cambiarán según los usuarios vayan añadiendo más fotos. Por otra parte, este test está ejecutando
completamente nuestra implementación y verificando que, tanto nuestro código como Flickr.com se
comportan de la forma esperada.

Veamos ahora el mismo test pero implementado con mocks.

/**
* Test a query of Flickr with mocks.
*/
public function testQueryWithMocks()
{
$client = $this->prophesize(Client::class);
$response = $this->prophesize(Response::class);
$response->getBody()->willReturn($this->getFixtureContent());

$request = $this->prophesize(RequestInterface::class);
$request->send()->willReturn($response->reveal());

$client->get(Argument::any())->willReturn($request->reveal());

$server = $this->prophesize(FlickrServer::class);
$flickr = new Flickr('config.yml', $client->reveal(), $server->reveal());

$photoCats = $flickr->callMethod('flickr.photos.search', array('text'

$this->assertEquals(100, count($photoCats->photos->photo));
}

private function getFixtureContent()


{
return file_get_contents('./tests/fixtures/cats.json');
}

Este test es el mismo que el anterior, pero con grandes diferencias conceptuales. Dado que estamos
inyectando mocks en el constructor del cliente HTTP Guzzle, el test nunca llega a conectarse a
Flickr.com. Por ello, es un test más rápido en ejecución. Para que funcione el tests hemos necesitado
guardar una búsqueda, almacenada en el fichero "./tests/fixtures/cats.json" de nuestro proyecto. Como

Capítulo 10. Testeando APIs 84


Testeo de código en desarrollos PHP.

contrapartida, con este test no llegamos a ver claramente que nuestra implementación está dando el
resultado esperado, dado que no estamos ejercitando las dependencias, de la misma forma que un
cambio en Flickr.com pasaría sin ser detectado por nuestros tests.

10.3.2 Test con autentificación

Los dos ejemplos anteriores ejecutaban un método por el que no es necesario autentificarse. Ahora
veamos un método en el que es necesario estar a autentificado y los distintos problemas que esto
conlleva.

/**
* Test create Galery without mocks.
*/
public function testCreateGallery()
{
$token = new TokenCredentials();
$token->setIdentifier('IDENTIFIER');
$token->setSecret('SECRET');
$this->flickr->setTokenCredentials($token);
$gallery = ['title' => 'Galería de ejemplo', 'description' => 'Galería de
$res = $this->flickr->callMethod('flickr.galleries.create', $gallery
$this->assertEquals('ok', $res->stat);
}

Al igual que el test testQueryDirect, este test se conecta con Flickr.com y realiza la acción de crear
una galería. Por ello podemos decir que tiene los mismos problemas que el test testQueryDirect, pero
en realidad tiene dos problemas más. Como podemos ver, en el test hemos tenido que crear un objeto
TokenCredentials, asignarle los valores Identifier y Secret a través de los setter correspondientes he
inyectar este objeto a nuestro objeto Flickr.com. Esto debemos hacerlo por como funciona Oauth.

Los valores IDENTIFIER y SECRET debemos configurarlos realizando una conexión real a
Flickr.com, aceptar el uso de la API desde nuestra aplicación y obtener la respuesta. Este proceso es
tedioso y complejo, además de inestable, dado que si en algún momento indicamos que ya no damos
permiso a nuestra aplicación a conectarse a Flickr.com, el test dejará de funcionar.

Además, este test presenta otro problema. Cada vez que ejecutamos el test estamos creando una
galería nueva. Esto se puede comprobar en el perfil de usario asociado a las credenciales. Dado que
Flickr.com no soporta un método para el borrado de galerías y automatizar el borrado en el mismo
test, la cuenta del usuario quedará con varias galerías con el nombre indicado en el test.

Algunos proyectos ofrecen un interfaz de testeo, generalmente llamado Sandbox, en el cual un


desarrollador puede realizar cualquier tipo de acción como si fuera el sistema real, pero en un entorno
aislado y periódicamente reseteado. Flickr.com no ofrece este tipo de entorno para poder ejercitar su
API, por lo que cualquier acción que se ejecute a nivel de testeo contra los servicios reales quedará
reflejada en las cuentas de usuario asociadas.

Hay que destacar que Oauth1 es bastante complejo de testear, debido a la autorización por parte del
usuario al acceso de una tercera aplicación a los servicios que permite acceder. En testeo unitario esto
es prácticamente imposible, debido a que hay una interacción a tres bandas. En un test de aceptación
con herramientas como Behat con Selenium o alguna otra herramienta este tipo de test se podría
automatizar, pero de tal y como lo estamos resolviendo, es necesario obtener las credenciales
manualmente para poder ejecutar estos tests.

Capítulo 10. Testeando APIs 85


Testeo de código en desarrollos PHP.

Veamos una implementación utilizando mocks, y por lo tanto, exenta de estos dos últimos problemas:

/**
* Test create Galery with mocks.
*/
public function testCreateGalleryWithMocks()
{
$client = $this->prophesize(Client::class);
$response = $this->prophesize(Response::class);
$response->getBody()->willReturn($this->getFixtureContentForCreateGallery(

$request = $this->prophesize(RequestInterface::class);
$request->send()->willReturn($response->reveal());

$client->post(Argument::any(), Argument::any())->willReturn($request

$server = $this->prophesize(FlickrServer::class);
$server->getHeaders(Argument::any(),
Argument::any(),
Argument::any(),
Argument::any())->willReturn([]);

$flickr = new Flickr('config.yml', $client->reveal(), $server->reveal());


$token = $this->prophesize(TokenCredentials::class);
$flickr->setTokenCredentials($token->reveal());

$gallery = ['title' => 'Galería de ejemplo', 'description' => 'Galería de


$res = $flickr->callMethod('flickr.galleries.create', $gallery, true

$this->assertEquals("ok", $res->stat);
}

private function getFixtureContentForCreateGallery()


{
return file_get_contents('./tests/fixtures/create-gallery.json');
}

Este caso presenta un escenario muy similar al test testQueryWithMocks, pese a que el método que
estamos testeando necesita autenticación. Como todo está mockeado y la respuesta inyectada en otro
fixture el test no tiene efectos secundarios en ninguna cuenta de Flickr.com y no hemos necesitado
conectarnos y aceptar el uso de la aplicación previa a la creación al test.

Por otra parte, y como comentábamos con el test de la búsqueda, este tipo de test no realiza nada, por
lo cual no podemos garantizar que la acción en los servicios de Flickr.com sea válida.

Optar por una u otra forma de realizar test a servicios externos depende de la naturaleza del problema
y es decisión del equipo de desarrollo. En la mayor parte de los casos la mejor solución, por
simplicidad y tiempo de ejecución sea la de mockear cualquier sistema externo, pero hay situaciones
concretas y críticas, como sistemas de pagos, en las cuales la mejor solución es testear el sistema de
forma completa. Algunos de estos servicios online, como Paypal.com son conscientes de la necesidad
de crear tests validos y ofrecen entornos de pruebas como comentábamos anteriormente, para que los
desarrollos sobre esta plataforma estén implementando de la forma más estable y con mejores

Capítulo 10. Testeando APIs 86


Testeo de código en desarrollos PHP.

garantías posibles.

Capítulo 10. Testeando APIs 87


Testeo de código en desarrollos PHP.

Capítulo 11. Integración continua


Integración continua
11.1 Introducción a integración continua
Integración continua es la práctica, en ingeniería del software, de unir todo el trabajo de diferentes
desarrolladores en la versión final del proyecto. Esta práctica se ha hecho necesaria en prácticamente
todos los entornos de desarrollo y en PHP, aunque no haya una forma estándar, hay herramientas que
podemos utilizar para realizar este tipo de tareas.

Tanto en equipos con varios desarrolladores, como en proyectos de código abierto, los cambios a los
que está sujeto un proyecto son constantes y generalmente rápidos, por lo que introducir herramientas
de monitorización y mejora continua en la calidad del software ayudan a que los equipos mantengan
unos varemos de calidad aceptables. Esta práctica es ventajosa tanto para la comunidad que está
trabajando en el proyecto, como para los dueños del proyecto.

La integración continua reduce la cantidad de procesos repetitivos que los desarrolladores necesitan
realizar cuando construyen o lanzan el software en el que están trabajando, y por lo tanto, reducen el
riesgo o se adelantan en la detección de errores.

11.2 Proceso de integración continua


El proceso de integración continua es bastante sencillo, suponiendo que hemos escogido nuestras
herramientas. Los pasos a seguir son los siguientes:

Un desarrollador finaliza una tarea y el cambio es integrado con el resto del proyecto.

La herramienta de integración continua detecta los cambios y ejecuta el conjunto de acciones


por las que está configurado. Esto suele incluir tests unitarios, funcionales, de integración,
generación de documentación a través de las anotaciones del código, métricas de calidad de
código, compilación para lenguajes en los que fuera necesario, etc...

El sistema de integración continua notifica al/los desarrolladores en caso de que se produzca un


fallo.

Con este sistema, la detección de errores queda automatizada y los errores localizados nada más un
desarrollador haya terminado una tarea.

En el caso de que un proyecto tenga un conjunto de tests de integración grande, ejecutar los tests
después de cada commit puede no ser una solución viable, dado que los tests funcionales son lentos.
Para solventar esta situación, y otras situaciones especiales, las herramientas de integración continua
pueden ser configuradas con gran cantidad de detalles.

11.3 Alternativas de herramientas para integración continua


Entre las distintas alternativas de productos de integración continua vamos a agruparlas entre las que
necesitan ser instalados por nosotros y los productos online. Entre los primeros tenemos alternativas
como Jenkins, PHPCI, TeamCity, Buildbot, Travis CI, Go Continuous Delivery, etc. Entre los

Capítulo 11. Integración continua 88


Testeo de código en desarrollos PHP.

sistemas online también contamos con Travis CI y otras alternativas como continuousphp, Bamboo,
Codeship, etc.

En el mercado hay multitud de proyectos para solventar el problema de integración continua. Hemos
agrupado las distntas opciones en función de los ejemplos que mostraremos a continuación. Para tener
una visión más global y tener referencia más detallada podemos consultar el artículo de Wikipedia en
inglés "Comparison of continuous integration software" [33], donde podemos ver una tabla con
multitud de proyectos de integración continua clasificados por plataforma, licencia, si consta de
builder para windows, sistema de notificación, integración con IDEs y otro tipo de integraciones.

Para ilustrar la forma de configurar un proyecto hemos decidido utilizar dos alternativas: Una
instalación de Jenkins y la versión online de Travis CI para proyectos de código libre. Travis CI es
gratuito para proyectos publicados bajo licencias de código abierto, por lo que podremos utilizarlo sin
problemas.

11.4 Jenkins
Pese a que Jenkins [34] es un proyecto escrito en Java, la comunidad PHP lo ha aceptado y utilizado
como herramienta de integración continua, ejecutando tanto PHPUnit como otras herramientas para
monitorizar la calidad de código [45]. Algunas de estas herramientas son:

PHP_CodeSniffer: Herramienta para detectar violaciones en la estandarización del código


elegida por el proyecto bajo la integración continua.

PHP_Depend: Analizador de código que genera determinadas métricas para asegurar la calidad
de código, he indicar posibles refactorizaciones según dichas métricas.

PHP Mess Detector: Analizador de código que determina secciones de código muerto, posibles
bugs o expresiones complejas.

PHP Copy/Paste Detector: Herramienta que encuentra fragmentos de código duplicado en una
base de código.

Para ilustrar una integración, configuraremos el proyecto de Strings, mencionado en el capítulo 3.

11.4.1 Instalación de Jenkins en Linux

Lo primero que necesitamos es descargar el fichero binario ejecutable de Jenkins en la página oficial
y ejecutar el comando:

java -jar jenkins.jar

Para instalar y ejecutar Jenkins hay otras alternativas, como instalar un contenedor utilizando Docker
y utilizar la imagen correspondiente. Este proceso sería similar a la instalación que hicimos en el
capítulo 8 con la instalación de WordPress" utilizando Docker*.

Otra alternativa en máquinas con la distribución Linux Ubuntu instalada podemos utilizar los paquetes
oficiales siguiendo los comandos:

wget -q -O - https://jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key a


sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/
> /etc/apt/sources.list.d/jenkins.list'
sudo apt-get update

Capítulo 11. Integración continua 89


Testeo de código en desarrollos PHP.

sudo apt-get install jenkins

Para más información sobre la instalación recomendamos visitar la documentación oficial Jenkins
[34].

Una vez instalado Jenkins podemos acceder a la aplicación abriendo un navegador y accediendo a la
url http://localhost:8080, que es la configuración por defecto.

11.4.2 Configuración de plugins y otros paquetes

Dado que vamos a configurar nuestro proyecto XStrings utilizando GitHub [10], necesitamos tener
instalado Git en la máquina donde ejecutaremos Jenkins. Además, necesitamos tener instalado
Composer [11], dado que seguimos el estándar PSR-4 [2] para autolading, y las dependencias de
testing de nuestro proyecto.

Jenkins es perfectamente configurable para desarrollos PHP, puesto que la comunidad ha creado
multitud de plugins. En nuestro ejemplo haremos uso de algunos plugins. Para hacer uso tenemos que
visitar la sección de configuración de Jenkins de la ejecución anteriormente lanzada bajo la URL
http://localhost:8080, marcar los plugins deseados y pulsar el botón "Install without restart". Después
de que todos los plugins estén instalados necesitamos reiniciar Jenkins.

Para nuestro proyecto vamos a instalar los siguientes plugins:

Clover PHP plugin: Lo utilizaremos para integrar los reportes en formato Clover.

GIT plugin: Necesitaremos este plugin para acceder a Github, descargar el código y detectar
nuevas versiones.

11.4.3 Configuración de nuestro proyecto XStrings

Para configurar nuestra librería, lo primero que necesitamos es crear un proyecto en nuestro Jenkins.
Para ello daremos al link "New Item". A continuación rellenaremos los datos necesarios para integrar
nuestro proyecto:

Rellenaremos "Item name:" con XStrings.


Marcaremos la opción "Freestyle project".
Pulsamos el botón "Ok".

A continuación se nos presentará la página de configuración del proyecto.

Como indicamos anteriormente, nuestro proyecto integrará el código publicado en un repositorio


público de https://github.com/josgilmo/xstring, por lo que lo marcaremos en la sección "Source Code
Management" la opción "Git" y rellenaremos el campo "Repository URL" con la url .git de XStrings.
En el campo "Branches to build", rellenaremos con "./master", que es la rama que deseamos integrar.

Para nuestro proyecto, hemos decido realizar las siguientes acciones:

Ejecutar los tests unitarios.


Utilizar la generación del fichero clover.xml para revisar la convertura de tests unitarios.
Verificar que la estandarización de código es el deseado.

Estas acciones las configuraremos añadiendo nuevas secciones pulsando en "Add build step",
seleccionando "Execute shell" y rellenando cada formulario con el comando correspondiente.

Por último crearemos las acciones posteriores a la ejecución de nuestra integración, en nuestro caso

Capítulo 11. Integración continua 90


Testeo de código en desarrollos PHP.

hemos decidido:

Publicar resultados del análisis estático de código.


Publicar resultados de cobertura de código.
Envío de notificación por mail en caso de que falle el proceso de integración.

Para crear estos pasos de post-proceso pulsaremos en "Add post-build action" y seleccionaremos la
correspondiente del desplegable de acciones.

Con esta configuración, cada vez que realicemos una modificación sobre la rama master de nuestro
proyecto, Jenkins lanzará el proceso de integración y podremos ver los resultados.

11.4.5 Jenkins PHP

Jenkins PHP [46] es una web especializada en información para configurar Jenkins como sistema de
integración continua para proyectos en PHP. Está desarrollado por Sebastian Bergmann, creador de
PHPUnit. En esa web explican cuales son los plugins necesarios para integrar un proyecto en PHP así
como una plantilla de ejemplo basada en Apache Ant para configurar de una forma completa nuestro
servidor Jenkins.

11.5 Travis CI
Travis CI [35] es un proyecto de integración continua con varias versiones. Una versión de código
libre alojada en https://travis-ci.org, en la que podemos configurar cualquier proyecto de código libre
publicado en GitHub. Y otra versión de pago, alojada en http://travis-ci.com, en la que podemos
testear cualquier repositorio privado. En esta sección nos centraremos en la versión gratuita para
proyectos de código libre.

La principal ventaja por la que utilizar una herramienta como Travis CI es el ahorro que sopone la
instalación y configuración de un proyecto bajo integración continua. Además, como Travis CI trabaja
en un entorno basado en contenedores, crear configuraciones específicas relativamente complejas es
trivial, frente a la instalación de Jenkins. Por ejemplo, supongamos que el proyecto que queremos
poner bajo integración continua es un proyecto de código abierto y queremos que sea funcional en la
mayor parte de versiones de PHP posibles. Utilizando Jenkins necesitaríamos instalar las distintas
versiones de PHP, lo cual no es un trabajo simple, y crear el proceso de integración con cada una de
las ejecuciones. Para Travis CI, pese a que es igual de complejo, la herramienta lo proporciona de
forma transparente y sencilla.

Trabajar con Travis CI no son todo ventajas. Las dependencias o versiones con las que podemos
trabajar vienen dadas por la herramienta y no es posible escoger otra, que en determinados casos
podría ser necesaria. A fecha de hoy, la versión más antigua de PHP soportada por Travis CI es 5.3.8,
he integrar un proyecto con una versión anterior podría ser no posible o producir problemas.

Además, herramientas de análisis estático de código, reportes de cobertura de código, etc,.. no son
proporcionadas por Travis CI, aunque existen otros proyectos que pueden complementarlo, que dan
soluciones especializadas para cada parte de la integración continua.

11.5.1 Configuración y uso de Travis CI

Para crear un proyecto en Travis CI, necesitamos tener una cuenta en Github.com y crear una cuenta
en Travis CI asociada a la de GitHub a través del protocolo de autentificación Oauth [29] (visto en el
capítulo 10 "Testeando API".

Una vez aceptado el acceso a GitHub por parte de Travis CI, seremos redireccionados a una página de

Capítulo 11. Integración continua 91


Testeo de código en desarrollos PHP.

Travis CI en la que veremos nuestros repositorios de GitHub. Clickando en los distintos botones
On/Off habilitaremos la integración del proyecto deseado en Travis CI. Además, necesitamos
configurar los "Services Hooks" en nuestra configuración de cada proyecto, para que en GitHub
aparezcan las notificaciones de estado de la integración continua.

Para que se produzca la ejecución de Travis CI, necesitamos configurar el contenedor donde se
producirá la integración en la forma que necesita nuestro proyecto. Los contenedores de Travis CI
tienen con la mayor parte de los paquetes necesarios para ejecutar un proyecto de integración para
PHP. Al igual que hicimos con Jenkins, necesitamos instalar las dependencias y crear el autoloading a
través de Composer. Esto, y el resto de configuraciones lo especificaremos en el fichero .travis.yml,
que debe estar en la raiz de nuestro proyecto.

El código Yaml que especifica nuestra integración continua es:

language: php

php:
- 5.4
- 5.5
- 5.6
- 7.0

before_script:
- composer install --dev

script: vendor/bin/phpunit --configuration phpunit.xml

notifications:
email:
- example.mail@example.com

Con esta configuración estamos especificando lo siguiente:

Es un proyecto PHP. Travis CI soporta varios lenguajes de programación, por lo que es


necesario indicarle el lenguaje la configuración actual.

La integración será ejecutada para las versiones PHP5.4, PHP5.5, PHP5.6 y PHP.7. Como
comentábamos anteriormente, este proceso en Jenkins sería tedioso puesto que tendríamos que
realizar al instalación de cada una de las versiones en la máquina donde ejecutar el proceso de
integración.

Antes de ejecutar el script de integración instalaremos las dependencias de Composer para un


entorno de desarrollo.

El script ejecutará los tests con la configuración especificada en el fichero phpunit.xml

En caso de que el build falle, se enviará una notificación al correo indicado.

Como podemos ver, teniendo una cuenta de Travis CI y el proyecto a integrar alojado en GitHub,
configurar un entorno de integración continua consiste solamente en editar un pequeño fichero en
formato Yaml y situarlo en la raíz de nuestro proyecto. La comodidad que ofrece este tipo de servicios
frente a la alternativa instalada por nosotros es claramente visible, aunque la perdida de flexibilidad y
posibilidades que ofrecen configuraciones propias en Jenkins también son considerables.

Capítulo 11. Integración continua 92


Testeo de código en desarrollos PHP.

Capítulo 12. Test de proyectos existentes


Test de proyectos existentes
En este capítulo veremos como están desarrollados los tests de algunos proyectos existentes. Veremos
que frameworks de tests están utilizando, cobertura de test, tipo de integración continua si está
configurado en algún proyecto público como Travis CI [35].

Para hacer un análisis más profundo de la calidad del código de testeo de los proyectos que
escogeremos, utilizaremos una herramienta específica para mutación de tests: Humbug.

12.1 Humbug
Humbug [36] es un framework para mutación de tests, con el objetivo de medir la eficacia real del
conjunto de tests con el objetivo de mejorarlo.

Humbug realiza un análisis del código y, de forma dinámica, realiza pequeñas mutaciones con el
objetivo de que los tests no pasen. Esto indicará que el tests está bien pensado he implementado. Dado
que los tests están pensados para prevenir una regresión, imponer al sistema una regresión real que no
sea detectada por nuestros tests implica que algo no está tan bien como parecía.

Hay que tener en cuenta que Humbug está en modo experimental, por lo que el resultado del análisis
que haremos a continuación debe ser considerado como un experimento.

Para usar Humbug debemos instalarlo en el proyecto que deseemos testear. Esto puede realizarse
mediante la instalación de un fichero .phar en el sistema, o bien mediante Composer [11] de forma
global en el sistema:

composer global require 'humbug/humbug=~1.0@dev'

Para ejecutar Humbug necesitamos un fichero de configuración, llamado "humbug.json.dist" o


"humbug.json", en la raíz de nuestro proyecto con la configuración necesaria. Una configuración
básica y auto explicativa sería:

{
"timeout": 10,
"source": {
"directories": [
"src"
]
},
"logs": {
"text": "humbuglog.txt",
"json": "humbuglog.json"
}
}

Para ejecutar Humbug solo debemos lanzar el comando "humbug".

Los resultados de Humbug se deben interpretar de la siguiente forma:

Capítulo 12. Test de proyectos existentes 93


Testeo de código en desarrollos PHP.

Mutación asesinada (.): Una mutación ha causado que el test falle, lo cual es una salida positiva.

Mutación escapada (E): Una mutación no ha conseguido que el test falle, por lo cual la
consideramos como un error, dado que esperamos que un test falle para un cambio de código.

Mutación no cubierta (S): Una mutación que ocurre en una linea no estaba cubierta por ningún
test unitario. Dado que no tiene test, consideramos esta salida como errónea.

Fatal Error (F): Una mutación ha generado un fatal error. Estas situaciones pueden deberse a
errores del propio Humbug, o a una salida positiva en nuestro entendimiento, dado que la
mutación a provocado una inestabilidad en la aplicación.

Timeout (T): Esto se debe a que el test unitario se excede en tiempo de ejecución configurado
por Humbug. Lo consideramos como una salida positiva dado que un timeout debe ser un
comportamiento inapropiado, a veces provocado porque una mutación acaba creando un bucle
infinito.

Las métricas para Humbug deben ser consideradas:

Puntuación de indicación de mutaciones (Mutation Score Indicator) es 47%, esto significa que
el 47 de todas las mutaciones generadas fueron detectadas (por ejemplo, asesinadas, timeouts o
errores fatales). Si la covertura de tests fuera 65%, esto indica que tenemos un 18% de tests
inestables.

Covertura de código por mutaciones (Mutation Code Coverage) es de 67%. En teoría debería
de ser igual a la covertura de código generada por los tests, pero la covertura de tests ignora las
mutaciones.

Mutaciones:

En la página oficial de Humbug se puede ver todos los tipos de mutaciones que puede realizar. Para
ilustrar y ayudar a entender los cambios que realiza en el código añadimos la tabla 12.1 de mutaciones
de lógica binaria:

Original Mutado
true false
false true
&& \ \
\ \ &&
and or
or and
!

: Mutaciones binarias

12.2 Silex
Silex [37] es un micro-framework desarrollado en PHP utilizando componentes de Symfony2 y Pimple
como inyector de depencencias e inspirado en sinatra (framework desarrollado en Ruby).

Añadimos Silex en este apartado por entender que es una versión simplificada de los desarrollos que
está realizando el equipo de Symfony2.

Capítulo 12. Test de proyectos existentes 94


Testeo de código en desarrollos PHP.

12.2.1 Tests en Silex

Para comenzar a explorar en los tests de Silex lo primero que necesitamos es descargar el código e
instalar las dependencias utilizando Composer:

git clone https://github.com/silexphp/Silex.git


cd Silex
composer install

La versión sobre la que vamos a realizar el análisis es la 1.3.

Viendo la configuración de phpunit.xml.dist, tenemos una suite de tests alojada en el directorio


tests/Silex.

Si ejecutamos los tests unitarios con la opción para ver la cobertura de tests obtendremos el resultado:

Cobertura de tests: 86.19% (1279 / 1484) Funciones y métodos: 81.92% (145 / 177) Clases y Traits:
52.17% (24 / 46)

La cobertura de código en lineas es bastante alta. Se considera de forma general que un proyecto con
una cobertura de tests por encima del 70% está en una situación favorable.

Para ver si estos tests son lo eficientes que deberían ser, ejecutemos Humbug y veamos el resultado
que obtenemos:

Humbug version 1.0-dev

Humbug running test suite to generate logs and code coverage data...

255 [==========================================================] 19 secs

Humbug has completed the initial test run successfully.


Tests: 255 Line Coverage: 86.16%

Humbug is analysing source files...

Mutation Testing is commencing on 48 files...


(.: killed, M: escaped, S: uncovered, E: fatal error, T: timed out)

M.M...MM....E...MMMM..MMMM..M..S.E...SM..M.......MM.E....M.M | 60 (12/48)
.S......MMMM..........EEE.SSE......E.M.........M............ | 120 (15/48)
....EM....MM...EE.......E.MEE...MMMMMMMS.................... | 180 (16/48)
.....M..ME.MMME...ESS..M..S..S..SS...SSSSSSSSSSSSSS.EEE..... | 240 (32/48)
..E.................M.........M....ESSMMM..............S..M. | 300 (38/48)
..E.E.M...M...SSS..................EM.MMM.M...E

347 mutations were generated:


234 mutants were killed
32 mutants were not covered by tests
55 covered mutants were not detected
26 fatal errors were encountered
0 time outs were encountered

Metrics:

Capítulo 12. Test de proyectos existentes 95


Testeo de código en desarrollos PHP.

Mutation Score Indicator (MSI): 75%


Mutation Code Coverage: 91%
Covered Code MSI: 83%

Remember that some mutants will inevitably be harmless (i.e. false positives).

Time: 2.39 minutes Memory: 17.00MB


Humbug results are being logged as TEXT to: humbuglog.txt

Viendo el resultado de Humbug, tenemos un 75% de MSI, mientras que la cobertura era del 86%, por
lo cual tenemos un 11% de los tests que no han respondido correctamente a una mutación. Teniendo
en cuenta que según la propia documentación de Humbug, es posible encontrar falsos positivos,
parece un resultado bastante coherente.

Silex utiliza las herramientas ofrecidas por PHPUnit para mockear, aunque hay relativamente pocos
mocks. De las 59 clases de tests, 20 utilizan algún tipo de mock.

12.2.2 Test de integración de Silex

Silex está integrado utilizando la herramienta online Travis CI. Esto podemos saberlo mirando el
fichero de configuración .travis.yml, situado en la raiz del proyecto.

El contenido de dicho fichero es el siguiente:

language: php

sudo: false

cache:
directories:
- $HOME/.composer/cache

before_script:
# symfony/*
- sh -c "if [ '$TWIG_VERSION' != '2.0' ]; then sed -i 's/~1.8|~2.0/~1.8/g'
composer update; fi"
- sh -c "if [ '$SYMFONY_DEPS_VERSION' = '3.0' ];
then sed -i 's/~2\.3|3\.0\.\*/3.0.*@dev/g' composer.json; composer upd
- sh -c "if [ '$SYMFONY_DEPS_VERSION' = '2.8' ];
then sed -i 's/~2\.3|3\.0\.\*/2.8.*@dev/g' composer.json; composer upd
- sh -c "if [ '$SYMFONY_DEPS_VERSION' = '' ];
then sed -i 's/~2\.3|3\.0\.\*/2.7.*@dev/g' composer.json; composer upd
- sh -c "if [ '$SYMFONY_DEPS_VERSION' = '2.3' ];
then sed -i 's/~2\.3|3\.0\.\*/2.3.*@dev/g' composer.json; composer upd
- composer install

script: phpunit

matrix:
include:
- php: 5.3
- php: 5.4
- php: 5.5
- php: 5.6

Capítulo 12. Test de proyectos existentes 96


Testeo de código en desarrollos PHP.

env: TWIG_VERSION=2.0
- php: 5.6
env: SYMFONY_DEPS_VERSION=2.3
- php: 5.6
env: SYMFONY_DEPS_VERSION=2.8
- php: 5.6
env: SYMFONY_DEPS_VERSION=3
- php: 7.0
- php: hhvm

Viendo el fichero de configuración podemos apreciar que Silex funcionará para las versiones de PHP
desde la 5.3 hasta la 7.0. Además, además, el script de instalación realiza modificaciones sobre el
fichero de composer.json" para adaptarlo a las versiones de los distintos módulos de Symfony2*
instalados.

12.3 Slim
Slim [38] es un micro-framework escrito en PHP, que según el propio proyecto, ayuda a desarrollar
rápidamente aplicaciones web y API.

Desarrollado por Josh Lockhart, autor del libro Modern PHP [5] de la popular página web "PHP The
Right Way".

La simplicidad the Slim como framework se puede ver con el clásico "hello world" que aparece como
ejemplo al comienzo de la documentación:

$app = new \Slim\Slim();


$app->get('/hello/:name', function ($name) {
echo "Hello, $name";
});
$app->run();

12.3.1 Test en Slim

Para comenzar a explorar los tests de Slim necesitamos descargarnos el código fuente e instalar las
dependencias:

git clone https://github.com/slimphp/Slim.git


cd Slim
composer install

La versión de Slim sobre la que estamos estudiando los tests es la 2.6.2, aunque ya hay una versión
beta de Slim 3.

Tras la ejecución de los tests unitarios, la cobertura de Slim sería la siguiente:

Cobertura de tests: 94.31% (1276 / 1353)


Funciones y métodos: 88.29% (264 / 299)
Clases y Traits: 71.43% (15 / 21)

La cobertura de tests de *Slim" es del 94% del código.

Como hicimos en el caso de Silex, para evaluar la calidad de los tests unitarios de Slim ejecutaremos

Capítulo 12. Test de proyectos existentes 97


Testeo de código en desarrollos PHP.

Humbug. El resultado obtenido por la ejecución es:

Humbug version 1.0-dev

Humbug running test suite to generate logs and code coverage data...

395 [==========================================================] 11 secs

Humbug has completed the initial test run successfully.


Tests: 395 Line Coverage: 94.31%

Humbug is analysing source files...

Mutation Testing is commencing on 21 files...


(.: killed, M: escaped, S: uncovered, E: fatal error, T: timed out)

.............MMM.......E............E..M.................M.. | 60 ( 3/21)
.M..MM.............MSM...MMMM...........MM....M.M.MM....M.M. | 120 ( 5/21)
M.M........MMM.M..........M.MS..M.M..M...M.M..M.........M..M | 180 ( 6/21)
........M...........MM....................MMM....MMM........ | 240 ( 7/21)
.......M.............M.......M..M....................M...MMM | 300 (13/21)
..M.M.MSSSSS...M...MM.TEEEEEEEE.EE...M...MMS........M....... | 360 (16/21)
...E........SSS.M..M...M.M...M.M..SSS.....MME....E.........M | 420 (19/21)
MMSM.......

431 mutations were generated:


323 mutants were killed
15 mutants were not covered by tests
77 covered mutants were not detected
15 fatal errors were encountered
1 time outs were encountered

Metrics:
Mutation Score Indicator (MSI): 79%
Mutation Code Coverage: 97%
Covered Code MSI: 81%

Remember that some mutants will inevitably be harmless (i.e. false positives).

Time: 3.13 minutes Memory: 18.75MB


Humbug results are being logged as TEXT to: humbuglog.txt

El valor de MSI de Slim es de 79% frente al 94% de la cobertura de tests unitarios, esto es un 15% de
diferencia. Esto nos indica que hay un conjunto de tests mayor que en Silex (proporcionalmente), que
no están testeando de la mejor forma el código, debido a que el test ha pasado después de que se han
realizado mutaciones en el código.

12.3.3 Test de integración de Slim

Al igual que Silex, Slim está integrado utilizando Travis CI. El contenido del fichero de configuración
.travis.yml para este proyecto es:

language: php

Capítulo 12. Test de proyectos existentes 98


Testeo de código en desarrollos PHP.

php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm

script: phpunit --coverage-text

Como podemos ver, la configuración es muy sencilla, teniendo en cuenta solo las versiones de PHP
para las que Slim es compatible (desde la 5.3 hasta la 7.0, incluyendo hhvm) y la generación de la
cobertura en formato texto.

Capítulo 12. Test de proyectos existentes 99


Testeo de código en desarrollos PHP.

Capítulo 13. Más Tests


Más sobre tests
En esta sección estudiaremos distintas posibilidades no contemplados hasta el momento sobre testeo.
Por ejemplo, veremos como testear algunas de las últimas funcionalidades añadidas por el lenguaje
PHP como son los Generators y Traits. También veremos cómo testear algunas situaciones no
triviales de testear como son los métodos constructores privados, la ejecución de comandos,
controladores y funciones globales. Además plantearemos algunos debates que suelen surgir en los
distintos equipos de desarrollos como la cuestión si merece la pena testear los métodos setters y
getters, así como mencionaremos cómo extender PHPUnit para conseguir saber qué tests son lentos.

13.1 Testeando constructor privado


Una situación especial que nos podemos encontrar, y que realizar un test unitario no es sencillo, es
testear un constructor privado. Este tipo de constructores son utilizados en algunos patrones de
diseño, como pueden ser Singleton. En estas situaciones especiales podemos tener alguna lógica en el
constructor que merezca la pena testear, pero al ser un método privado, testearla desde un test unitario
directamente no es algo trivial. Para conseguir el test deseado debemos de utilizar reflexión mediante
la librería de PHP Reflexion.

La librería Reflexion contiene una completa interfaz de usuario por la que podemos realizar ingeniería
inversa a clases, interfaces, funciones, métodos y extensiones de nuestro propio código PHP.

En el siguiente ejemplo vamos a ver como crear un objeto con un constructor privado.

namespace Src;

/**
* Example class with private constructor.
*
*/
class ClassPrivateConstruct
{
/**
* Unique instance.
*
* @var \stdClass number of pings
*/
private static $instance = null;

/**
* Variable One.
*
* @var string
*/
protected $varOne = null;

/**

Capítulo 13. Más Tests 100


Testeo de código en desarrollos PHP.

* Variable Two.
*
* @var string
*/
protected $varTwo = null;

/**
* Private constructor.
*
* @param string $varOne Variable One.
* @param string $varTwo Variable Two.
*
*
* @return void
*/
private function __construct($varOne, $varTwo)
{
$this->varOne = $varOne;
$this->varTwo = $varTwo;
}

/**
* Public creation of instance.
*
* @param string $varOne Variable One.
* @param string $varTwo Variable Two.
*
* @return ClassPrivateConstruct
*/
public static function create($varOne, $varTwo)
{
if (!self::$instance) {
self::$instance = new ClassPrivateConstruct($varOne, $varTwo
}

return self::$instance;
}
}

A continuación, incluimos el código necesario para poder testear el fragmento de código anterior:

namespace Test;

use Src\ClassPrivateConstruct;

/**
* Testing for ClassPrivateConstruct Class
*
*/
class ClassPrivateConstructTest extends \PHPUnit_Framework_TestCase
{
/**

Capítulo 13. Más Tests 101


Testeo de código en desarrollos PHP.

* Test for private constructor.a


*
* @return void
*/
public function testConstruct()
{

$reflectionClass = new \ReflectionClass(ClassPrivateConstruct::class);


$meth = $reflectionClass->getMethod('__construct');
$this->assertTrue($meth->isPrivate());

$class = ClassPrivateConstruct::create("var1", "var2");


$propertyVarOne = $reflectionClass->getProperty('varOne');
$propertyVarTwo = $reflectionClass->getProperty('varTwo');

$propertyVarOne->setAccessible(true);
$propertyVarTwo->setAccessible(true);

$this->assertEquals("var1", $propertyVarOne->getValue($class));
$this->assertEquals("var2", $propertyVarTwo->getValue($class));

}
}

Como podemos ver, nuestra clase ClassPrivateConstruct lo único que hace es una posible
implementación de Singleton a la que le pasamos dos parámetros. En nuestro test, mediante la primera
aserción nos aseguramos de que el constructor es privado. En las dos siguientes aserciones, mediante
reflexión también, verificamos que los argumentos que le hemos pasado han sido correctamente
asignados en nuestra creación estática de nuestro objeto.

13.2 Testeando generadores


En la versión 5.5 de PHP se incluyó el concepto de Generator. Un Generator no es más que un
mecanismo para implementar de forma sencilla el patrón de diseño Iteraror, sin la sobrecarga de
trabajo de implementar la lógica de dicho patrón. La documentación oficial de PHP da más
información sobre esta nueva herramienta del lenguaje [39]. En el capítulo 2 de Modern PHP [5] se
explica con detalle los Generators con algunos ejemplos de uso.

Para ilustrar una forma de testear generadores hemos creado la clase GeneratorClass, la cual tiene un
método getCountOfWords, que dado un fichero de texto devuelve de forma ordenada el número de
veces ese fichero contiene cada palabra.

Su implementación es:

namespace src;

/**
* Example of class that use generator.
*/
class GeneratorClass
{
/**

Capítulo 13. Más Tests 102


Testeo de código en desarrollos PHP.

* Count of words.
*
* @var [string => int] Number of times that appear a word hashed by word.
*/
private $counts = [];

/**
* Get counts of words.
*
* @param string $file Filename.
*
* @return Generator
* @throws \Exception File not found.
*/
public function getCountOfWords($file)
{
$f = fopen($file, 'r');

if (!$f) {
throw new \Exception();
}
while ($line = fgets($f)) {
$parts = explode(' ', trim($line));
foreach ($parts as $word) {
if (!isset($this->counts[$word])) {
$this->counts[$word] = 1;
} else {
$this->counts[$word]++;
}
}
}

arsort($this->counts);
foreach ($this->counts as $word => $count) {
yield $word => $this->counts[$word];
}

fclose($f);
}
}

En nuestro test unitario, dado que queremos verificar que el uso de Generator, testeamos que el
método getCountOfWords pese a no tener ninguna sentencia return nos devuelve un objeto del tipo
Generator, que a su vez implementa el interfaz Iterator, como esperábamos. Además testeamos que el
primer valor devuelto por el conjunto de palabras asociado al número de veces es el esperado.

La implementación de nuestro test unitario sería:

namespace Test;

use Src\GeneratorClass;

Capítulo 13. Más Tests 103


Testeo de código en desarrollos PHP.

/**
* Testing for GeneratorClass
*/
class GeneratorClassTes extends \PHPUnit_Framework_TestCase {

/**
* Test for check a Generator
*
* @return void
*/
public function testGetLines() {
$generator = new GeneratorClass();
$dictionary = Array();
$counts = $generator->getCountOfWords("./tests/fixture/file.txt"

$this->assertEquals(4, $counts->current());
$this->assertEquals("word2", $counts->key());
$this->assertInstanceOf("Iterator", $counts);
$this->assertInstanceOf("Generator", $counts);
}
}

El contenido de nuestro fichero file.txt, el cual hace pasar este test es:

word1 word2 word3 word4


word2 word2 word2 word3
word3 word4 word3 word5

13.3 Testeando comandos


En muchos desarrollos es necesario crear comandos: Un comando es una ejecución de un script,
generalmente en línea de comandos (no en un entorno web) que realiza tareas que deben ser
realizadas en segundo plano. Normalmente un comando tiene un único método público para su
ejecución (llamados run, execute, process, ...) y es llamado desde el una interfaz definida para ello.
Los frameworks modernos de PHP como Symfony2 y Lavarel tienen distintos interfaces para
implementar y ejecutar comandos, aunque el concepto de base es muy similar.

Testear comandos tiene una dificultad especial: El método público de su ejecución es el encargado de
realizar un gran conjunto de tareas sin devolver una salida testeable. Todas estas tareas que realiza el
método de ejecución, en un contexto de testeo unitario, deberíamos de crear los test dobles para
asegurarnos de que la parte testeada es la propia de la clase a testear.

Se considera buena práctica tener un log de ejecución de comando, para poder depurar errores o
mantener un registro de acciones. Además, utilizar un log en la ejecución de un comando nos da la
posibilidad de solucicionar la testeabilidad de comandos, pues podemos considerar nuestro log como
la salida del comando y verificar un resultado esperado.

Para ilustrar este problema y solución hemos creado la clase CommandClass: Esta clase es un
commando, sin pertenecer a ningún framework, que realiza un envío de mails a una lista pasada como
parámetro. El constructor de nuestro comando necesita como parámetros una instancia del objeto
Logger, que será el que en nuestro test inyectaremos con un log especial, y una instancia de una clase
Emailer, que también será mockeada en nuestro ejemplo.

Capítulo 13. Más Tests 104


Testeo de código en desarrollos PHP.

El código de nuestra clase CommandClass sería el siguiente:

namespace src;

/**
* CommandClass source code.
* Simulate a email sender command.
*/
class CommandClass
{
/**
* Logger.
*
* @var $logger
*/
private $logger = null;

/**
* Emailer.
*
* @var $emailer
*/
private $emailer = null;

/**
* Construct.
*
* @param \stdClass $logger Logger.
* @param \stdClass $emailer Emailer.
*
*/
public function __construct(\stdClass $logger, \stdClass $emailer)
{
$this->logger = $logger;
$this->emailer = $emailer;
}

/**
* Messages.
*
* @param array $messages Array of messages hashed as
* ["email"=> string, "content"=> string].
*
* @return void
*/
public function execute(array $messages)
{
$this->logger->log("Start command");
foreach ($messages as $message) {
$this->logger->log("Validating email ".$message['email']);
if (filter_var($message['email'], FILTER_VALIDATE_EMAIL)) {
$this->logger->log("Email ".$message['email']. " is valid", Lo

Capítulo 13. Más Tests 105


Testeo de código en desarrollos PHP.

try {
$this->emailer->send($message['email'], $message['content'
$this->logger->log("Email ".$message['email']. " sended",
} catch (\Exception $e) {
$this->logger->log($e->getMessage(), Logger::ERROR);
}

} else {
$this->logger->log("Email ".$message['email']." is not valid",
}
}
$this->logger->log("End command");
}

/**
* Return $this->logger.
*
* @return \stdClass
*/
public function getLogger()
{
return $this->logger;
}
}

El test unitario de nuestra clase Command, como indicabamos anteriormente creará un objeto
CommandClass con una lista de emails de ejemplos y ejecutará nuestro método execute. Dado que
nuestro ejemplo es muy sencillo, no hemos creado mocks utilizando Prophecy como vimos en el
capítulo 6 (test dobles), sino que hemos creado dos clases en nuestro namespace de Test. Un Logger,
que deja en memoria los logs de forma que podamos recuperarlos para verificar su contenido, y un
Emailer, que siempre devuelve true como implementación del método send.

La implementación de nuestro test unitario sería:

namespace Test;

use Src\CommandClass;
use Src\Logger;
use Src\Emailer;

/**
* Test for CommandClass
*/
class CommandClassTest extends \PHPUnit_Framework_TestCase
{

/**
* Test for a command class. A simple mail sender command.
*
* @return void
*/
public function testCommandSend()

Capítulo 13. Más Tests 106


Testeo de código en desarrollos PHP.

{
$messages = [
["email" => "email@example.com", "content" => "Good example of mai
["email" => "emailexample.com", "content" => "Bad example of mail"
];

$command = new CommandClass(new Logger(), new Emailer());


$command->execute($messages);

$logger = $command->getLogger();
$logs = $logger->getLogs();
$this->assertEquals($logs[0]['message'], 'Start command');
$this->assertEquals($logs[1]['message'], 'Validating email email@examp
$this->assertEquals($logs[2]['message'], 'Email email@example.com is v
$this->assertEquals($logs[3]['message'], 'Email email@example.com send

$this->assertEquals($logs[4]['message'], 'Validating email emailexampl


$this->assertEquals($logs[5]['message'], 'Email emailexample.com is no
$this->assertEquals($logs[6]['message'], 'End command');

}
}

13.4 Testeando clases abstractas


Desde el punto de vista de testeo unitario, testear clases abstractas no tiene sentido excepto en los
métodos implementados en la clase abstracta. No es tan poco común que en el desarrollo de una
librería, tengamos la necesidad de crear clases abstractas con el objetivo de extenderlas en las
distintas capas de la aplicación. Para estar seguro de que estas implementaciones estén testeadas es
necesario crear test unitarios para estas clases abstractas.

Para realizar estos tests PHPUnit [1] permite la creación de mocks. El siguiente ejemplo es extraído
de la documentación de PHPUnit y nos sirve para ilustrar esta situación:

abstract class AbstractClass


{
public function concreteMethod()
{
return $this->abstractMethod();
}

public abstract function abstractMethod();


}

class AbstractClassTest extends PHPUnit_Framework_TestCase


{
public function testConcreteMethod()
{
$stub = $this->getMockForAbstractClass('AbstractClass');

$stub->expects($this->any())
->method('abstractMethod')

Capítulo 13. Más Tests 107


Testeo de código en desarrollos PHP.

->will($this->returnValue(TRUE));

$this->assertTrue($stub->concreteMethod());
}
}

En este ejemplo podemos ver como PHPUnit tiene el método especial getMockForAbstractClass,
dedicado a crear mocks para clases abstractas y poder crear test unitarios.

13.5 Testeando traits


Los traits fueron incluidos en la versión 5.4 de PHP. En el capítulo 2 del libro "Modern PHP" [5]
explican con detalle la forma de utilizar esta nueva funcionalidad de PHP.

De forma similar que PHPUnit soporta testeo para clases abstracta soporta testeo para traits, con la
necesidad de que debemos crear los mocks con el método getMockForTrait. El ejemplo siguiente,
también extraído de la documentación de PHPUnit nos enseña como testear este tipo de situaciones.

trait AbstractTrait
{
public function concreteMethod()
{
return $this->abstractMethod();
}

public abstract function abstractMethod();


}

class TraitClassTest extends PHPUnit_Framework_TestCase


{
public function testConcreteMethod()
{
$mock = $this->getMockForTrait('AbstractTrait');

$mock->expects($this->any())
->method('abstractMethod')
->will($this->returnValue(TRUE));

$this->assertTrue($mock->concreteMethod());
}
}

En el ejemplo creamos un mock de la clase AbstractClass, mockeamos el comportamiento del método


abstracto y verificamos el comportamiento del método concreto, tal y como deberíamos hacer para
verificar la funcionalidad de la clase abstracta.

13.6 Testeando controladores


En los frameworks PHP orientados a web, como pueden ser Symfony2, Zend Framework o Laravel
existen, aunque de distintas formas, los métodos controladores, que dentro de la arquitectura MVC
(patrón de diseño formado por el modelo, la vista y el controlador) son los responsables de enviar
comandos al modelo para actualizar el estado del modelo. También es el responsable de enviar

Capítulo 13. Más Tests 108


Testeo de código en desarrollos PHP.

información a la vista con el objeto de presentarle el resultados del envío de comandos al modelo en
la propia vista.

Al ser una parte tan importante del desarrollo web, testear estos controladores en cada framework
tiene una implementación particular. En este PFC no expondremos cada una de las formas de testear
un controlador, dado que cada framework tiene una documentación específica para poder ser testeado.

Para ver más detalles sobre la forma de testear este tipo de clases, recomendamos consultar las
siguiente lista:

Información de como testear en Laravel 5.1 [40]. Este documento no está centrado solo a cómo
testear controladores, aunque en la sección Application Testing queda reflejada como
extendiendo la clase TestCase de Laravel se pueden testear este tipo de clases.

Información de como testear en Symfony2 [41]. En esta página se detalla como testear en
Symfony2. En el apartado "Functional Tests" detallan como se pueden crear test que
extendiendo de la clase propia de Symfony2, WebTestCase, se pueden testear controladores.

Información de como testear en Zend2 [42]. En el apartado Your first controller tests explican
como testear controladores en Zend2.

Además, como vimos en el capítulo 9, Codeception soporta test funcionales para estos frameworks y
otros como Yii2 y Phalcon, como test funcionales.

13.7 Testeando funciones globales


Cuando nos encontramos con funciones globales podemos estar en situaciones en la que la
testeabilidad sea complicada. Algunas de estas situaciones las encontramos con funciones con
dependencias externas. Por ejemplo funciones como file_get_contents o time. Frente a estas
situaciones tenemos dos alternativas:

Mockearlas

Imaginemos que tenemos un trozo de código donde llamamos la función nativa file_get_contents, que
lo que hace es obtener el contenido de un fichero o url. Para poder testear esta función podemos
encapsularla dentro de otra de la siguiente forma:

class SomeClass

public function fetch($file)


{
return file_get_contents($file);
}

public function getContents($file) {


return "Content for $file:".$this->fetch($file);
}

Testear este método podría realizarse de la siguiente forma:

Capítulo 13. Más Tests 109


Testeo de código en desarrollos PHP.

class SomeClassTest extends PHPUnit_Framework_TestCase {

public function testGetContents()


{
$mock = Mockery::mock('SomeClass')->makePartial();
$mock->shouldReceive('fetch')
->once()
->with('foo')
->andReturn('bar');
$sentence = $mock->getContents('foo');

$this->assertEquals('Content for foo: bar', $sentence);


}
}

Alojarlas en un namespace

Otra solución sería utilizar namespaces. Agrupando una colección de funciones dentro de un
namespace nos ayuda a mockear cuando testeemos código que dependa de estas funciones. Un
ejemplo de esta situación sería el siguiente:

namespace App\Helpers;

function showTime() {
return time();
}

De esta forma estamos redefiniendo la función time para el namespaces App, pudiendo ser utilizada
en un tests unitario de la siguiente forma:

namespace App\Helpers;

function time() { return 'foo'; }

class FunctionsTest extends PHPUnit_Framework_TestCase {


public function testShowTime()
{
$this->assertEquals('foo', showTime());
}
}

Como vemos, hemos añadido una definición de la función time en nuestro código del test, lo que hará
que por defecto, cuando la llamada de la función showTime es ejecutada busca una función time
dentro del mismo namespace y si la encuentra la ejecuta. Realmente no es una forma de mockear,
pero es una forma de alterar el comportamiento de funciones de PHP para mejorar la testeabilidad de
nuestro código.

13.8 Testeando getters y setters


Un debate relativo al testeo de un proyecto es el de testear los getters y setters de las distintas clases,
lo cual divide a la comunidad de desarrolladores. En la sección Frequently Asked Questions del libro

Capítulo 13. Más Tests 110


Testeo de código en desarrollos PHP.

Lavarel testing decode [4] también es comentado.

Personalmente creo que tener una cobertura de tests de 90% o 100% que no aportan valor es algo que
carece de importancia. Si pensamos que en algunos objetos los getters y setters pueden ser generados,
como por ejemplo en las entidades generadas por el ORM Doctrine2, se podría incluso auto generar
los tests unitarios de estos getters y setters. Sin embargo, para este tipo de métodos que tengan algún
tipo de lógica implementada tiene sentido que esta lógica quede descrita mediante uno o varios tests.

13.9 Extendiendo PHPUnit


PHPUnit puede ser extendido de varias formas. La más común es, como hemos visto en este capítulo,
crear una clase que extienda de PHPUnit_Framework_TestCase y contenga nuevos métodos o
sobreescriba otros según necesidades específicas de la situación en la que nos encontremos. Esto lo
hemos visto en las formas de testear de Symfony2 y su clase WebTestCase, y en Laravel, y su clase
TestCase.

Además de este mecanismo, y como podemos ver en la documentación de PHPUnit [1] es sencillo
extender las distintas partes del framework. Por ejemplo, podemos extender PHPUnit para mostrar
qué test ha sido lento, como hace el proyecto phpunit-speedtrap [43]. Este proyecto crea Listeners que
deben ser configurados en el fichero "phpunit.xml" o "phpunit.xml.dist" con el tiempo que se
considera a ser remarcado, y el número de tests lentos que se quieren presentar.

Este plugin se basa en una clase que implementa el interfaz PHPUnit_Framework_TestListener de


PHPUnit y crea los métodos necesarios para registrar el tiempo de ejecución de cada tests.

Capítulo 13. Más Tests 111


Testeo de código en desarrollos PHP.

Conclusiones
Conclusiones
En este PFC hemos intentado dar visibilidad a las distintas herramientas para la creación de tests
automáticos y las distintas metodologías que podemos realizar. Para ello, hemos intentado dar
respuesta a los planteamientos que planteábamos en el anteproyecto, basándonos en cinco objetivos:

Estudiar como testar una clase en PHP. Para dar respuesta a este apartado hemos creado una
clase que mejoraba el tipo de datos string de PHP y hemos creado test unitarios en dos
frameworks de testeo, PHPUnit y Atoum. En la memoria de este PFC hemos expuesto algunos
ejemplos de los tests, intentando dar visibilidad a las diferencias estas dos herramientas, aunque
demostrando que ambas son válidas para el objetivo de este punto.

Testo de bases de datos. En el capítulo 5 hemos explicado los distintos puntos que debemos
considerar cuando planteamos realizar tests a un desarrollo con dependencias con bases de
datos. Hemos planteado un ejemplo en el que el código testeado realizaba consultas a bases de
datos, mostrando hincapié en la complejidad que este tipo de test de integración (hay que
destacar que no sse pueden considerar test unitarios) debido a la dificultad para crear un entorno
bajo control, similar a producción.

En el capítulo 6 hemos trabajado en dar respuesta al objetivo del uso de mocks, creando una
extensión de Monolog para enviar mensajes logs a Apache Solr. En el desarrollo de este
objetivo vimos que existía una gran cantidad de librerias para gestionar mocks por lo que
decidimos profundizar en este punto, con la misión de exponer la mejor opción para cada
situación. Respecto a todas estas librerías hemos llegado a una conclusión: Siempre que sea
posible, es preferible utilizar Prophecy frente a las otras alternativas. Llegamos a la conclusión
puesto que es la herramienta más estricta, lo cual nos exigirá implementar nuestro código
siguiendo buenas prácticas, frente a atajos que nos facilitan herramientas. Consideramos como
herramientas de mocks poco recomendables las que permiten utilizar mocks parciales, mocks de
clases no existentes, etc.

El objetivo de nuestro anteproyecto en el que pretendíamos documentar TDD ha sido satisfecho


en el capítulo 7. En este capítulo hemos explicado en qué consiste TDD, cuales son los pasos a
seguir y hemos realizado dos ejemplos. El primero, el juego para niños Fizzbuzz, ha sido
desarrollado utilizando PHPUnit, con el objetivo de presentar la metodología con una
herramienta de propósito general de testing. El segundo hemos implementado una pequeña
relación entre dos clases utilizando Phpspec, como herramienta de propósito específico para
TDD. En el capítulo 8 hemos explicado BDD utilizando un ejemplo con Behat.

En nuestro anteproyecto pusimos como objetivo testear con Selenium una aplicación que pueda
existir en producción. Este objetivo lo hemos cumplimentado en los capítulos 8 y 9, aunque no
hemos usado directamente Selenium, sino que hemos usado otras herramientas más modernas,
que a su vez, utilizar Selenium u otros simuladores de navegadores. En el capítulo 8 testamos
algunas funcionalidades básicas de WordPress utilizando Behat y en el capítulo 9 repetimos el
ejercicio con Codeception, con el objetivo de estudiar distintas alternativas. Debido a las
grandes diferencias que existen entre estas dos herramientas, no podemos concluir sobre el uso
de una frente a la otra. Ambas herramientas ofrecen ventajas e inconvenientes, no siendo
excluyentes. En nuestra opinión, algunas partes de un proyecto podrían estar testeadas
utilizando Behat y otras utilizando Codeception. De esta forma se podrían obtener las ventajas

Conclusiones 112
Testeo de código en desarrollos PHP.

de las dos herramientas.

En lo referente a integración continua, hemos realizado dos ejemplos utilizando dos


herramientas muy distintas: Jenkins y Travis CI. Podemos concluir, y como lo está haciendo la
comunidad PHP, que utilizar una herramienta como Travis CI puede ser mejor solución para
proyectos de código abierto alojados en sitios como GitHub. Así lo vimos en el capítulo 12
cuando repasamos los tests de Silex y Slim. Esta solución ofrece la posibilidad de ver el estado
de integración para cualquier usuario, he incluso nos da la oportunidad de ver el estado de la
integración si decidimos colaborar con un proyecto. Por otra parte, utilizar Jenkins en entornos
privados es recomendable, dado que la flexibilidad es total.

Tras la realización de este proyecto podemos concluir que trabajar con generación de test ofrece una
gran cantidad de ventajas frente a no hacerlo. Algunas de estas ventajas son cuantificables a través de
las herramientas de análisis estático de código. Podemos llegar a esta conclusión debido a algunas
prácticas que hay que mantener cuando escribimos código testeable:

Composición frente a herencia.


Código con disposición a inyección de dependencias.
Principio de simple responsabilidad.
Con el objetivo de facilitar la cobertura, mejor complejidad ciclomática.
Mayor legibilidad del código.
Uso de interfaces en el código de testing, por lo que ayuda a la comprensión del código.
...

Todos los puntos mencionados anteriormente pueden ser alcanzados sin testeo automático, pero no se
puede realizar testeo si el código no cumple estos puntos, por lo que por necesidad, un código con
testeo automático deberá ser mejor que uno sin testeo.

Las ventajas mencionadas anteriormente justifican solo mejoras a nivel de calidad de código, pero no
hay que olvidar que el testeo automático ofrece otras ventajas, como hemos visto a lo largo de este
PFC, como son regresión, menor índice de errores,

Herramientas utilizadas
Para realizar este PFC hemos realizado:

Vim, como editor de código PHP para los distintos ejemplos y Gedit como editor de textos para
la memoria, en formato Markdown

PHP 5.5.30 instalado en mi portátil para realizar los distintos ejemplos.

Futuras lineas de trabajo


Dado que el desarrollo de un PFC debe estar acotado en tiempo, hay algunas lineas de investigación
que, o bien no hemos abordado, o no hemos profundizado todo lo que podríamos haber llegado.
Algunos de los puntos que consideramos haber dejado de lado son:

En este documento hemos intentado incluir herramientas mantenidas por la comunidad y hemos
dejado de lado otras abandonadas como pueden ser SimpleTest, SnapTest, etc. Una revisión de
estas herramientas nos podría haber enseñado la forma en la que se trabaja anteriormente.

En las próximas fechas a la edición de este documento saldrá a la luz la versión PHP 7. En este
documento no hemos abordado los posibles cambios del lenguaje con respecto a la forma en la
que testeamos, cuando pensamos que algunos cambios de PHP 7 cambiarán la forma de testear.

Conclusiones 113
Testeo de código en desarrollos PHP.

Por ejemplo, uno de los cambios que promete PHP 7 y que merece la pena destacar aquí por el
impacto que tendrá en la forma que testeamos en PHP es el tipado estricto. Hasta el momento,
verificar en un test que la devolución de un método o función tenía un tipo concreto era
habitual, dado que PHP 5 y versiones anteriores es de tipado dinámico. A partir de PHP 7 el
tipo de datos de la devolución de una función puede ser definido en la función, por lo que no
será necesrario testearlo más. Esto puede que afecte a la forma en la que usamos las
herramientas de testing.

Con este cambio de versión, todos los frameworks que hemos analizado en este documento
deberían de evolucionar para adaptarse al lenguaje. Algunos de estas herramientas tienen
prevista una fecha por la lanzar la versión correspondiente a PHP 7, como PHPUnit, que en
diciembre de 2016 prevé tener la versión 6.0, que dará soporte únicamente a PHP 7.

Otra linea de investigación, aunque no estrictamente relacionada con el testing, pero sí con la
calidad de código sería la relacionada con las herramientas de análisis estático de código,
métricas de calidad de código, etc. Estas herramientas, al igual que la práctica de crear tests,
tiene un objetivo común con el testing: mejorar la calidad del software y minimizar los errores.
Produndizar en estas herramientas es valioso para cualquier lenguaje de programación.

En este PFC nos hemos centrado en un único lenguaje de programación: PHP. Sin embargo, hay
multitud de lenguajes. Este estudio podría realizarse a casi cualquier lenguaje de programación,
desde Java hasta Erlang. Estudiar como crear test automáticos para las herramientas que
usamos, desde el punto de vista de desarrollador de software, es una tarea tremendamente útil y
en algunos sectores del mundo laboral, imprescindible.

Además, no podemos sabemos qué ideas surgirán de la comunidad de desarrollo, o que práctica será
importada de otra comunidad. Estudiar y aprender sobre lenguajes de programación, o sobre partes
tan pequeñas como el testeo automático, puede ser extendido y profundizado extensamente.

Conclusiones 114
Testeo de código en desarrollos PHP.

Bibliografía
Bibliografía
[1] Sebastian Beerman, documentación de PHPUnit. Documento en formato HTML accesible por
internet en la dirección: http://phpunit.de.

[2] Varios autores, "PHP Framework Interop Group. Documento en formato HTML accesible por
internet en la dirección: http://www.php-fig.org.

[3] Zdenek Machek, "PHPUnit Essentials", Packt Publishing, 2014. ISBN: 978-1-78328-343-9.

[4] Jeffrey Way, "Lavarel testing decode", Leanpub, 2013.

[5] Josh Lockhart, "Modern PHP: New Features and Good Practices", O'Reilly, 2015. ISBN: 978-1-
491-90501-2.

[6] Varios autores, documentación de Prophecy. Documento en formato HTML accesible por internet
en la dirección: https://github.com/phpspec/prophecy.

[7] Varios autores, documentación de Atoum. Documento en formato HTML accesible por internet en
la dirección: https://github.com/atoum/atoum

[8] Michael Feathers, "Working Effectively with Legacy Code", Prentice Hall, 2004. ISBN-13: 007-
6092025986

[9] Varios autores. Artículo de Wikipedia (en inglés) "Software testing". Documento en formato
HTML accesible por internet en la dirección:
https://en.wikipedia.org/wiki/Software_testing#Testing_Types

[10] Página oficial de Github.com. Sitio web completo accesible por internet en la dirección:
https://github.com.

[11] Varios autores, documentación de Composer. Sitio web completo accesible por internet en la
dirección: https://getcomposer.org/doc/.

[12] Página oficial de Packagist. Sitio web completo accesible por internet en la dirección:
https://packagist.org

[13] Varios autores, documentación de Faker. Documentación en formato HTML accesible por
internet en la dirección: https://github.com/fzaninotto/Faker.

[14] Varios autores, documentación de Alice. Documentación en formato HTML accesible por
internet en la dirección: https://github.com/nelmio/alice.

[15] Varios autores, documentación de Samsui. Documentación en formato HTML accesible por
internet en la dirección: https://github.com/mauris/samsui.

[16] Documentación de Mockery. Documento en formato HTML accesible por internet en la


dirección: http://docs.mockery.io/en/latest/.

[17] Documentación de Phake. Documento en formato HTML accesible por internet en la dirección:

Bibliografía 115
Testeo de código en desarrollos PHP.

http://phake.readthedocs.org/en/.

[18] Documentación de AspectMock. Documento en formato HTML accesible por internet en la


dirección: https://github.com/Codeception/AspectMock.

[19] Martin Fowler, artículo "Mocks Aren't Stubs". Documento en formato HTML accesible por
internet en la dirección: http://martinfowler.com/articles/mocksArentStubs.html

[20] Konstantin Kudryashov, "Conceptual difference between Mockery and Prophecy". Documento
en formato HTML accesible por internet en la dirección:
http://everzet.com/post/72910908762/conceptual-difference-between-mockery-and-prophecy.

[21] Documentación de Monolog. Documentación en formato HTML accesible por internet en la


dirección: https://github.com/Seldaek/monolog.

[22] Documentación de vfsStream. Documentación en formato HTML accesible por internet en la


dirección: https://github.com/mikey179/vfsStream.

[23] Documentación de phpspec. Documentación en formato HTML accesible por internet en la


dirección: http://www.phpspec.net/en/latest/.

[24] Documentación de Behat. Documentación en formato HTML accesible por internet en la


dirección: http://docs.behat.org/en/v2.5/.

[25] Documentación de Pho. Documentación en formato HTML accesible por internet en la


dirección: https://github.com/danielstjules/pho.

[26] Documentación de Peridot. Documentación en formato HTML accesible por internet en la


dirección: http://peridot-php.github.io/.

[27] Página oficial de Codeception. Documentación en formato HTML accesible por internet en la
dirección: http://codeception.com/.

[28] API Documentation of Flickr.com. Documentación en formato HTML accesible por internet en la
dirección: https://www.flickr.com/services/api/.

[29] Documentación de Oauth 1.0. Documentación en formato HTML accesible por internet en la
dirección: http://oauth.net/core/1.0/.

[30] Documentación de Guzzle. Documentación en formato HTML accesible por internet en la


dirección: https://github.com/guzzle/guzzle.

[31] Documentación de Config. Documentación en formato HTML accesible por internet en la


dirección: http://github.com/hassankhan/config.

[32] Documentación de oauth1-client. Documentación en formato HTML accesible por internet en la


dirección: https://github.com/thephpleague/oauth1-client.

[33] Varios autores. Artículo de Wikipedia (en inglés) "Comparison of continuous integration
software". Documento en formato HTML accesible por internet en la dirección:
https://en.wikipedia.org/wiki/Comparison_of_continuous_integration_software

[34] Documentación de Jenkins. Documentación en formato HTML accesible por internet en la


dirección: https://jenkins-ci.org/.

[35] Página oficial de Travis CI. Sitio web completo accesible por internet en la dirección:

Bibliografía 116
Testeo de código en desarrollos PHP.

https://travis-ci.org/.

[36] Documentación de Humbug. Documentación en formato HTML accesible por internet en la


dirección: https://github.com/padraic/humbug.

[37] Documentación de Silex. Documentación en formato HTML accesible por internet en la


dirección: http://silex.sensiolabs.org/.

[38] Documentación de Slim. Documentación en formato HTML accesible por internet en la


dirección: http://www.slimframework.com/.

[39] Documentación sobre Generators. Documentación en formato HTML accesible por internet en la
dirección: http://php.net/manual/en/language.generators.overview.php.

[40] Documentación de testeo para Lavarel 5.1. Documentación en formato HTML accesible por
internet en la dirección: http://laravel.com/docs/5.1/testing.

[41] Documentación de testeo para la última versión de Symfony. Documentación en formato HTML
accesible por internet en la dirección: http://symfony.com/doc/current/book/testing.html.

[42] Documentación de testeo Zend2. Documentación en formato HTML accesible por internet en la
dirección: http://framework.zend.com/manual/2.0/en/user-guide/unit-testing.html.

[43] Documentación de phpunit-speedtrap. Documentación en formato HTML accesible por internet


en la dirección: https://github.com/johnkary/phpunit-speedtrap.

[44] Varios autores. Artículo de Wikipedia (en inglés) "Agile software development". Documento en
formato HTML accesible por internet en la dirección:
https://en.wikipedia.org/wiki/Agile_software_development

[45] Sitio oficial de calidad de código PHP, "The PHP Quality Assurance Toolchain". Documento en
formato HTML accesible por internet en la dirección: http://phpqatools.org/

[46] Sebastian Beerman, sitio web "Jenkins PHP". Documento en formato HTML accesible por
internet en la dirección: http://jenkins-php.org.

[47] Artículo sobre PageObject Pattern. Documento en formato HTML accesible por internet en la
dirección: https://code.google.com/p/selenium/wiki/PageObjects.

[48] Gerard Meszaros, "Xunit Test Patterns: Refactoring Test Code", Addison-Wesley Professional,
2007. ISBN-13: 007-6092037590.

Bibliografía 117

You might also like