You are on page 1of 88

Testing para Aplicaciones Symfony2

Fernando Arconada
Este libro est a la venta en http://leanpub.com/testingsymfony2
Esta versin se public en 2015-02-23

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
2013 - 2015 Fernando Arconada

Twitea sobre el libro!


Por favor ayuda a Fernando Arconada hablando sobre el libro en Twitter!
El hashtag sugerido para este libro es #testingsf2.
Descubre lo que otra gente est diciendo sobre el libro haciendo click en este enlace para buscar el
hashtag en Twitter:
https://twitter.com/search?q=#testingsf2

ndice general
Por qu testing? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Test unitario y test funcional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

TDD (Test Driven Development) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

BDD (Behavior Driven Development) . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


StoryBDD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
SpecBDD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

6
7
11

100% de Cobertura? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

13

Mocks, Dummies, Stubs y otros


Doubles . . . . . . . . . . . .
Dummy . . . . . . . . . . . .
Fakes . . . . . . . . . . . . . .
Stubs . . . . . . . . . . . . . .
Mocks . . . . . . . . . . . . .
Escuelas o estilos de Mocking
De qu no hacer Test Doubles
Libreras de Mocks . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

15
15
15
16
16
17
18
18
20

Calidad del software . . . . . . . . . . . . . . . . . .


Sntomas de software difcil de testear y mantener .
Principios SOLID . . . . . . . . . . . . . . . . . . .
Notas sobre inyeccin de dependencias en Symfony
Calidad mediante mtricas . . . . . . . . . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

28
28
37
43
50

DataFixutures . . . . . . . . . . . .
Entornos de ejecucin en Symfony
DoctrineFixturesBundle . . . . . .
Faker . . . . . . . . . . . . . . . .
Alice . . . . . . . . . . . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

57
57
58
62
64

Matthias Noback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

68

.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

NDICE GENERAL

La experiencia de testing con PHP: entrevista a Matthias Noback por Fernando Arconada .

68

Pawe Jdrzejewski lider del proyecto Sylius.org . . . . . . . . . . . . . . . . . . . . . .


Quien es Pawe? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Cual es tu experiencia con el cdigo PHP y su calidad? . . . . . . . . . . . . . . . . . .
Qu piensas sobre la calidad de los proyectos PHP en general? . . . . . . . . . . . . . .
Qu es Sylius? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Es Sylius una aplicacin como Magento, PrestaShop, etc o es un framework? . . . . . .
Cuntas personas hay colaborando en Sylius? . . . . . . . . . . . . . . . . . . . . . . .
Sueles hacer BDD, TDD o algo parecido? Qu significa esto en tu flujo de trabajo? . .
Sinceramente, empezaste haciendo testing desde el principio de Sylius? . . . . . . . . .
Cmo puedo ejecutar los tests de Sylius? . . . . . . . . . . . . . . . . . . . . . . . . . .
Tus colaboradores siguen el BDD? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
100% de cobertura de cdigo? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Puedes recomendar un libro, post o artculo relacionado con la calidad del cdigo que
haya cambiado tu forma de pensar? . . . . . . . . . . . . . . . . . . . . . . . . . .
Por qu PHPSpec y BeHat? Cuntanos lo mejor y lo peor de estas herramientas . . . . .
Por qu no Codeception o PHPUnit? . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Est muerto el TDD? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

74
74
74
74
75
75
75
75
76
76
76
77

.
.
.
.

77
77
78
78

De PHPSpec + Behat a Codeception . . .


Nivel humano . . . . . . . . . . . . . .
Comodidad del lenguaje . . . . . . . .
Documentacin . . . . . . . . . . . . .
Extensibilidad . . . . . . . . . . . . . .
No hago lo que sea driven development
Tests funcionales . . . . . . . . . . . .
Es Codeception la solucin definitiva?

.
.
.
.
.
.
.
.

79
79
80
81
81
81
82
82

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

Por qu testing?
La respuesta es sencilla: porque ya lo hacemos desde que empezamos a programar. Tecleas unas
lneas de cdigo, abres el navegador, rellenas un formulario con los datos que se te ocurren para
probar y lo envas a ver si has hecho todo bien.
El nico problema de todo esto es que los test los creas y se ejecutan de forma manual.
Si podemos hacer que los tests los ejecute un ordenador entonces podremos ejecutarlos de forma
continuada en un ciclo segn vayamos desarrollando la aplicacin. Esto nos dar un nivel de
confianza muy alto sobre el funcionamiento de lo que estamos desarrollando.
La regla bsica es escribir tests que demuestren que el cdigo funciona como se espera. Pero este no
hay ms motivos para escribir tests de software:
Verifican de forma automtica que las especificaciones que hemos definido se cumplen.
Mejoran la documentacin, la forma de escribir las especificaciones en herramientas como
Behat hace que la descripcin de la funcionalidad y los casos limite sean fcilmente entendibles
incluso para personas no tcnicas.
Hacen que trabajar en equipo sea mas sencillo y seguro, puesto que es fcil emplear y modificar
piezas de software de terceros sabiendo que todo sigue funcionando
Permiten evolucionar el software de forma segura. Por muy buena que sea la documentacin
de una aplicacin cuando pasa mucho tiempo nos vamos olvidando de ella y suele ser entonces
cuando nos toca hacer cambios. Cambios que hacemos cruzando los dedos y que an despus
de haber probado la pgina a la que creemos que afecta no tenemos la completa certeza de
que no se haya roto nada en otro sitio. Con los test automatizados de software evitamos en
gran parte este problema porque el ordenador va a probar por nosotros todas las partes de la
aplicacin que consideramos que debera ser probadas el da que los creamos.
Ahorran tiempo. Este punto resulta controvertido sobre todo en aplicaciones pequeas, en
aplicaciones que consideramos que no van a evolucionar. El problema es determinar una
aplicacin pequea y tener la certeza de que algo para a permanecer as para siempre. Para un
script de administracin de un servidor no escribo tests, quizs debera. Cuando parece que no
hay duda es cuando estamos delante de una aplicacin grande, de las llamadas vivas o muy
vivas, ciertamente en estos casos no solo ahorran tiempo si no que de no hacerlo acabaremos
con una bola de cdigo de esas que al cabo del tiempo dan miedo y que son el padre de la frase
si funciona no lo toques
Escribir tests de software tambin tiene inconvenientes, sobre todo si acabamos de empezar:
Se pierde tiempo aprendiendo cmo escribir un test.

Por qu testing?

Se pierde tiempo escribiendo el test.


Hay que esperar a que el test se ejecute.
Hay que perder tiempo manteniendo el test.
Se tiene una falsa sesancin de seguridad por el hecho de que los tests pasen.

Ciertamente escribir y mantener los tests cuesta tiempo, pero si la aplicacin merece la pena (no
todas las aplicaciones merecen este esfuerzo extra, por ejemplo porque va durar muy poco), al final
la balanza entre el tiempo perdido y el ahorrado siempre acaba de nuestro lado.
Con respecto a lo de la falsa sensacin de seguridad, se supone que con los tests garantizamos que
todo funcione no?. S, pero slo con los buenos tests, aquellos que verifican al completo los posibles
casos de una pieza de cdigo y tienen las aserciones correctas.

Test unitario y test funcional


Un test unitario se centra en probar una nica pieza de software y normalmente esta pieza se prueba
de forma aislada respecto a otras. Por ejemplo, nos centramos en probar un mtodo de una clase.
Segn Michel Feathers un test no es unitario si:

habla con la base de datos


utiliza la red
toca el sistema de ficheros
no se puede ejecutar en paralelo a otros tests
hay que hacer cosas especiales en el entorno (como cambiar ficheros de configuracin) para
ejecutarlo

Un test que hace alguna de estas cosas no es malo de por si, pero ser un test de integracin o
funcional.
En Symfony ya est habilitada por defecto la integracin con PHPUnit, pero tambin podemos
escribir tests unitarios con PHPSpec fcilmente.
Un test de integracin es en el que se prueba la interaccin entre dos o ms unidades. Los tests de
integracin prueban si dos componentes pueden trabajar juntos. Por ejemplo, el uso de una librera
de terceros y un mtodo nuestro.
Un test funcional es un tipo especial de test de integracin que ejecuta la aplicacin desde un punto
de vista del usuario. Por ejemplo, un usuario rellena una formulario, pulsa en envar y se genera un
email.
No creo que sea necesaria distinguir entre test funcionales y de integracin. Los test funcionales de
Symfony no los distinguen.
Un test funcional no es muy diferente de uno unitario, se distinguen en que siguen los siguientes
pasos:

haces un request
compruebas la respuesta
haces click en un link o en envas un formulario
compruebas la respuesta
repetir desde el inicio

Un ejemplo:
http://www.artima.com/weblogs/viewpost.jsp?thread=126923
http://symfony.com/doc/current/book/testing.html
http://symfony.com/doc/current/book/testing.html#functional-tests

Test unitario y test funcional

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<?php
// src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
namespace Acme\DemoBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class DemoControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/demo/hello/Fabien');
$this->assertGreaterThan(
0,
$crawler->filter('html:contains("Hello Fabien")')->count()
);
}
}

Los test de aceptacin no son muy diferentes de los test funcionales, la principal diferencia es
que los tests de aceptacin estn dirigidos a usuarios no tcnicos y se escriben un lenguaje fcil de
comprender.
Por ejemplo en Codeception:
1
2
3
4
5
6
7
8
9

<?php
$I = new WebGuy($scenario);
$I->wantTo('sign in');
$I->amOnPage('/login');
$I->fillField('signin[username]', 'davert');
$I->fillField('signin[password]','qwerty');
$I->click('LOGIN');
$I->see('Welcome, Davert!');
?>

Podemos escribir los test funcionales con BeHat o Codeception. O mejor, en mi opinin, podemos
prescindir de los test funcionales si tenemos buenos tests de aceptacin. Para que estos tests de
aceptacin sean buenos debemos incluir tests que se centren en los aspectos que nos interesen a los
desarrolladores y que a un usuario es posible que no le importen.
http://behat.org/
http://codeception.com/

TDD (Test Driven Development)


Desarrollo guiado por test (Test Driven Development) es una prctica de programacin que se basa
en desarrollar tests de cdigo previos al cdigo que resuelve la funcionalidad, escribir ese cdigo
para hacer que el test pase y refactorizarlo.

Ciclo TDD

En TDD se deben seguir las siguientes reglas:


No se permite escribir cdigo de produccin a menos que se haya hecho pasar su correspondiente test unitario.
No se permite escribir ms de un test unitario que el necesario para que falle.
No se permite escribir ms cdigo de produccin que el necesario para superar un test fallido.
La idea del TDD es crear ciclos rpido de micro-test y escribir cdigo para superarlos. No hay que
dedicarle una hora a escribir el test y luego centrarnos en el desarrollo, sino iteraciones muy cortas
de tet y cdigo.
Una de los puntos fuertes de TDD es el hecho de hacer varias iteraciones de refactorizacin una vez
que pasa el test hasta que te quedes completamente satisfecho con el diseo implementado.
Citando a Matt Wynne (uno de los autoresde Cucumber):
Hay multitud de proyectos que sufren de escasez de refactorizacin, pero no hay demasiados que
sufran de un exceso. Si no ests seguro de si has refactorizado suficiente entonces es probable que no
lo sea.
http://cukes.info/

BDD (Behavior Driven Development)


BDD fue originalmente desarrollado por Dan North como una tcnica para ayudar a aprender mejor
Test Driven Development centrndose en cmo TDD se convierte en una tcnica de diseo. Esto llev
a renombrar los tests como Behaviors (comportamientos) para explicar de una forma ms efectiva
cmo TDD debe centrarse en lo que deben hacer los objetos.
Un error comn es pensar que BDD es TDD aadiendo tests de aceptacin. Segn Dan North:
TDD -como se describi originalmente- trata tambin sobre el comportamiento del sistema al
completo. Ken Beck (uno de los padres de TDD), describe TDD operando en mltiples niveles de
abstraccin, no solamente en el cdigo.
BDD se centra en los buenos hbitos de TDD. Estos bueno hbitos son:
Trabajar de fuera (lo general) hacia dentro (la implementacin) partiendo de un objetivo desde
el punto de vista de negocio.
Usar ejemplos para clarificar los requerimientos.
Desarrollar empleando un lenguaje ubicuo
Si ests habituado al TDD puede parecer evidente trabajar de fuera hacia dentro, pero muchos
equipos se limitan a pequeas unidades de cdigo. Desde el punto de vista de negocio se suele
testear manualmente o si se hace de forma automtica, se suele hacer una vez que el cdigo ya
ha sido escrito. El principal inconveniente de escribir los tests a priori es que te obligas a entender
perfectamente lo que quieres conseguir. BDD incita a escribir los tests en forma de escenarios de
forma que se establece un puente de comunicacin entre los equipos de negocio y desarrollo.
Si revisamos el esquema de flujo de trabajo de un ciclo TDD o BDD , vemos que se divide en dos
partes:
http://www.infoq.com/articles/virtual-panel-bdd
http://martinfowler.com/bliki/UbiquitousLanguage.html

BDD (Behavior Driven Development)

Ciclo de TDD/BDD

Una parte del ciclo es funcional, y ah describimos desde un punto de vista de ms alto nivel
la caracterstica que queremos implementar. Hay otra parte en ciclo en la que bajamos a nivel de
cdigo, preferiblemente a nivel unitario de un mtodo y testeamos esas pocas lneas de software.
Seguir el ciclo de desarrollo de TDD/BDD no impone una herramienta, y de hecho, no tenemos
por qu elegir ninguna. Podramos desarrollar ejecutando todos los tests de forma manual aunque
nuestro inters est en ejecutar los test de forma automtica.

StoryBDD
Para especificar el comportamiento de alto nivel del software nos expresamos en un lenguaje de alto
nivel, lo ms cercano posible a la forma que nos expresamos de persona a persona. Intentaremos
expresarnos como si hablsemos con el cliente. Contaremos una historia en muy pocas palabras y
sin ambigedades.
A esta parte del ciclo de desarrollo TDD en el que describimos el comportamiento de alto nivel le
llamaremos StoryBDD.
El lenguaje empleado puede ser asi:

BDD (Behavior Driven Development)

1
2
3
4
5
6
7
8
9
10
11
12
13
14

Feature: Ser capaz de gestionar la entidad Task


Crear, borrar, modificar, listar todas las tareas
Scenario: La URL para gestionar tareas existe
When I go to "/tareas"
Then the response status code should be 200
And the "h2" element should contain "ToDo App"
Scenario: Hay una URL que devuelve una
When I go to "/tareas/lista"
Then the response status code should
And the header "Content-Type" should
And the response should be in JSON
And the JSON node "root" should have

lista de tareas en JSON


be 200
be equal to "application/json"
20 elements

En una primera parte se describe el comportamiento que queremos lograr (Feature) y posteriormente
se definen una serie de criterios de aceptacin (Scenario) en el que se expresan las precondiciones y
postcondiciones que se deben cumplir para dar por buena la funcionalidad.
Hay herramientas como Behat que permiten escribir test automticos en un lenguaje natural como
el del ejemplo anterior de forma que sea totalmente legible para el usuario y as poder convertir
estos test en test de aceptacin. Los tests de aceptacin sirven para verificar el cumplimiento de los
requisitos solicitados por el cliente.

Given/When/Then
El lenguaje con el que describimos los escenarios en Behat se llama Gherkin . Es el mismo
lenguaje que se emplea en Cucumber (una popular herramienta de BDD de Ruby). Como Gherkin
es parecido al lenguaje hablado, pero fijando ciertas estructuras ofrece una forma de que todas las
personas implicadas en el desarrollo (incluido el cliente), puedan describir los requerimientos con
una expresin similar. As es ms fcil que se comuniquen todos los participantes.
En Gherkin se expresan los escenarios en trminos de Given/When/Then que es la conexin con
las expresiones humanas de causa y efecto. En lenguaje de computadores sera el equivalente a
input/process/output.
Given
El objetivo de Given es poner el sistema en un estado conocido antes de que el usuario empiece a
interactuar con l. En este punto hay que tratar de evitar las interacciones de usuario.
Ejemplos:
http://behat.org/
https://github.com/cucumber/cucumber/wiki/Given-When-Then

BDD (Behavior Driven Development)

Crear los registros que deber contener la base de datos


Cosas que deben haber sucedido antes de llegar al escenario. Por ejemplo haber hecho login o
haber seguido una secuencia de pginas. Esto es una interaccin de usuario, pero al ser previa
al escenario est permitida.
When
El objetivo de When es describir la accin del escenario.
Ejemplos:
Interactuar con la pgina, rellenar un formulario.
Interactuar con la UI
Then
El propsito de Then es observar la salida que se ha producido. A ser posible estas observaciones
se deben centrar en aspectos que generen valor de negocio. Las observaciones tambin deben ser
alguna especie de salida, un informe, un mensaje, etc y no algo que est oculto en el sistema.
Ejemplos:
Alguna salida relativa a lo especificado en Given + When
Verificar que se ha enviado un email
Comprobar que aparece un mensaje en pantalla tras enviar un formulario
And, But
Si hay mltiples Given, When o Then se pueden escribir de esta forma:
1
2
3
4
5
6

Scenario: Hay una URL que devuelve una lista de tareas en JSON
When I go to "/tareas/lista"
Then the response status code should be 200
And the header "Content-Type" should be equal to "application/json"
And the response should be in JSON
And the JSON node "root" should have 20 elements

BDD (Behavior Driven Development)

10

Usar Gherkin o no usar Gherkin


Como hemos visto, podemos escribir los test en trmino de Given/When/Then (lenguaje Gherkin),
pero luego tenemos que crear los correspondientes tests en PHP que sern los que realmente ejecuten
ese lenguaje natural. En Behat hay muchos tests ya escritos que podemos reutilizar pero con toda
seguridad tendremos que escribir alguno.
Hay otro software para hacer BDD en PHP llamado Codeception. La gente de Codepction ha
deshechado la idea de escribir los escenarios de test en Gherkin.
Argumentan que en realidad los usuarios/clientes no llegan a expresarse con Gherkin y para los
desarrolladores resulta ms complejo puesto que es slo una capa de lenguaje natural que luego
tienen que volver a codificar con PHP.
Proponen especificar los escenarios de BDD con una sintaxis como esta:
1
2
3
4
5
6
7
8

<?php
$I = new WebGuy($scenario);
$I->wantTo('sign in');
$I->amOnPage('/login');
$I->fillField('signin[username]', 'davert');
$I->fillField('signin[password]','qwerty');
$I->click('LOGIN');
$I->see('Welcome, Davert!');

Esta sintaxis, que es directamente cdigo, resulta prcticamente igual de fcil de leer para los
desarrolladores y no tienen que cambiar su forma de expresarse. Adems, se puede utilizar el code
completion de los IDE de desarrollo.
Con un sencillo comando los escenarios escritos con Codeception se pueden convertir a lenguaje
natural para ser entregado a personal no tcnico.
1
2
3
4
5
6
7
8

$ php codecept.phar generate:scenarios


I
I
I
I
I
I

WANT TO SIGN IN
am on page '/login'
fill field ['signin[username]', 'davert']
fill field ['signin[password]', 'qwerty']
click 'LOGIN'
see 'Welcome, Davert!'
http://codeception.com/

BDD (Behavior Driven Development)

11

SpecBDD
Si StoryBDD se centra en la especificacin a nivel de negocio/funcional, SpecBDD se centra
en la especificacin a nivel de cdigo. Lo que comnmente conocemos como test unitario. Una
herramienta de StoryBDD debe dejar clara la narrativa, lo que pretendemos conseguir. Una
herramienta SpecBDD est enfocada a cmo conseguir esa funcionalidad: la implementacin.
La verdadera diferencia entre cualquier herramienta para ejecutar tests unitarios xUnit, por ejemplo
PHPUnit, y una herramienta de tipo xSpec, como PHPSpec es el lenguaje empleado.
Con un herramienta xUnit escribes una asercin de un test:
1

$this->assertEquals("<p>Hi, there</p>", $sut->toHtml("Hi, there"));

Con una herramienta tipo PHPSpec escribes:


1

$this->toHtml("Hi, there")->shouldReturn("<p>Hi, there</p>");

PHPSpec especifica ms claramente lo que debe hacer nuestra clase a testear, de forma que se
mantiene la ubicuidad del lenguaje al expresar el comportamiento como lo haramos al expresarnos
de forma natural.
Otra diferencia importante es que las herramientas tipo xSpec se centran ms en el comportamiento
que las herramientas xUnit, que suelen estar ms centradas en la estructura del cdigo.
Como se puede ver en el ejemplo de Konstantin Kudryashov
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<?php
class UserRatingCalculator
{
private $dispatcher;
public function __construct(EventDispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}
public function increaseUserRating(User $user, $add = 1)
{
$this->dispatcher->userRatingIncreasing($user->getRating());
$user->setRating($user->getRating() + $add);
$this->dispatcher->userRatingIncreased($user->getRating());
}
}
http://everzet.com/post/72910908762/conceptual-difference-between-mockery-and-prophecy

BDD (Behavior Driven Development)

12

Con una herramienta xUnit:


1
2
3
4
5
6
7
8
9
10
11

<?php
$user = Mockery::mock('User');
$user->shouldReceive('getRating')->andReturn(2, 2, 4);
$user->shouldReceive('setRating')->with(4)->once();
$disp = Mockery::mock('EventDispatcher');
$disp->shouldReceive('userRatingIncreasing')->with(2)->once();
$disp->shouldReceive('userRatingIncreased')->with(4)->once();
$calc = new UserRatingCalculator($disp->mock());
$calc->increaseUserRating($user->mock(), 2);

El test est vinculado a la estructura (al nmero de lnea) en que se llama getRating() puesto que el
mock devolver 2, 2 y 4 en ese orden.
Con PHPSpec y Phrophecy el test no depende del orden:
1
2
3
4
5
6
7
8
9
10
11
12
13

<?php
$user = $prophet->prophesize('User');
$user->getRating()->willReturn(2);
$user->setRating(Argument::type('integer'))->will(function($rating) {
$this->getRating()->willReturn($rating);
})->shouldBeCalled();
$disp = $prophet->prophesize('EventDispatcher');
$disp->userRatingIncreasing(2)->shouldBeCalled();
$disp->userRatingIncreased(4)->shouldBeCalled();
$calc = new UserRatingCalculator($disp->reveal());
$calc->increaseUserRating($user->reveal(), 2);

100% de Cobertura?
La cobertura de cdigo es una medida empleada para describir el porcentaje de lneas que han sido
ejecutadas por los tests.
La librera de Sebastian Bergmann, autor de PHPUnit, php-code-coverage provee de la funcionalidad necesaria para calcular las mtricas de cobertura y mostrarlas en forma de una web en la que
podamos explorar nuestro cdigo.

Ejemplo de web cobertura generada por php-code-coverage

Si seguimos estrictamente una prctica de desarrollo de TDD o BDD tendremos siempre una
cobertura del 100% del cdigo puesto que debemos escribir los test y luego el cdigo. Slo
escribiremos el cdigo justo para pasar los tests.
Que tengamos un 100% de cobertura no quiere decir que el cdigo est probado perfectamente. Eso
depende de la calidad de los test y de la correccin de las aserciones.
Un ejemplo de test con PHPSpec:

https://github.com/sebastianbergmann/php-code-coverage

100% de Cobertura?

1
2
3
4
5
6
7

14

<?php
// cobertura 100%
public function it_should_have_a_list_action(TaskRepository $taskRepo) {
$taskRepo->findAll()->shouldBeCalled();
$response = $this->listAction();
$response->shouldHaveType('Symfony\Component\HttpFoundation\Response');
}

Este test va a tener un 100% de cobertura puesto que el mtodo listAction() se ejecuta por todas
sus lneas.
1
2
3
4
5

<?php
// tambien cobertura 100%
public function it_should_have_a_list_action(TaskRepository $taskRepo) {
$response = $this->listAction();
}

Este otro test tambin obtiene una cobertura del 100% puesto que pasa por todas las lneas de
listAction(), pero no vale lo mismo que el test anterior. Lo que nos interesa es garantizar que
llamamos a findAll() dentro de nuestro cdigo y aqu no se comprueba.
En una metodologa TDD/BDD estricta no se debe dar el problema de que haya tests invlidos puesto que habremos de especificar primero en el test que findAll() debe ser llamado
$taskRepo->findAll()->shouldBeCalled(); y si no lo especificamos no debemos codificarlo en
nuestro cdigo de produccin.
Este libro trata sobre testing. Quizs se deje llevar hacia TDD BDD a hacia ninguno. Tu filosofa
de desarrollo la debes marcar t y sentirte cmodo con ella. No hay verdades absolutas.
He oido exigir al menos un 60% de cobertura para poder desplegar una aplicacin en produccin.
Esto qu aporta?. Eso quiere decir que es cdigo de ms calidad?. Ya hemos visto que en realidad
no tiene por qu ser as. Refactorizar varias veces es un paso fundamental que realmente aumenta
la calidad. La cobertura, si es con tests bien escritos, aumenta la seguridad.
Si se sigue con cuidado BDD y se dispone de experiencia, lograremos una cobertura de 100% con tests
precisos. Por otro lado esto implica estar muy entrenado en BDD y nos pagan por funcionalidades,
no por escribir tests.
Hay gente que prefiere escribir un 20% de los test antes de escribir el cdigo y el 80% o menos despus.
Por otro lado cuanto ms retrasemos el test ms retrasamos el feedback que obtenemos. Quizs no
sea apropiado perseguir el 100% de cobertura, pero deberamos tener unos buenos tests de aceptacin
que prueben bien la aplicacin al completo ya se hayan escrito antes o despus del cdigo.

Mocks, Dummies, Stubs y otros


En los tests normalmente nos centramos en el comportamiento de un objeto, de ah el nombre de test
unitario, pero en el mundo real un objeto necesita de otros para realizar su funcin. Cuando estamos
haciendo testing, rpidamente acabamos incorporando a nuestro vocabulario la palabra Mocks.
Cuando estamos haciendo un test unitario y la clase que queremos testear (el SUT -Subject Under
Test- ) tiene objetos colaboradores (objetos que usa para desarrollar su funcionalidad) debemos
sustituirlos por otros objetos que actan como los dobles de los actores en las pelculas para
asegurarnos el aislamiento del SUT. A estos dobles les llamamos comnmente Mocks, pero luego
acabamos escuchando palabras como Dummy y Stub y terminamos con dudas sobre lo que estamos
empleando. Voy a intentar aclarar la diferencia entre los diferentes tipos. Pero por qu reemplazar
los colaboradores por otros objetos falsos o dobles? - porque pueden ser costosos de crear - hacen
los tests lentos - pueden tener comportamientos que no controlamos (esto es lo mas importante) pueden generar otros objetos que interfieran con otros tests - necesitamos controlar el valor concreto
que devuelven para emplearlo en SUT

Doubles
Todos los colaboradores que reemplazamos por objetos de mentira que insertamos para controlar
el SUT son dobles o Doubles o Test Doubles. Un Test Double es el nombre que damos a los objetos
que reemplazan a los colaboradores. Sirve tanto para un Mock que para un Stub, as que si tenemos
duda en una conversacin y no queremos hacer el ridculo, empleando la expresin Test Double
acertaremos seguro. De los diferentes tipo de Test Doubles que vamos a ver a continuacin, slo los
Mocks verifican el comportamiento. El resto de Doubles slo verifican el estado.

Dummy
Un objeto de tipo dummy es un objeto tonto, un objeto que no vamos a usar para nada pero que
necesitamos por ejemplo para poder satisfacer las necesidades de un constructor y que luego no
vamos a utilizar.
Ejemplo de un Dummy con Prophecy para ser insertado en la clase Markdown y cumplir con los
requerimientos del constructor:

Mocks, Dummies, Stubs y otros

1
2
3

16

<?php
$eventDispatcher = $this->prophesize('MarkdownEventEventDispatcher');
$markdown = new Markdown($eventDispatcher->reveal());

Fakes
Un objeto double de tipo Fake aade un poco ms de funcionalidad a los Dummy. Si intentamos
llamar a un mtodo de un objeto Dummy tendremos un error. Hay veces que tenemos que poder
llamar a un mtodo de un Dummy simplemente para que nuestro test continue y no tener un
error. Para esto estn los Fake que son Dummies con mtodos, pero ojo, estos mtodos no hacen
ni devuelven nada.
Ejemplo de un objeto Fake que como he dicho es como un Dummy, pero con algo ms de funcionalidad, en este caso se va a declarar un mtodo para que cuando se llame internamente no de error.
El mtodo puede ser llamado con un argumento de tipo 'Markdown\Event\EndOfLineListener':
1
2
3
4
5

<?php
$eventDispatcher = $this->prophesize('Markdown\Event\EventDispatcher');
$eventDispatcher->addListener(Argument::type('Markdown\Event\EndOfLineListen\
er'));
$markdown = new Markdown($eventDispatcher->reveal());

Stubs
En los Stubs lo importante es lo que devuelve la llamada a sus mtodos, son una forma de garantizar
la salida de los objetos colaboradores. Por ejemplo cuando llamo a $em->find(1) devolver un
objeto entidad. La finalidad de los Stubs es reemplazar una funcionalidad concreta del colaborador
para garantizar el funcionamiento del colaborador.
Con Mockery se creara de la siguiente forma:
1
2
3

<?php
$miMock = m::mock('MiClase');
$miMock->shouldReceive('readTemp')->andReturn(11);

Con PHPUnit:

Mocks, Dummies, Stubs y otros

1
2
3
4

17

<?php
$miMock = $this->getMock('MiClase');
$miMock->expects($this->any())->method('readTemp')->will($this->returnValue(\
11));

Con PHPSpec y Prophecy


1
2
3

<?php
$miMock = $this->prophesize('MiClase');
$miMock->readTemp()->willReturn(11);

Mocks
Es un tipo de objeto Double sobre los que establecemos unas expectativas de uso y del que no nos
preocupa controlar lo que devuelve su llamada.
Por ejemplo: espero que el objeto colaborador $em->flush() sea llamado una sola vez dentro del
SUT, o que no sea llamado nunca, o al menos una vez, o que persist() sea llamada con un objeto
de tipo MiEntidad.
Con Mockery se creara de la siguiente forma:
1
2
3

<?php
$miMock = m::mock('MiClase');
$miMock->shouldReceive('readTemp')->times(3);

Con PHPUnit:
1
2
3

<?php
$miMock = $this->getMock('MiClase', array('readTemp', '', false);
$miMock->expects($this->exactly(3))

Con PHPSpec y Prophecy:


1
2
3

<?php
$miMock = $this->prophesize('MiClase');
$miMock->readTemp()->shouldBeCalledTimes(3);

Mocks, Dummies, Stubs y otros

18

Escuelas o estilos de Mocking


Cuando hablamos de reemplazar los colaboradores de la clase a testear he dado a entender que
hay que reemplazar todos los colaboradores por algn tipo de Test Double, pero hay dos corrientes
de Mocking: la escuela clsica (escuela de Chicago) y los Mockists o Mockistas (escuela de
Londres).
La corriente clsica de mocking establece que hay que usar colaboradores que sean objetos reales
siempre que sea posible y slo en los casos que no se pueda o la relacin con el colaborador sea
complicada usar Test Doubles. Por el contrario, los Mockistas dicen que cuando estamos escribiendo
un test todos los colaboradores deben ser algn tipo de Test Doubles.
Ahora bien, qu corriente seguir? cada uno debe sentirse cmodo y seguro con lo que hace as que
no se puede imponer un estilo. Es posible que a uno le ofrezca ms confianza emplear colaboradores
reales en vez de Test Doubles.
Cuando estamos escribiendo el test y nos encontramos que la clase del SUT tiene un colaborador
debemos tomar una decisin. Los Mockistas, crearn un Test Double. Los de la escuela clsica deben
decidir si el colaborador es suficientemente sencillo como para usar un objeto real o si es complicado
de emplear o tiene un comportamiento difcil de reproducir en cuyo caso emplearn un Test Double.
Si no estamos escribiendo tests para un cdigo heredado e intentamos seguir un flujo de trabajo de
TDD en el que los tests se escriben antes que el cdigo es muy posible que los objetos colaboradores
todava no existan y por lo tanto no nos quede ms remedio que emplear Test Doubles. La diferencia
es que los seguidores de la escuela clsica debern reemplazar estos dobles por los objetos reales
cuando los hayan creado. Para evitar este trabajo la gente de la escuela clsica suele centrarse primero
en crear los objetos de dominio.
En realidad, si seguimos la corriente clsica de testing no hacemos tests unitarios sino tests de
integracin en mayor o menor medida. Esto tiene la ventaja que podemos detectar algunos errores
que se nos pueden escapar si por ejemplo no hemos escrito adecuadamente las expectativas de los
colaboradores. De todas formas aunque en los tests unitarios de los Mockistas se nos pueden escapar
algunos posible errores se puede compensar fcilmente si junto a los tests unitarios ejecutamos tests
de aceptacin que atraviesan el sistema y no slo una clase.

De qu no hacer Test Doubles


En la lnea de la escuela Mockista o de Londres est la idea de hacer Tests Doubles de todos los
objetos colaboradores de nuestro SUT. Esto no es exactamente cierto siempre, o mejor dicho, para
todo tipo de objetos.
Los Value Objects son objetos simples que no se identifican por una propiedad de identidad $id sino
por el valor de los atributos. Los podemos considerar como si fuesen tipos primitivos del lenguaje
pero adaptado a nuestro dominio.
Un ejemplo clsico son las clases que representan colores o direcciones postales.

19

Mocks, Dummies, Stubs y otros

1
2
3
4
5

<?php
$color1
$color2
$color1
$color1

= new Color('Rojo'); //Color es un Value Object


= new Color('Rojo');
== $color2; // true
=== $color2; // false, igualdad no significa identidad

De estos Value Object no necesitamos hacer Doubles puesto que los podemos crear sin ningn tipo
de complicacin ni sobrecoste y debemos tratarlos como primitivas del lenguaje.
Otro tipo de objetos colaboradores para los que no debemos hacer Test Doubles son los objetos que
no podemos modificar. Concretamente las libreras de terceros [Growing Object-Oriented Software
Guided by Tests, captulo 8]. El feedback que obtenemos de los tests no lo podemos trasladar a
un refactoring de la librera. Aunque dispongamos del cdigo fuente no es conveniente modificar
objetos de terceros. Adems no podemos tener total seguridad de que el Double que creemos imite
perfectamente el comportamiento de la librera incluso tras una actualizacin. Para integrar libreras
de terceros en nuestro cdigo debemos hacerlos mediante una capa de adaptacin e interfaces que
representen la relacin de nuestra lgica de negocio con el mundo exterior.

Integracin de libreras de terceros

Probaremos el comportamiento de estas libreras de terceros mediante tests de integracin en los


que sustituiremos nuestros propios objetos de aplicacin por Doubles para mantener la filosofa de
aislamiento y repetibilidad.
Hay una excepcin y es que cuando el comportamiento de la librera sea complicado de reproducir
en los tests de integracin, como por ejemplo puede ser el caso de una cache, es preferible emplear
un Double en su lugar.

Mocks, Dummies, Stubs y otros

20

Libreras de Mocks
Ahora que ms o menos tenemos claro qu son los Mocks, Stubs y allegados hay que ver como los
creamos en nuestros tests. Para ello tenemos diversas posibilidades.

PHPUnit
La primera opcin es no usar ninguna librera, porque casi seguro que ya estaremos usando
PHPUnit para nuestros tests. A fin de cuentas es la referencia para el testing con PHP. Un Mock o
un Stub con PHPUnit se crea de la siguiente forma:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<?php
$emMock = $this->getMock('\Doctrine\ORM\EntityManager',
array('getRepository', 'getClassMetadata', 'persist', 'flush'), array(), '',\
false);
$emMock->expects($this->any())
->method('getRepository')
->will($this->returnValue(new FakeRepository()));
$emMock->expects($this->any())
->method('getClassMetadata')
->will($this->returnValue((object)array('name' => 'aClass')));
$emMock->expects($this->any())
->method('persist')
->will($this->returnValue(null));
$emMock->expects($this->any())
->method('flush')
->will($this->returnValue(null));

Usar PHPUnit para esta labor tiene los siguientes inconvenientes:


Hay que escribir demasiado cdigo.
No queda clara cual es la diferencia entre Mocks y Stubs.

Mockery
Mockery es posiblemente la librera ms usada en PHP para crear Mocks y Stubs. Realmente
simplifica mucho la tarea de escribir cdigo y lo hace mas legible. El mismo ejemplo de antes en
PHPUnit escrito con Mockery sera as:
http://phpunit.de/
https://github.com/padraic/mockery

Mocks, Dummies, Stubs y otros

1
2
3
4
5
6
7
8

21

<?php
$emMock = \Mockery::mock('\Doctrine\ORM\EntityManager',
array(
'getRepository' => new FakeRepository(),
'getClassMetadata' => (object)array('name' => 'aClass'),
'persist' => null,
'flush' => null,
));

Adems de ser muchsimo ms claro y cmodo, Mockery ofrece muchas otras facilidades.
Integrar Mockery en PHPUnit es muy sencillo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<?php
use \Mockery as m;
class TemperatureTest extends PHPUnit_Framework_TestCase
{
public function tearDown()
{
m::close();
}
public function testGetsAverageTemperatureFromThreeServiceReadings()
{
$service = m::mock('service');
$service->shouldReceive('readTemp')->times(3)->andReturn(10, 12, 14);
$temperature = new Temperature($service);
$this->assertEquals(12, $temperature->average());
}
}

La parte importante de la integracin es el mtodo tearDown(). La llamada esttica a m::close()


limpia el contenedor de Mockery para ese test y ejecuta las tareas de verificacin necesarias para
revisar las expectativas.

Prophecy
Es una librera de mocking bastante nueva creada expresamente para satisfacer las necesidades de
PHPSpec2. Respecto a Mockery notaremos que cambia la sintaxis que a mi entender es muy clara y

Mocks, Dummies, Stubs y otros

22

legible. Hay otras diferencias ms importantes respecto a Mockery, pero voy a reservar un apartado
entero para esto a continuacin.
Lo que nos debe quedar claro es que si nos decantamos por PHPSpec2, aunque podemos usar
Mockery u otra librera, Prophecy es la librera en la que se van a centrar los desarrolladores y
por lo tanto donde encontraremos ms soporte y garanta de continuidad.

AspectMock
Esta librera se ha creado dentro del framework de testing de Codeception. Se basa en las librera de
AOP GoAOP y podemos hacer cosas como crear doubles de mtodos estticos.
Un ejemplo de test con AspectMock usado dentro de PHPUnit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<?php
use AspectMock\Test as test;
class UserTest extends \PHPUnit_Framework_TestCase
{
protected function tearDown()
{
test::clean(); // remove all registered test doubles
}
public function testDoubleClass()
{
$user = test::double('demo\UserModel', ['save' => null]);
\demo\UserModel::tableName();
\demo\UserModel::tableName();
$user->verifyInvokedMultipleTimes('tableName',2);
}
}

La principal ventaja de AspectMock es que podemos emplearlo para hacer mocks de casi cualquier
cdigo PHP. No siempre nos enfrentamos con software bien estructurado y que haya sido creado
teniendo en cuenta el testing. Es posible que queremos hacer tests del algo que nunca escuch hablar
de inyeccin de dependencias.
https://github.com/phpspec/prophecy
https://github.com/lisachenko/go-aop-php
https://github.com/Codeception/AspectMock

Mocks, Dummies, Stubs y otros

23

Diferencia conceptual entre Mockery y Prophecy


Traduccin/adaptacin del post de Konstantin Kudryashov @everzet http://everzet.com/post/72910908762/
conceptual-difference-between-mockery-and-prophecy
La idea del artculo es explicar la diferencia sintctica y de implementacin entre ambas libreras.
Conceptualmente y en contraposicin con Mockery, Prophecy prioriza los mensajes de los objetos
(cmo se comunican los objetos) sobre la estructura (cundo se comunican los objetos)
Un ejemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14

<?php
interface User
{
public function getRating();
public function setRating($rating);
}
class UserRatingCalculator
{
public function increaseUserRating(User $user, $add = 1)
{
$user->setRating($user->getRating() + $add);
}
}

Es ms o menos una variacin del ejemplo de la calculadora. El test con Mockery sera algo as:
1
2
3
4
5
6
7

<?php
$user = Mockery::mock('User');
$user->shouldReceive('getRating')->andReturn(2);
$user->shouldReceive('setRating')->with(4)->once();
$calc = new UserRatingCalculator();
$calc->increaseUserRating($user->mock(), 2);

El test con Prophecy seria este:

Mocks, Dummies, Stubs y otros

1
2
3
4
5
6
7

24

<?php
$user = $prophet->prophesize('User');
$user->getRating()->willReturn(2);
$user->setRating(4)->shouldBeCalled();
$calc = new UserRatingCalculator();
$calc->increaseUserRating($user->reveal(), 2);

Excepto por pequeas diferencias ambos tests parecen iguales. Compliquemos un poco ms las cosas.
Digamos que ahora queremos disparar un evento antes y despus de un cambio en el rating. La nueva
calculadora sera esta:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<?php
class UserRatingCalculator
{
private $dispatcher;
public function __construct(EventDispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}
public function increaseUserRating(User $user, $add = 1)
{
$this->dispatcher->userRatingIncreasing($user->getRating());
$user->setRating($user->getRating() + $add);
$this->dispatcher->userRatingIncreased($user->getRating());
}
}

Ahora necesitamos verificar qu evento es disparado con un argumento especfico. En Mockery el


test sera este:

Mocks, Dummies, Stubs y otros

1
2
3
4
5
6
7
8
9
10
11

25

<?php
$user = Mockery::mock('User');
$user->shouldReceive('getRating')->andReturn(2, 2, 4);
$user->shouldReceive('setRating')->with(4)->once();
$disp = Mockery::mock('EventDispatcher');
$disp->shouldReceive('userRatingIncreasing')->with(2)->once();
$disp->shouldReceive('userRatingIncreased')->with(4)->once();
$calc = new UserRatingCalculator($disp->mock());
$calc->increaseUserRating($user->mock(), 2);

La clave est en fijarse cmo se ha hecho el stub del mtodo getRating() con tres valores de retorno
consecutivos. A esto de le llama structure binding, fijacin a la estructura del cdigo, significa
que los tests son dependientes de como el cdigo est escrito (estructurado), hay tres llamadas
consecutivas en ese orden para devolver esos valores.
Con Prophecy la solucin es diferente:
1
2
3
4
5
6
7
8
9
10
11
12
13

<?php
$user = $prophet->prophesize('User');
$user->getRating()->willReturn(2);
$user->setRating(Argument::type('integer'))->will(function($rating) {
$this->getRating()->willReturn($rating);
})->shouldBeCalled();
$disp = $prophet->prophesize('EventDispatcher');
$disp->userRatingIncreasing(2)->shouldBeCalled();
$disp->userRatingIncreased(4)->shouldBeCalled();
$calc = new UserRatingCalculator($disp->reveal());
$calc->increaseUserRating($user->reveal(), 2);

Prophecy usa un enfoque tipo message binding (orientado el mensaje), que significa que el
comportamiento del mtodo no cambia en el tiempo, sino es cambiado por el otro mtodo.
Cul es la diferencia real entre el enfoque de ambas libreras? Consideremos un cambio en la
calculadora:

Mocks, Dummies, Stubs y otros

1
2
3
4
5
6
7
8
9
10
11

26

<?php
public function increaseUserRating(User $user, $add = 1)
{
$initialRating = $user->getRating();
$this->dispatcher->userRatingIncreasing($initialRating);
$user->setRating($initialRating + $add);
$resultingRating = $user->getRating();
$this->dispatcher->userRatingIncreased($resultingRating);
}

Simplemente hemos puesto el rating inicial en una variable local $initialRating por clarificar el
cdigo. El problema es que este pequeo cambio hace que se rompa el test hecho con Mockery
puesto que ahora hay slo dos llamadas a getRating() en vez de tres. Si los test estn asociados a
la estructura y esta cambia entonces el test falla.
En el caso de Prophecy el test inicial sigue pasando porque el test se ha creado centrado en los
mensajes que se pasan entre los objetos y esto no ha cambiado.
La diferencia conceptual no esta en cmo escribir los tests, sino cundo hay que corregirlos. Mockery
te puede poner en la situacin en la que tengas que rehacer los tests simplemente porque has hecho
un refactoring que afecta a la estructura. Prophecy postula que en este caso el test no debera fallar
porque el comportamiento de los objetos sigue siendo el mismo.

Qu librera de Mocking elegir


Un pequeo rbol de decisin nos puede asistir a la hora de elegir una librera de Mocking

27

Mocks, Dummies, Stubs y otros

rbol de decisin de librera de mocking

Este rbol refleja lo que lo elegira, es posible que haya gente que no empleara Prophecy por ser muy
nueva y porque Mockery tiene mucha comunidad y es muy sencilla de integrar en casi cualquier
contexto de testing. Lo que tengo claro es que no empleara el sistema de Mocking de PHPUnit
porque hay que escribir demasiado cdigo y es difcil de leer.

Calidad del software


La calidad del software no se da porque se hagan tests aunque hacerlos puede ayudar. Tambin es
cierto que un software de calidad es ms sencillo de testear.

Sntomas de software difcil de testear y mantener


Desarrollar software haciendo TDD no hace que nuestro cdigo est mejor diseado ni que sea ms
sencillo de evolucionar. Cuando incorporamos el testing a nuestro proceso de desarrollo, una de las
metas que buscamos, es que el software tenga ms calidad. Para conseguir esta calidad debemos
evitar ciertas prcticas que son indicativas de lo contrario.
Cuanto mejor sea nuestro cdigo ms sencillo ser crear los tests para verificar su funcionamiento.

Uso del operador new


Ver el operador new dentro de una clase es un bad smell inmediato. Los objetos deben probarse
de forma aislada y si creamos objetos en el interior de lo que estamos probando rompemos esa
regla porque no tenemos control sobre estos nuevos objetos. Adems, para eso est la inyeccin de
dependencias. Si inyectamos los objetos que necesitamos, por ejemplo en el constructor, es sencillo
reemplazalos por otros. Esto es fundamental en los tests, pero tambin lo ser para extender nuestro
cdigo de forma que podamos reemplazarlo por otro que haga algo parecido y que implemente la
misma interfaz.
Un ejemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13

<?php
class Coche {
private $motor;
public function __construct() {
$this->motor = new Motor();
}
public function avanzar($velocidad, $tiempo=1) {
$this->motor->encender();
// hacer otras cosas
}
}

Calidad del software

29

En esta clase Coche no podemos cambiar el tipo de Motor por otro. En nuestros tests tampoco
podremos reemplazar ese Motor por un Mock de ste para esegurarnos que llamar a avanzar()
enciende el Motoruna vez.
En el ejemplo anterior si aplicamos inyeccin de dependencias:
1
2
3
4
5
6
7
8
9
10
11
12
13

<?php
class Coche {
public $motor;
public function __construct($motor) {
$this->motor = $motor;
}
public function avanzar($velocidad) {
$this->motor->encender();
// hacer otras cosas
}
}

Simplemente pasamos Motor como un parmetro del constructor. Ahora podemos reemplazar
nuestro Motor por un MotorDeAvion
1
2
3

<?php
$coche1 = new Coche(new Motor());
$coche2 = new Coche(new MotorDeAvion());

De la misma forma podemos escribir nuestros test as:


1
2
3
4
5
6
7
8
9
10
11
12
13

<?php
use \Mockery as m;
class CocheTest extends PHPUnit_Framework_TestCase
{
public function testCocheArrancaMotor()
{
$motorMock = m::mock('Motor');
$motorMock->shouldReceive('encender')->once();
$coche = new Coche($motorMock);
}

Calidad del software

14
15
16
17
18

30

protected function tearDown()


{
m::close();
}
}

Si no podemos sustituir el Motor por un Test Double obtenemos un error. Este es uno de los motivos
por los que podemos decir que escribir test hace que nuestro cdigo mejore en calidad.
No por ver un operador new tenemos que afirmar ese cdigo es malo, hay casos en los que no es una
mala prctica.
Los Value Object (objetos simples que no tienen un atributo de identidad como por ejemplo
instancias de CodigoPostal) no hay que inyectarlos y los podemos crear con new. Estos Value Object
son parecidos a los tipos primitivos del lenguaje y no tienen a su vez dependencias con objetos ms
complejos que ellos (otros Value Objects).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<?php
class CodigoPostal {
private $cp;
public function __construct($cp) {
$this->cp = $cp;
}
// getter y setter
}
class MiController {
public function newCodigoPostalAction($cp) {
$cpObject = new CodigoPostal($cp);
// ... no pasa nada por ese 'new'
}
}

Los Data Transfer Objects (DTO), de alguna forma son un wrapper o un facade de una base de datos
del algn tipo. Los DTO tienen identidad. La finalidad de los DTO es facilitar el flujo de informacin
entre una capa de dominio y otra. No tienen dependencias, pero pueden tener asociaciones. Por
ejemplo, a un array o a una coleccin. Son objetos de transicin que de alguna forma deben ser
persistidos o serializados.
Es complicado insertar un objeto como inyeccin de dependencias que previamente no debera
existir.
Un ejemplo de estos objetos DTO son las entidades de Doctrine o los objetos de eventos tipo Event

Calidad del software

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

31

<?php
class UserService {
private $reposotory;
private $eDispatcher;
public function __construct(EventDispatcher $eDispatcher, UserRepository $repos\
itory)
{
$this->eDispatcher = $eDispatcher;
$this->repository = $repository;
}
public function addUser() {
// ... no pasa nada por este 'new'
$event = new UserEvent($user)
$this->eDispatcher->dispatch('user.new', $event);
};
}

En determinadas situaciones podemos crear los DTO mediante factorias (Factory Pattern) en cuyo
caso, las factoras debern ser insertadas mediante injeccin de dependencias.

Encadenamiento de metodos (Demeter law)


La Ley de Demeter (Demeter Law) o Principio de menos conocimiento, se resume de la siguiente
forma: cada unidad debe hablar solo con sus amigos y no hablar con extraos (Wikipedia). Yo lo
suelo resumir como hay que evitar el encadenamiento de mtodos (cuando cambias de objeto en
la cadena).
Esto no debera hacerse:
1
2

<?php
$coche->motor->getTurbo()->encender()

En su lugar seria preferible:


1
2

<?php
$coche->motor->arrancar()

Nosotros no debemos conocer si estamos trabajando con un motor que posee un Turbo y cmo se
maneja. Slo queremos arrancar el coche.
Tambin es ms complicado escribir los test si no se sigue esta regla:

Calidad del software

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

32

<?php
use \Mockery as m;
class CocheTest extends PHPUnit_Framework_TestCase
{
public function testCocheArrancaMotor()
{
$turboMock = m::mock('Turbo');
$turboMock->shouldReceive('encender')->once();
$motor = m::mock('Motor');
$motor->shouldReceive('getTurbo')->andReturn($turboMock);
$coche = new Coche($motor);
}
protected function tearDown()
{
m::close();
}
}

Imaginemos que nuestro controlador trabaja con un objeto Coche, nosotros slo sabemos de Coche
a travs de sus mtodos pero tenemos o no debemos tener conocimiento de los elementos que lo
componen.
En Symfony es comn encadenar mtodos de esta forma. Un ejemplo de la documentacin oficial:
1
2
3
4
5
6
7
8
9
10
11
12
13

<?php
use Acme\StoreBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
//...
public function saveAction($product)
{
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return new Response('Created product id '.$product->getId());
}

Calidad del software

33

Se deberan evitar llamadas de este tipo $em = $this->getDoctrine()->getManager();. En su lugar


podramos reemplazarlo por un servicio que gestione las entidades de tipo Producto:
1
2
3
4
5
6
7
8
9
10

<?php
use Acme\StoreBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
//...
public function saveAction($product)
{
$this->productService->save($product);
return new Response('Created product id '.$product->getId());
}

Uso de Singleton
El patrn Singleton tiene la distincin de ser no solamente uno de los patrones ms sencillos, sino
tambin uno de los ms controvertidos. El principal problema con este patrn es que se introduce
un estado global en nuestro programa. Todo el mundo entiende que no hay que emplear variables
globales, pero muchas veces se sustituyen por Singletons que hacen la misma funcin.
Otro gran problema con los Singletons es que puede ocultar dependencias entre mdulos.
Por ejemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<?php
class S { //Singleton
private static $instance;
public static function getInstance() {};
public function getValue() {};
public function setValue() {};
}
class A {
private $s;
public function __construct(S $mySingleton) {
$this->s = $mysingleton;
}
public function unaFuncion() {
// ...
if ($this->s->getInstance()->getValue())
// ....

Calidad del software

19
20
21
22
23
24
25
26
27
28
29
30
31
32

34

}
}
class B {
private $s;
public function __construct(S $mySingleton) {
$this->s = $mysingleton;
}
public function otraFuncion() {
$z = ...
$this->s->getInstance()->setValue($z);
}
}

Las clases A y B tienen una dependencia entre s que no puede ser percibida a simple vista mirando
las interfaces de las clases.

Uso de clases ContainerAware


ContainerAware is the new Singleton (Tobias Schilitt)
Esta clase implementa el mtodo setContainer() que permite inyectar el DIC dentro de un objeto
de forma que pueda recuperar los servicios directamente de l.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<?php
namespace Symfony\Component\DependencyInjection;
/**
* A simple implementation of ContainerAwareInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
abstract class ContainerAware implements ContainerAwareInterface
{
/**
* @var ContainerInterface
*
* @api
*/

Calidad del software

18
19
20
21
22
23
24
25
26
27
28
29
30
31

35

protected $container;
/**
* Sets the Container associated with this Controller.
*
* @param ContainerInterface $container A ContainerInterface instance
*
* @api
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}

Es bastante comn ver esto, por ejemplo en los controladores base de Symfony2.
1
2
3
4
5

<?php
// en Symfony / Bundle / FrameworkBundle / Controller / Controller.php
// ...
class Controller extends ContainerAware
// ...

Poner el Container al completo dentro una clase es tan malo como trabajar con Singleton
Esta prctica de inyectar el Container hace que el cdigo sea difcil de testear. Para hacer el testing
podemos:
Inyectar un Test Double del DIC y devolver Doubles de los servicios. Es decir, inyectar un
Double del DIC que hace casi lo mismo que el propio DIC pero que responde con Doubles de
servicios. Esto al final es hacer el cdigo de testing ms farragoso y aadir sobrecarga para
nada.
Usar el DIC pero hacer que este devuelva Doubles del los servicios. Implica cambiar la
configuracion del DIC y en realidad estamos haciendo tests de integracin en vez de tests
unitarios.
Hacer un Double de la clase a testear para sobreescribir un mtodo, normalmente get(),
que es el que se suele usar para acceder a los servicios del contenedor. Esto es una mala
prctica. Sobreescribir un mtodo de la propia clase a testear es ya en si un bad smell porque
no puedes asegurar que la versin con la que sobreescribes tenga el mismo comportamiento
que la versin original.
Por otro lado, el uso directo del contenedor de dependencias nos oculta las dependencias de las que
hace uso la clase puesto que la nica dependencia aparente es el propio contenedor.

Calidad del software

36

Demasiadas dependencias
Cuando una clase tiene demasiadas dependencias, digamos unas 5 6, aunque no se puede poner un
nmero exacto, es posible que tengamos que refactorizar puesto que seguramente est muy acoplada
entre diferentes objetos y su propsito seguramente sea demasiado amplio.
La solucin a este bad smell no es ocultar las dependencias agrupdola en otra clase o pasando el
Container. La solucin es repensar su funcionalidad y en todo caso si algunas de esas dependencias
se pueden agrupar como un servicio.

Clases con muchas responsabilidades


Segun el principio de nica Responsabilidad (Single Responsibility Principle) (Wikipedia) En
programacin orientada a objetos se suele definir como principio de diseo que cada clase debe
tener una nica responsabilidad y que esta debe estar contenida nicamente en la clase.
As: * Una clase debera tener slo una razn para cambiar. * Cada responsabilidad es el eje del
cambio. * Para contener la propagacin del cambio, debemos separar las responsabilidades. * Si una
clase asume ms de una responsabilidad, ser ms sensible al cambio. * Si una clase asume ms de
una responsabilidad, las responsabilidades se acoplan.
Si para describir el propsito de una clase necesitamos ms de una treintena de palabras o
necesitamos emplear conjunciones y u o tenga ms de una responsabilidad
Un ejemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<?php
class UserService {
private $mailer;
private $reposotory;
public function __construct(Mailer $mailer, UserRepository $repository) {
$this->mailer = $mailer;
$this->repository = $repository;
}
public function addUser() {};
public function emailNewUser(){
// envia un mail al usuario cada vez que se crea uno
};
}

Calidad del software

37

Este servicio se describe como Servicio que crea un usuario y enva el email de confirmacin. La
conjuncin y de la frase ya nos da la idea de que est haciendo ms cosas de las que debera.
Una forma de solucionar esto es empleando eventos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<?php
class UserService {
private $reposotory;
private $eDispatcher;
public function __construct(EventDispatcher $eDispatcher, UserRepository $repos\
itory)
{
$this->eDispatcher = $eDispatcher;
$this->repository = $repository;
}
public function addUser() {
// ...
$event = new UserEvent($user)
$this->eDispatcher->dispatch('user.new', $event);
};
}

Ahora UserService slo se encarga de crear usuarios. Enviar los emails es cosa de la clase que
gestione ese evento. As podemos extender ms el cdigo, por ejemplo, escribiendo un log cada vez
que se cree un usuario sin tener que modificar el servicio.

Principios SOLID
Principio de Responsabilidad nica (Single Responsibility
Principle -SRP- )
Una clase no debe tener ms de una razn para cambiar
Las clases deben ser diseadas para tener una nica rea de ocupacin y un conjunto de operaciones
que definan e implementen esa ocupacin en particular un nada ms.
Este punto ya lo hemos visto cuando hablbamos en los Bad Smells sobre clases con demasiadas
responsabilidades.

Calidad del software

38

Principio de Abierto Cerrado (Open close principle -OCP- )


Los elementos de software deben ser abiertos para ser extendidos y cerrados para no ser modificados.
Es decir, debemos ser capaces de cambiar aquello que hace una clase sin cambiar el propio cdigo
de la clase. Las nuevas funcionalidades se deben implementar con de nuevas clases y no cambiando
las que ya existen.
Un ejemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<?php
class Circulo {
public $radio;
public $centro;
}
class Cuadrado {
public $lado;
public $puntoSuperiorIzquierda;
}
class PaintGUI {
public function dibujarCirculo(Circulo cir) {}
public function dibujarCuadrado(Cuadrado cua) {}
}

Si ahora aadimos un nuevo tipo de figura Triangulo debemos modificar la clase PaintGUI, sera
mucho ms efectivo si hubisemos creado una interfaz para las figuras con un mtodo comn.
1
2
3
4
5
6
7
8
9
10
11
12
13
14

<?php
interface FiguraInterface {
public function dibujar();
}
class Circulo implements FiguraInterface {
public $radio;
public $centro;
public function dibujar(){}
}
class Cuadrado implements FiguraInterface {
public $lado;
public $puntoSuperiorIzquierda;

Calidad del software

15
16
17
18
19
20
21
22

39

public function dibujar(){}


}
class PaintGUI {
public function dibujar(FiguraInterface fig) {
fig.dibujar();
}
}

De esta forma no hay que modificar PaintGUI, nuestra clase esta abierta para ser extendida y cerrada
para ser modificada.

Principio de sustitucin de Liskov (Liskov sustitution principle


-LSP- )
Las clases hijas (derivadas) no deben romper las definiciones de tipo de la clase padre.
Los subtipos se deben poder sustituir por sus clases base
Una clase debe sobreescribir los mtodos de la clase padre de forma que no rompa su funcionalidad.
Se ve mejor con un ejemplo:
1
2
3
4
5
6
7
8
9
10

<?php
class Vehicle {
function startEngine() {
// Default engine start functionality
}
function accelerate() {
// Default acceleration functionality
}
}

La clase Vehicle puede ser abstracta y tener dos implementaciones:

Calidad del software

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

40

<?php
class Car extends Vehicle {
function startEngine() {
$this->engageIgnition();
parent::startEngine();
}
private function engageIgnition() {
// ...
}
}
class ElectricBus extends Vehicle {
function accelerate() {
$this->increaseVoltage();
$this->connectIndividualEngines();
}
private function increaseVoltage() {
// ...
}
private function connectIndividualEngines() {
// ...
}
}

Una clase cliente debera poder hacer uso de Car o ElectricBus como si de un Vehicle se tratase.
1
2
3
4
5
6
7

<?php
class Driver {
function go(Vehicle $v) {
$v->startEngine();
$v->accelerate();
}
}

Lo requerimientos que deben cumplir los mtodos que sobreescriban los de la clase padre son:
Su signatura debe encajar con la del mtodo padre

Calidad del software

41

Sus precondiciones (lo que acepta) debe ser igual o ms dbil


Su postcondicin (lo que devuelve) debe ser igual o ms fuerte
Si hay excepciones deben ser del mismo tipo que las del mtodo padre

Principio de Segregacin de Interfaces (Interface Segregation


Principle -ISP- )
Los clientes no deben ser forzados a depender de interfaces que no usan.
Este principio trata sobre las interfaces pesadas, que son las que tienen muchos mtodos. Si un cliente
tiene que implementar una interfaz pesada es posible que se vea obligado a implementar mtodos
que no use. Es mejor dividir las interfaces agrupando los mtodos por su funcionalidad de forma
que las clases cliente slo implementen los que necesiten.

Principio de Inversin de Dependencias (Dependency Inversion


Principle -DIP-)
Este principio tiene dos aspectos clave:
Las abstracciones no deben depender de los detalles (implementaciones).
Los detalles deben depender de abstracciones.
Hay que asegurarse que siempre estamos trabajando con interfaces de forma que se pueda garantizar
el reemplazo de una implemetacin por otra sin violar las especificaciones de la interfaz de acuerdo
con el principio de sustitucin de Liskov. La clase cliente nunca debe conocer detalles de la clase que
implementa la interfaz.
Corregir el acoplamiento mediante la inversin de dependencias
En el mundo real no podemos tener cero acoplamiento, ya que no es posible tener solamente clases
que no dependan de ninguna otra. Sin embargo debemos reducir el acoplamiento directo siempre
que sea posible, permitiendo cambiar una pieza del sistema sin tener que cambiar nada ms.
Si las clases slo dependen de abstracciones (interfaces o clases base) podemos corregir el acoplamiento de forma que podamos recomponer el sistema cambiando las implementaciones de las clases
que usan.
Supongamos que tenemos la siguiente clase Foo que depende de la clase Bar:

42

Calidad del software

Foo depende de Bar

No podemos llevarnos a otro sitio la clase Foo sin llevarnos con ella la clase Bar. Para desacoplar
estas clases podemos introducir la interfaz IBar de forma que Foo dependa de la ella y que Bar la
implemente.

Foo depende la interfaz IBar

Introducir IBar es un cambio necesario pero no suficiente. Hemos extraido la interfaz desde Bar,
pero esta perspectiva es incorrecta. Debemos extraer la interfaz que implementar Bar a partir de
los requerimientos de Foo de forma que podamos hacer un paquete con Foo e IBar
Injeccin Vs Inversin
No hay que confundir inyeccin de dependencias e inversin de dependencias, a pesar de que suelen
compartir el mismo acrnimo DI (Dependency Inversion/Dependency Injection).

Calidad del software

43

La injeccin trata de proveer las dependecias que necesita una clase en su contructor, setter o
atributos, para evitar que la clase tenga que crear estos objetos. Esto tiene mucho que ver con el
uso del operador new.
La inyeccin de dependencias tiene nada que ver con el acoplamiento de las clases. Podemos inyectar
dependencias sin que se reduzca el acoplamiento.
Un ejemplo:
1
2
3
4
5
6
7
8

<?php
class Foo {
private $bar;
public function __construct(Bar $bar) {
$this->bar = $bar;
}
}

Aqu estamos inyectando Bar pero la clase Foo sigue dependiendo de la implementacin concreta
de Bar.
La inversin de dependencias trata sobre cmo Foo establece un contrato tipo IBar que marca la
implementacin queBar debe completar. Ojo, no es Bar el que crea un contrato IBar que Foo puede
usar.
1
2
3
4
5
6
7
8
9

<?php
class Foo {
private $bar;
public function __construct(IBar $bar) {
$this->bar = $bar;
}
}

Normalmente inyeccin e inversin de dependencias van de la mano porque de esta forma es sencillo
reemplazar las implementaciones.

Notas sobre inyeccin de dependencias en Symfony


Symfony provee un componente para inyectar dependencias en otros objetos, pero conviene hacer
algunas anotaciones al respecto.

Calidad del software

44

Tipos de inyeccin
Existen diferentes formas en las que las dependencias pueden ser inyectadas.
En el constructor es la forma ms normal y la recomendada.
1
2
3
4
5
6
7
8
9
10
11
12

<?php
class NewsletterManager
{
protected $mailer;
public function __construct(\Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
}

Podemos indicar el tipo \Mailer para el parmetro de forma que obtengamos un error claro si se
inyecta otra cosa
La inyeccin en el constructor tiene ventajas:
Si la dependencia es un requerimiento nos aseguramos de que est presente si no el objeto no
se puede construir.
El constructor slo se llama cuando se crea el objeto por lo que podemos estar seguros de que
la dependencia no se cambia durante su vida.
Entre los inconvenientes se encuentra:
No es apropiado para trabajar con dependencias opcionales.
Es difcil trabajar con jerarquas de clases al tener que extender y sobreescribir el constructor
si fuese necesario.
Podemos inyectar las dependencias mediantes mtodos setter con la dependencia como parmetro:

Calidad del software

1
2
3
4
5
6
7
8
9
10
11
12

45

<?php
class NewsletterManager
{
protected $mailer;
public function setMailer(\Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
}

Las ventajas de usar un setter son:


Es sencillo trabajar con dependencias opcionales. Si no hace falta no se llama al setter.
Se puede llamar al setter varias veces. Esto es especialmente til si se aade la dependencia a
una coleccin.
Los inconvenientes son:
Como el setter se puede llamar ms de una vez y no slo en el momento de construir el objeto,
no puedes asegurar que la dependencia haya sido cambiada
Es posible que el setter no se haya llamado nunca y hay que aadir comprobaciones para
asegurarnos de que esta dependencia est presente
Las dependencias tambin se pueden inyectar como propiedades, pero este mtodo slo tiene
inconvenientes puesto que no hay control de cuando se satisfacen o cuando se modifican.
En general siempre hay que utilizar la inyeccin de dependencias en el constructor y evitar
hacerlo en el setter.
Si un objeto es dependencia de otro, por ejemplo usamos el objeto NewsletterManager del ejemplo
anterior en nuestro constructor.
1
2

<?php
$objeto = new UnaClase(new NewsletterManager());

Si dentro de nuestro constructor lo reconfiguramos $this->newsManager->setMailer($otraCosa).

Calidad del software

1
2
3
4
5
6
7
8

46

<?php
class UnaClase {
private $newsManager;
public function __construct($newsManager){
$this->newsManager = $newsManager;
$this->newsManager->setMailer($otraCosa)
}
}

Estaremos rompiendo la encapsulacin, puesto que nuestro controlador no debera saber mas que de
sus propias dependencias y ahora tiene informacin sobre cmo esta compuesto el NewsletterManager
que emplea. Ahora no se puede reescribir NewsletterManagersin cambiar el cdigo de UnaClase.
Otra razn para no emplear la inyeccin en el setter es que podemos tener objetos perfectamente
construidos pero que no tengan disponibles todas las dependencias que necesitan.
Un ejemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

<?php
class Foo {
private $dep;
public function setDependency(SomeDependency $dep) {
$this->dep = $dep;
}
public function someMethod() {
$baz = $this->dep->xx();
}
}

class Bar {
private $foo;
public function __construct(Foo $foo) {
$this->foo = $foo;
$this->foo->someMethod();
}
}

Calidad del software

26
27

47

$foo = new Foo();


$bar = new Bar($foo);

Aqu se producir un error porque Bar no tiene forma de saber si se ha llamado a setDependency()
de Foo.
Se puede dar el caso de que un test unitario que tenga un Mock de Foo->someMethod() funcione
perfectamente y falle al pasarlo a produccin, lo que nos puede llevar a errores difciles de trazar.
El caso que puede justificar la inyeccin de dependencias en setter es cuando stas son opcionales y
el objeto no las necesita para cumplir su responsabilidad:
1
2
3
4
5
6
7
8
9
10
11
12
13

<?php
class Foo {
private $logger;
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
public function someMethod() {
if ($this->logger) $this->logger->log('someMethod called');
}
}

Cuando Bar llame a $foo->someMethod() no ocurrir nada si no hay un logger. Como mucho se
producir algo de confusin al echar de menos los logs.

Controladores como servicio


Despus de quedarnos con la idea de que no es recomendable inyectar el contenedor de dependencias
directamente y que es mejor poner las dependencias exactas que necesita un objeto en su constructor,
llegamos al punto en que se plantea que debemos declarar nuestros controladores de Symfony como
servicios para poder hacer esto.
Esto en Symfony se hace de la siguiente forma:

Calidad del software

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

48

<?php
// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
public function indexAction($name)
{
return new Response('<html><body>Hello '.$name.'!</body></html>');
}
}
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
# ...
acme.controller.hello.class: Acme\HelloBundle\Controller\HelloController
services:
acme.hello.controller:
class: "%acme.controller.hello.class%"

Si un controlador est definido como un servicio tenemos que referirnos a l con una sintaxis
diferente:
1
2

<?php
$this->forward('acme.hello.controller:indexAction');

Y tamben dentro de los ficheros de configuracin:


1
2
3
4

app/config/routing.yml
hello:
path:
/hello
defaults: { _controller: acme.hello.controller:indexAction }

El hecho de tener que emplear una sintaxis diferente si nuestro controlador es un servicio o no es
una desventaja y un problema, entiendo que esto debera ser totalmente transparente. Adems hace
que sea ms difcil de configurar.
Hay un ticket de Symfony relativo al tema de los controladores como servicio, en l, Fabien
Potencier (el padre de Symfony) alega que los controladores no deben ser un servicio puesto que
https://github.com/symfony/symfony-docs/issues/457

Calidad del software

49

el DIC (Dependency Injection Container) se debe emplear para manejar objetos globales y los
controladores no encajaran ah, son el pegamento entre la capa de Vista y Modelo y deberan ser lo
ms ligeros posible.

JMSDiExtraBundle
Existe un bundle JMSSiExtraBundle, que aporta una solucin, en mi opinin perfecta, entre
declarar el controlador como servicio e inyectar el contenedor.
JMSSiExtraBundle permite inyectar dependencias mediante anotaciones y permite inyectarlas en
los controladores sin necesidad de declararlos como servicios, de forma que no nos vemos obligados
a cambiar la sintaxis.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

<?php
use JMS\DiExtraBundle\Annotation as DI;
class Controller
{
private $em;
private $session;
/**
* @DI\InjectParams({
*
"em" = @DI\Inject("doctrine.orm.entity_manager"),
*
"session" = @DI\Inject("session")
* })
*/
public function __construct($em, $session)
{
$this->em = $em;
$this->session = $session;
}
// ... some actions
}

Por otro lado, en el mismo hilo tambin se indica que los controladores se deberan testear
funcionalmente y no de forma unitaria, de forma que el Mocking del DIC ya no es un problema
Mi recomendacin es declarar todos los servicios en el fichero services.yml a excepcin de los
controladores, a los que les injectaremos sus dependencias mediante anotaciones gracias a este
bundle.
http://jmsyst.com/bundles/JMSDiExtraBundle

50

Calidad del software

Una nota: la injeccin en el constructor no es posible cuando hereda de una clase padre que tamben
tiene definido un constructor con injeccin de dependencias.

Calidad mediante mtricas


Una de las variables a tener en cuenta para decidir sobre la calidad del software es su simplicidad.
Podemos establecer una serie de mtricas que no una idea sobre sto e ir testeando peridicamente
que no crecemos en complejidad.
Para obtener los valores de estas mtricas podemos emplear la herramienta phploc de Sebastian
Bergmann.
Con esta herramienta obtenemos valores como estos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

phploc 2.0.4 by Sebastian Bergmann.


Directories
Files
Size
Lines of Code (LOC)
Comment Lines of Code (CLOC)
Non-Comment Lines of Code (NCLOC)
Logical Lines of Code (LLOC)
Classes
Average Class Length
Average Method Length
Functions
Average Function Length
Not in classes or functions
Complexity
Cyclomatic Complexity / LLOC
Cyclomatic Complexity / Number of Methods
Dependencies
Global Accesses
Global Constants
Global Variables
Super-Global Variables
Attribute Accesses
https://github.com/sebastianbergmann/phploc

6
11

565
230
335
131
89
9
2
0
0
42

(40.71%)
(59.29%)
(23.19%)
(67.94%)

(0.00%)
(32.06%)

0.02
1.08

0
0 (0.00%)
0 (0.00%)
0 (0.00%)
38

51

Calidad del software

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

Non-Static
Static
Method Calls
Non-Static
Static
Structure
Namespaces
Interfaces
Traits
Classes
Abstract Classes
Concrete Classes
Methods
Scope
Non-Static Methods
Static Methods
Visibility
Public Method
Non-Public Methods
Functions
Named Functions
Anonymous Functions
Constants
Global Constants
Class Constants

38
0
52
50
2

(100.00%)
(0.00%)
(96.15%)
(3.85%)

7
2
0
9
0 (0.00%)
9 (100.00%)
39
39 (100.00%)
0 (0.00%)
37
2
0
0
0
0
0
0

(94.87%)
(5.13%)
(0.00%)
(0.00%)
(0.00%)
(0.00%)

Complejidad ciclomtica
La complejidad ciclomtica (Cyclomatic Complexity -CC- ) indica el nmero de puntos en el cdigo
donde se toman decisiones.
Por ejemplo:
1
2
3
4
5
6
7
8
9

<?php
// ....
public function hacerAlgo() {
// Uno
$i = $i+2;
if($i == TRUE) // Dos
{}

Calidad del software

10
11
12

52

foreach($i as $algo) // Tres


{}
}

La siguiente funcin tiene un valor de CC de 3.


En funcin del valor obtenido para la funcin podemos clasificar la complejidad en:

1 - 4: Poca Complejidad
5 - 7: Complejidad moderada
8 - 10: Complejidad alta
11+: Complejidad muy alta

Es ms interesante obtener la CC promedio del nmero de mtodos, dividiendo CC/Numero_de_mtodos.


En funcin del valor obtenido y la siguiente tabla podemos clasificar la complejidad en:

1 - 2: Poca Complejidad
3 - 4: Complejidad Moderada
5 - 6: Complejidad Alta
6+: Complejidad muy alta

Tambin se puede obtener la CC por lnea de cdigo, en cuyo caso aplicamos la siguiente tabla:

0.01 - 0.05: Complejidad Baja


0.06 - 0.10: Complejidad Moderada
0.11 - 0.15: Complejidad Alta
0.15+: Complejidad muy Alta

Un valor de 0.05 indica que el 5% de nuestro cdigo se dedica a tomar decisiones.

NPATH
NPATH es una mtrica de software que nos indica en nmero de posibles caminos que puede seguir
un mtodo.
En el ejemplo anterior:

Calidad del software

1
2
3
4
5
6
7
8
9
10
11

53

<?php
// ....
public function hacerAlgo() {
$i = $i+2;
if($i == TRUE)
{}
foreach($i as $algo)
{}
}

El valor mximo de NPATH para el mtodo es 8, que se calcula de NPATH = 2(CC - 1).
En funcin del valor de NPATH y la siguiente tabla podemos clasificar la complejidad en:

1 - 16: Complejidad Baja.


17 - 128: Complejidad Moderada.
129 - 1024: Complejidad Alta.
1025+: Complejidad Muy Alta.

El valor de NPATH es importante porque no indica el nmero mnimo de tests que debemos crear
para probar completamente un mtodo. En el ejemplo anterior deberamos crear 8 tests unitarios para
asegurarnos de probar completamente la funcion.

CRAP
C.R.A.P. (Change Risk Analysis and Predictions) es una mtrica de software creada por Alberto
Savoia diseada para analizar la cantidad de esfuerzo que hay que invertir en mantener un trozo
de cdigo.
La frmulade CRAP es:
1

C.R.A.P.(m) = comp(m)^2 * (1 cov(m)/100)^3 + comp(m)

Donde comp es la complejidad ciclomtica y cov el porcentaje del cdigo cubierto por tests.
Si la cobertura es del 100% la frmula se reduce a:

http://www.artima.com/weblogs/viewpost.jsp?thread=210575

54

Calidad del software

C.R.A.P.(m) = comp(m)

En este caso el riesgo de cambiar algo es directamente proporcional a la complejidad del cdigo.
Si no hay ningn tipo de test la frmula es:
1

C.R.A.P.(m) = comp(m)^2 + comp(m)

En cuyo caso el riesgo a la hora de cambiar algo superior al cuadrado de la complejidad.


Se puede reducir el valor de CRAP bajando la complejidad o subiendo la cobertura o ambas cosas.
Podemos inferir las siguientes reglas:

Un mtodo de complejidad baja y sin tests es bueno


Un mtodo de complejidad baja y con buenos tests es magnfico
Un mtodo de complejidad moderada y con buenos tests es aceptable
Un mtodo de complejidad moderada y sin tests es basura

O visto con nmeros en funcin del valor de CRAP:

< 5 Magnfico
5-15 Aceptable
15-30 bueno, hay que revisarlo
30+ es basura

Podemos obtener la mtrica de CRAP cuando vemos un informe de covertura de cdigo


Por ejemplo:

CRAP
https://github.com/sebastianbergmann/php-code-coverage

55

Calidad del software

PDepend
PDepend es otra herramienta de anlisis de cdigo esttico un poco ms compleja que phploc.
Calcula mtricas de software y genera grficas de dependencia y mtricas que son muy tiles para
obtener una visin general y rpida de nuestro proyecto.
Un ejemplo de grfico de dependencias:

Grfico de dependencias

El grfico muestra la calidad de nuestro diseo en trminos de extensibilidad, reusabilidad y


mantenibilidad.
PDepend calcula las siguientes mtricas:
Ca, Acoplamiento Aferente: nmero de paquetes que dependen de clases en este paquete. Es
un buen indicador de cmo cambios en clases de este paquete pueden afectar a otras partes
del software.
Ce, Acoplamiento Eferente: nmero de otros paquetes que tienen clases de las que dependen
las clases de este paquete. Indica cmo de sensible es este paquete respecto a cambios en otros
paquetes.
I, Inestabilidad: Es el ratio entre el acoplamiento eferente y el acoplamiento total I =
Ce/(Ce+Ca). Produce valores entre [0, 1]. Un valor de I=0 indica un valor de estabilidad
mximo, es decir que no depende de nadie. Un valor de I=1 indica una inestabilidad mxima,
es decir una clase de la que nadie depende pero que depende de otras. La palabra inestabilidad
suena a problema pero no tiene por qu serlo, es normal tener alguna clases con I=1
http://pdepend.org/

56

Calidad del software

A, Abstraccin: Es el ratio entre las clases abstractas (o interfaces) ac y el total de clases y se


calcula como A = ac/(ac + cc). Un valor de A=0 indica que todas las clases de ese quete
son no-abstractas, mientras que un valor de A=1 indica que ese paquete slo contiene clases
abstractas.
No todos los paquetes en un proyecto pueden tener los valores mximos. La lnea diagonal de la
grfica de dependencia representa la zona indicativa de paquetes que tienen una buena relacin
antre abstraccin y estabilidad. Es deseable que todos los paquetes se aproximen a esa lnea.
Con PDepend podemos generar grficas de pirmide de mtricas:

Grfica de pirmide

En la pirmide se distinguen tres bloques:

bloques de la grfica de pirmide

Tamao y complejidad, que continene mtricas como la compleidad ciclomtica.


Acoplamiento, que da una idea de la relacin entre diferentes partes del software.
Herencia, que proporciona una visin del uso de la herencia de clases.
Para tener ms informacin sobre los valores individuales que figuran en la grfica en recomendable
leerse el manual de PDepend.
Si nos habituamos a leer los grficos generados por PDepend podemos obtener rpidamente una
visin general sobre la calidad de nuestro software y nos sirve como gua para comprobar la buena
direccin de nuestras decisiones de diseo.
http://pdepend.org/documentation/handbook/reports/overview-pyramid.html

DataFixutures
Una de las cosas que seguro vamos a tener que hacer en el testing de nuestra aplicacin es crear un
conjunto de datos de prueba. Esto tambin debemos hacerlo de manera automatizada.
Los datos de prueba se emplearn en los tests funcionales y en el StoryBDD pero nunca en los tests
unitarios. Hay que recordar que los tests unitarios no interacionan con las base de datos.

Entornos de ejecucin en Symfony


Una aplicacin Symfony es la combinacin del cdigo y un conjunto de ficheros de configuracin
que dictan cmo se debe comportar ese cdigo (documentacion de Symfony). La configuracin
puede definir la base de datos a emplear, si algo debe ser cacheado o cmo de explcito debe ser el
log. En Symfony la idea de entorno (environment) es el mismo cdigo con diferentes configuraciones.
Una aplicacin Symfony viene por defecto con tres entornos: dev, prod y test. Cada entorno tiene su
fichero de configuracin:
el entorno de dev: app/config/config_dev.yml
el entorno de prod: app/config/config_prod.yml
el entorno de test: app/config/config_test.yml
Esto se configura en el fichero AppKernel.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14

<?php
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
// ...
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
}
}
http://symfony.com/doc/current/cookbook/configuration/environments.html

DataFixutures

58

Podemos cambiar el fichero config_test.yml para usar una base de datos diferente usando un
parameters_test.yml personalizado:
1
2
3
4

#config_test.yml
imports:
- { resource: config_dev.yml }
- { resource: parameters_test.yml }

Para llamar a la aplicacin desde el navegador usando el entorno de test debemos usar el fichero
app_test.php. Es decir: http://localhost/nuestrapp/app_test.php
Es interesante tener un entorno de test especfico separado del de desarrollo. As podemos tener
parmetros parecidos a los de produccin pero con un conjunto de datos controlado.

DoctrineFixturesBundle
Si empleas Doctrine, el bundle DoctrinefixturesBundle facilita la tarea de carga de datos.
Los datos generados se pueden emplear para el entorno de test o para cargar los datos iniciales de
la aplicacin. Si lo usas para la carga inicial de datos hay que tener mucho cuidado, ya que antes de
cargar los datos borra la base de datos al completo si no se usa el flag append.
Para instalar el bundle hay que aadir a composer.json:
1
2
3
4
5

{
"require": {
"doctrine/doctrine-fixtures-bundle": "2.2.*"
}
}

Y actualizar las libreras:


1

$ php composer.phar update doctrine/doctrine-fixtures-bundle

Por ltimo hay que registrar el bundle en AppKernel.php:

DataFixutures

1
2
3
4
5
6
7
8
9
10
11

59

<?php
// ...
public function registerBundles()
{
$bundles = array(
// ...
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),
// ...
);
// ...
}

Si tenemos claro que este bundle slo lo vamos a emplear en los entornos de dev y test pero no
en prod lo registraremos en AppKernel.php slo para eso entornos y as nos ahorramos cargarlo en
produccin.
1
2
3
4
5
6
7

<?php
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
$bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
}

Una vez instalado DoctrineFixturesBundle crearemos la carpeta DataFixtures/ORM dentro de


nuestro bundle y ah crearemos clases que heredan de AbstractFixture y que, opcionalmente,
implementan OrderedFixtureInterface. Por ejemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14

<?php
# Test/MyBundle/DataFixtures/ORM/LoadUserData.php
namespace Test3\MyBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Test\MyBundle\Entity\User;
class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
{
/**
* Main method for fixtures insertion

DataFixutures

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

60

*
* @param Doctrine\Manager $manager
*/
public function load($manager)
{
$user = new User();
$manager->persist($user);
$manager->flush();
//Associate a reference for other fixtures
$this->addReference('user-admin', $user);
}
/**
* Get the order of this execution
*
* @return int
*/
public function getOrder()
{
return 1;
}
}

El mtodo getOrder() devuelve un nmero que indica en qu orden ejecutar las clases de inserccin
de datos.
La carga de datos se ejecuta mediante el comando:
1

./app/console doctrine:fixtures:load

Si queremos que los datos existentes no se eliminen ejecutaremos:


1

./app/console doctrine:fixtures:load --append

Si en vez de trabajar con un ORM empleamos un ADM, como MongoDB, ejecutaremos:


1

app/console doctrine:mongodb:fixtures:load

Cuando trabajamos con MongoDB las clases de los fixtures se guardan en la carpeta DataFixtures/MongoDB.

DataFixutures

61

Es sencillo compartir objetos entre las clases que definimos. Para guardarlo llamamos a $this->addReference('mi-r
$miObjeto); y para recuperarlo en cualquier otra clase de fixtures a $this->getReference('mi-referencia').
Si necesitamos hacer cosas complejas, por ejemplo, usando el contenedor de dependencias, podemos
obtenerlo en la clase de fixtures. Para ello tienes que implementar la interfaz ContainerAwareInterface
y el mtodo setContainer.
Veamos un ejemplo para crear las contraseas de los usuarios:
// src/Acme/HelloBundle/DataFixtures/ORM/LoadUserData.php
namespace AcmeHelloBundleDataFixturesORM;
use DoctrineCommonDataFixturesFixtureInterface; use DoctrineCommonPersistenceObjectManager; use SymfonyComponentDependencyInjectionContainerAwareInterface; use SymfonyComponentDependencyInjectionContainerInterface; use AcmeHelloBundleEntityUser;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

<?php
class LoadUserData implements FixtureInterface, ContainerAwareInterface
{
/**
* @var ContainerInterface
*/
private $container;
/**
* {@inheritDoc}
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
/**
* {@inheritDoc}
*/
public function load(ObjectManager $manager)
{
$user = new User();
$user->setUsername('admin');
$user->setSalt(md5(uniqid()));
$encoder = $this->container
->get('security.encoder_factory')
->getEncoder($user)

DataFixutures

29
30
31
32
33
34
35

;
$user->setPassword($encoder->encodePassword('secret', $user->getSalt()));
$manager->persist($user);
$manager->flush();
}
}

Faker
Faker es una librera de Francois Zaninotto que nos ayuda a crear datos de prueba.
Instalar esta librera es muy sencillo con Composer:
1
2
3
4

#composer.json
"require-dev": {
"fzaninotto/faker": "dev-master"
}

Podemos aadirla en require-dev o en require.


Usar Faker es muy sencillo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// use the factory to create a Faker\Generator instance


$faker = Faker\Factory::create();
// generate data by accessing properties
echo $faker->name;
// 'Lucy Cechtelar';
echo $faker->address;
// "426 Jordy Lodge
// Cartwrightshire, SC 88120-6700"
echo $faker->text;
// Sint velit eveniet. Rerum atque repellat voluptatem quia rerum. Numquam exc\
epturi
// beatae sint laudantium consequatur. Magni occaecati itaque sint et sit temp\
ore. Nesciunt
// amet quidem. Iusto deleniti cum autem ad quia aperiam.
// A consectetur quos aliquam. In iste aliquid et aut similique suscipit. Cons\
equatur qui
// quaerat iste minus hic expedita. Consequuntur error magni et laboriosam. Au\
https://github.com/fzaninotto/Faker

62

DataFixutures

19
20
21
22
23
24
25

63

t aspernatur
// voluptatem sit aliquam. Dolores voluptatum est.
// Aut molestias et maxime. Fugit autem facilis quos vero. Eius quibusdam poss\
imus est.
// Ea quaerat et quisquam. Deleniti sunt quam. Adipisci consequatur id in occa\
ecati.
// Et sint et. Ut ducimus quod nemo ab voluptatum.

Cada vez que llamamos a $faker->name genera un nombre diferente.


Tiene muchos proveedores de datos: colores, Visa, email, etc. Lo que hace que podamos rellenar casi
cualquier tipo de dato sin necesitar de crear un proveedor propio.
Hay dos modificadores que se pueden poner a las propiedades unique() y optional() que como se
intuye, devuelve valores nicos en todas las llamadas o devuelve algunas llamadas como null.
Ejemplo de unique():
1
2
3
4
5
6
7
8

<?php
// unique() forces providers to return unique values
$values = array();
for ($i=0; $i < 10; $i++) {
// get a random digit, but always a new one, to avoid duplicates
$values []= $faker->unique()->randomDigit;
}
print_r($values); // [4, 1, 8, 5, 0, 2, 6, 9, 7, 3]

Ejemplo de optional():
1
2
3
4
5
6
7
8

<?php
// optional() sometimes bypasses the provider to return null instead
$values = array();
for ($i=0; $i < 10; $i++) {
// get a random digit, but also null sometimes
$values []= $faker->optional()->randomDigit;
}
print_r($values); // [1, 4, null, 9, 5, null, null, 4, 6, null]

Se puede ajustar la probabilidad de que un valor sea null:

DataFixutures

1
2
3
4
5
6
7

64

<?php
// optional() accepts a weight argument to specify the probability of receiving \
a NULL value.
// 0 will always return NULL; 1 will always return the provider. Default weight \
is 0.5.
$faker->optional($weight = 0.1)->randomDigit; // 90% chance of NULL
$faker->optional($weight = 0.9)->randomDigit; // 10% chance of NULL

Por defecto los valores generados por Faker estn en ingls, pero se pueden generar en espaol o en
otros idiomas.
1
2

<?php
$faker = Faker\Factory::create('es_ES');

Faker genera datos aleatorios, pero puede que nos interese generar siempre el mismo conjunto de
datos. Para hacer esto debemos inicializar Faker con la misma semilla:
1
2
3

<?php
$faker = Faker\Factory::create();
$faker->seed(1234);

De esta forma sucesivas invocaciones al script de Faker generarn los mismo resultados.
Faker tiene la posibilidad de crear entidades directamente con Doctrine pero lo ideal es emplearlo
dentro de DoctrineFixturesBundle.

Alice
Alice es una librera que permite crear fixtures de una forma bastante expresiva y sencilla mediante
archivos yaml.
Para instalarlo lo hacemos con Composer:
1
2
3
4

#composer.json
"require-dev": {
"nelmio/alice": "*"
}

Posteriormente podemos emplearlo dentro de DoctrineFixturesBundle. Por ejemplo:


https://github.com/fzaninotto/Faker/tree/master/src/Faker/Provider
https://github.com/nelmio/alice

DataFixutures

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

65

<?php
class LoadAdvisesData implements FixtureInterface
{
/**
* {@inheritDoc}
*/
public function load(ObjectManager $manager)
{
$loader = new \Nelmio\Alice\Loader\Yaml();
$objects = $loader->load(__DIR__.'/fixtures.yml');
$persister = new \Nelmio\Alice\ORM\Doctrine($manager);
$persister->persist($objects);
}
}

La definicin de los objetos de las entidades se hacen en fixtures.yml. Para un ejemplo sencillo se
crean todos los objetos dentro del mismo fichero, pero podramos partirlo en varios ficheros.
Los ficheros yaml de fixtures tienen esta forma:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Nelmio\Entity\User:
user0:
username: bob
fullname: Bob
birthDate: 1980-10-10
email: bob@example.org
favoriteNumber: 42
user1:
username: alice
fullname: Alice
birthDate: 1978-07-12
email: alice@example.org
favoriteNumber: 27
Nelmio\Entity\Group:
group1:
name: Admins

Se indica la clase de la entidad, un identificador y el valor de las propiedades.


Para no tener que ir creando las entidades de una en una se pueden hacer con rangos:

DataFixutures

1
2
3
4
5
6
7

66

Nelmio\Entity\User:
user{1..10}:
username: bob
fullname: Bob
birthDate: 1980-10-10
email: bob@example.org
favoriteNumber: 42

De esta forma se crean las entidades user1, user2 , user10, pero todos con los mismos valores.
Alice se combina con Faker. No hay que hacer nada a nivel de configuracin. Llamando, por ejemplo,
a <username()> estamos llamando en realidad a $faker->username
El fichero anterior para la creacin de usuarios quedara de esta forma:
1
2
3
4
5
6
7

Nelmio\Entity\User:
user{1..10}:
username: <username()>
fullname: <firstName()> <lastName()>
birthDate: <date()>
email: <email()>
favoriteNumber: <numberBetween(1, 200)>

Se puede llamar a mtodos de la entidad, incluido el constructor. Para ello especificaremos los
parmetros entre corchetes.
Para llamar el mtodo setLocation():
1
2
3
4

Nelmio\Entity\User:
user1:
username: <username()>
setLocation: [40.689269, -74.044737]

Para llamar al constructor:


1
2
3

Nelmio\Entity\User:
user1:
__construct: [<username()>]

Se puede establecer que ciertos datos son opcionales mediante la notacin 50%? value : empty
value donde 50%es la probabilidad. Si null es un valor vlido entonces se puede resumir como %50?
value.
Un ejemplo:

DataFixutures

1
2
3
4
5
6
7

67

Nelmio\Entity\User:
user{1..10}:
username: <username()>
fullname: <firstName()> <lastName()>
birthDate: <date()>
email: <email()>
favoriteNumber: 50%? <numberBetween(1, 200)>

Unas entidades pueden referenciar a otras empleando la notacin @nombre.


Un ejemplo con usuarios y grupos:
1
2
3
4
5
6
7

Nelmio\Entity\User:
# ...
Nelmio\Entity\Group:
group1:
name: Admins
owner: @user1

Se pueden establecer referencias a mltiples objetos, referencias opcional etc. Lo mejor es revisar la
documentacin de Alice.
En resumen con Alice podemos crear de una forma muy sencilla y legible los fixtures que
necesitemos en el 90% de los casos. Un ejemplo en el que no podramos, o sera difcil emplear
Alice, es que tengamos que hacer uso del Container de Symfony, como cuando queremos usar el
servicio de encoder para las passwords.
Como buena prctica empleamos Alice dentro de DoctrineFixturesBundle de forma que tenemos lo
mejor entre la flexibilidad de escribir los fixtures usando PHP y hacerlos de forma ms expresiva
empleando un fichero yaml para usarlo con Alice.
https://github.com/nelmio/alice

Matthias Noback
Matthias (@matthiasnoback) es un programador de PHP profesional y experimentado que ha
dado unas cuantas conferencias en la comunidad de PHP. Es el autor del libro A year with
Symfony, (disponible tambin en castellano) y del blog PHP & Symfony About PHP and Symfony2
development.
Sigo su blog habitualmente con bastante inters puesto que suele tener posts relacionados con las
buenas prcticas en el uso de Symfony. Matthias ha comenzado a escribir una serie de posts sobre
su experiencia de testing con PHP y pens que tena que trasladar esto a este libro puesto que
estamos hablando de lo mismo. Me puse en contacto con Matthias y amablemente accedi a que
le entrevistase y traducir sus artculos sobre testing para este libro.
Mi interesante su visin del testing, con consejos y trucos concretos.

La experiencia de testing con PHP: entrevista a


Matthias Noback por Fernando Arconada
Quin es Mattias Noback?
Soy un desarrollador de PHP, escritor y orador. Vivo en Zeist, los pases bajos, con mi novia, un hijo
de nueve y una hija recin nacida. Actualmente tengo mi propio negocio llamado Nobacks Office.
Esto me da montn de libertad: trabajo como desarrollador en un proyecto ms o menos a alrededor
de media semana y el tiempo restante lo puedo dedicar a mi familia, a escribir en el blog, o terminar
mi segundo libro.

Cual es tu experiencia con el testing BDD/TDD?


Como escrib en los artculos introductorios sobre testing que tengo mi blog: llevo escribiendo tests
para mi cdigo desde hace un par de aos. Los tests unitarios han cambiado la forma en la que trabajo
y han hecho que el cdigo que entreg sea mucho mejor. Las clases que estn bien diseadas son
tambin fcilmente testeables y viceversa. Igualmente importante: los tests unitarios me ha ayudado
a reducir mucho la cuenta del nmero de bugs y el montn de trabajo a rehacer.
No siempre escribo los tests lo primero, aunque gradualmente he ido acostumbrando a escribir los
tests al principio, desde el punto en el que creo que es la nica forma de puedo garantizar que estoy
escribiendo cdigo que sirve para el propsito que yo o mi cliente tenemos en la cabeza.
https://leanpub.com/a-year-with-symfony
http://php-and-symfony.matthiasnoback.nl/
http://php-and-symfony.matthiasnoback.nl/2014/07/the-php-testing-experience-interview-by-fernando-arconada

Matthias Noback

69

Recientemente he empezado a escribir escenarios (para un proyecto privado), ejecutndolos con


BeHat. He hecho esto despus de escribir el cdigo, lo cual ha hecho que este proceso sea un poco
ms costoso. Hacer tests para un cdigo que ya existe es casi siempre es ms difcil que escribir
cdigo testeable en primer lugar. An con todo, viendo que todos los pasos en los escenarios se
ejecutan exitosamente, me da un montn de confianza de que la aplicacin hace lo que quiero en
trminos de mi propio negocio (es una herramienta para control de tiempo y auto facturacin, es
simple pero hay dinero implicado, as que debe funcionar siempre).

Algn proyecto open source del que te sientas orgulloso?


Realmente no trabajado en grandes proyectos todava, simplemente pequeas libreras. De alguna
forma estoy orgulloso de mi muestreador de Leanpub. Puedes usarlo para recorrer los ficheros de
manuscrito de un libro de Leanpub y extraer texto de ejemplo de ellos. Contiene algunos iteradores
interesantes y usa Aura.Cli como librera de lnea de comandos.

Debo escribir tests incluso para proyectos pequeos?


La razn por la que preguntas esto (eso creo) es que los tests automticos presumiblemente son
una forma de reemplazar las comprobaciones manuales. Cuando un proyecto es pequeo por qu
preocuparse escribiendo estos test automticos?. El tiempo que necesitas invertir en ellos realmente
sobrepasa el tiempo que necesitas para comprobar de forma rpida el comportamiento de tu
aplicacin usando la UI (ya sea un navegador o la lnea de comandos).
En mi experiencia los proyectos pequeos se vuelven grandes. Si quieres introducir tests por las
razones habituales, entonces te va a costar bastante tiempo aadir los tests al cdigo que ya
existe (como coment antes: hacer que el cdigo de produccin que no tiene tests sea testeable
probablemente cueste bastante tiempo). Por otro lado escribir tests para pequeos proyectos es
particularmente sencillo, mucho ms fcil que escribir tests para proyectos grandes. Y dado este
pequeo coste si t escribes tests para un proyecto pequeo te va a dar todas las ventajas habituales
del conjunto de tests automticos: - puedes asegurarte que un cambio aqu, no romper la aplicacin
all. - No necesitas confiar en la consistencia de tu propio comportamiento cuando haces las
comprobaciones manuales. Le puedes dejar esto al ordenador.

Cual es tu opinin de los proyectos PHP en general?


Mirando la comunidad de PHP de hoy en da y comparndola con la de hace un par de aos, llego
a la conclusin de que los desarrolladores de PHP hoy se preocupan mucho ms del open source.
Lo bueno del open source es: que la gente trabaja realmente duro para hacerlo bien. En particular
porque el cdigo open source que t escribes puede ser juzgado por cualquiera que mire a tu pgina
de GitHub. As que lo haces tambin como puedes (incluso aades algunos tests). En realidad hay un
montn de cdigo indocumentado y de basura difcil de testear en GitHub, pero en general (aunque
no tengo cifras que respalden mi opinin), las cosas han ido realmente mejor durante estos aos .

Matthias Noback

70

Necesitamos una cobertura del 100%?


No, casi nadie tiene una cobertura de tests unitarios del 100%. Usar un flujo de trabajo estricto de
TDD, automticamente te va llevar a tener una cobertura completa. Pero probablemente no hars
esto. Incluso si t crees que TDD es la forma ideal de desarrollar (como yo lo creo), aun as no te
agradar todo el tiempo. As que en esto se va la cobertura del 100%.

Haces tests desde el principio del proyecto?


No, no siempre. Aunque normalmente lo pienso bastante antes de decidir si lo hago o no. El cdigo
open source necesita tests porque la gente va a confiar en l. Mi propio cdigo necesita tests si yo no
confo mi mismo, poniendo el foco especialmente, si mi negocio, o parte de mi negocio depende de
esto. En otras circunstancias puedo decidir no escribir tests para mi cdigo. Por favor ten en cuenta
sin embargo que prcticamente siempre rechaz esta decisin porque no escribir tests inicialmente
me pueden hacer ir ms rpido, pero realmente me ralentizan al final.

PHPSpec, BeHat, Codeception, PHPUnit, Cules usas?


Mayormente uso PHPUnit, algunas veces incluso para los tests funcionales. Como mencionaba
anteriormente tambin uso ahora BeHat. Prov Codeception por algn tiempo pero no llegue a
sentirme bien con l (no tengo una buena razn que justifique este sentimiento negativo). He
experimentado con PHPSpec un poco, as como con Prophecy, que es la herramienta de generacin
de test doubles que emplea PHPSpec. En mi experiencia puedes usar PHPUnit como usas PHPSpec,
pero si eres nuevo en el testing, definitivamente recomiendo PHPSpec ya que te lleva ms fcilmente
en la buena direccin, cuando se trata de tres unitarios (o especificacin de objetos). Actualmente
Prophecy est muy bien y sientes que haces tus tests mejores si la usas. Personalmente utilizo
PHPUnit y el paquete que existe para unir PHPUnit y Prophecy, lo cual hace muy sencillo definir
los tests doubles con Prophecy dentro de los casos de prueba de PHPUnit.

Hay alguna otra herramienta de control de calidad que suelas


usar?
Uso herramientas para verificar los estndares de codificacin de un proyecto (como por ejemplo
PHP_CodeSniffer). Para libreras open source uso Scrutinizer CI, que es un servicio de integracin
continua para proyectos PHP. Para cada commit genera algunas grficas sobre la calidad del cdigo
e indica formas en las que puedes mejorarlo. Puedes trazar el estado del proyecto durante el tiempo.
Ofrece algunas vistas interesantes de tu cdigo y si t sigues (mayormente) sus consejos, tu proyecto
acabar siendo mejor que como empez.

Matthias Noback

71

Est muerto el TDD? Y el BDD?


No, no creo que el TDD este muerto. Creo que hacer todo tests desde el principio est muerto (al
menos en lo que se refiere a los tres unitarios), o ms bien nunca estuvo vivo, excepto en los coding
dojos. Lo que empez con el TDD est muerto fue una discusin en un post de David Hansson.
Aunque creo que el seguramente no abandon TDD por las razones correctas, dice algunas cosas
interesantes y honestas sobre su experiencia con TDD:
Algunas veces soy absorbido por un vrtex de fundamentalismo, sintindome mal por
no seguir el verdadero evangelio. [] Era como un ciclo yoy de orgullo, cuando era
capaz de serguir al pie de la letra las enseanzas, y un golpe de desesperacin, cuando no
poda. Senta como si me cayese del vagn. Algo sobre lo que mantenerme en silencio.
Ciertamente algo para no admitir en pblico.
Al principio no poda creerlo. Yo mismo soy un fuerte defensor y profesor de la forma correcta,
que es hacer tests desde el principio o un TDD estricto. Despus de simplemente leer el ttulo de
su post yo estaba cegado por un sentimiento de oposicin contra la opinin de David. Cuando mi
revuelta inicial desapareci, esto cambi. Entr en una discusin de Twitter (o pelea) con Manuel
Lemos, lder de la comunidad PHPClasess, despus de que el escribiese el artculo Por qu TDD
fallo en volverse popular. Creo que el artculo en s mismo es bastante peligroso para la comunidad
de PHP y en concreto para la comunidad de PHPClasses, sin embargo estoy de acuerdo en una cosa.
l dice (con el mismo espritu que David):
Mucha gente sigui a los predicadores de TDD para ms tarde darse cuenta que no
era lo apropiado para ellos. Todava algunos no se atreven a admitirlo y nunca se han
arrepentido abiertamente.
Ahora entiendo mejor tanto a David como a Manuel. Predicar es realmente una cosa peligrosa.
Vociferar cosas como qu, qu no escribes tests? es incluso ms peligroso. Para los recin llegados
al testing esto puede ser muy intimidatorio. Ellos pensarn que estn haciendo algo muy mal. No
se atrevern a admitir que no estn escribiendo tests, por qu: quin sera semejante pecador como
para no escribir al menos algn test?. Esto es actualmente uno de los defectos del mayor defensor
de los tres unitarios en PHP Chris Hartjes. Como el Grumpy programmer (programador grun)
representa un enfadado alter ego en Twitter. El programador grun, o el tester protestn, se enfadar
si no escribes tests para tu cdigo.
En la vida real, Chris es una persona muy agradable (no lo conozco en persona, pero conozco sus
informes como asistente) y ha escrito algunos libros tiles sobre tests unitarios que venden bien,
lo cual es bueno para difundir la cobertura de los tests. A pesar de todo, hay un poco de peligro
en la aproximacin de Chris: alguien cabrendose contigo por no escribir tres definitivamente no
es una buena razn para empezar a escribir tests. En realidad, no deberas avergonzarte de tu falta
de experiencia de testing. En realidad deberas estar motivado para hablar sobre testing, e intentar
buscarte algn mentor. Se que Chris estar de acuerdo conmigo, que es lo que realmente me encanta
de un tweet reciente de Cris:

Matthias Noback

72

Tal vez, en lugar de vete de mi jardn debo comenzar diciendo ven y sintate en el
porche conmigo.
Por que estoy contando todo esto? Bien, por diferentes razones. Lo primero, TDD no es lo mismo
que testing (quizs hayas advertido que yo ya he hecho un gran salto conceptual entre estas dos
palabras en los prrafos anteriores). Esto significa que TDD puede estar muerto, pero al mismo
tiempo el testing puede estar muy vivo (lo que creo que es el caso). De todas formas, TDD es una
tcnica que necesitas dominar como desarrollador y deberas aprenderlo. Pero no puedes aprender
sobre testing de otros desarrolladores que te hacen sentir avergonzado por algo que todava no has
aprendido (o no lo has hecho bien). La solucin? Todos los testers experimentados en el mundo
deberan volverse mentores de alguien que sea un novato en el testing. As que esta es mi llamada
de atencin:
Si conoces a alguien que no sabe cmo escribir tests, o no lo hace, ensale.
Y si t eres ese alguien:
Encuentra alguien que te ensee cmo escribir mejores tests que los que haces y
pregntale para qu te ensee cmo lo hace.

Tienes un consejo para los recin llegados?


Estamos acostumbrado a lanzarnos rpidamente a por algo. De alguna forma nos tiramos de cabeza
a nadar sin tan siquiera pensar en guardar la ropa. Si somos moderadamente buenos programando
quizas no ocurran accidentes. Pero slamente despus de aprender a escribir los tests previamente
nos daremos cuenta de los pequeos accidentes que hemos causado en el pasado escribiendo
cdigo, refrescnado continuamente la pgina en el navegador o aadiendo var_dump tras cada lnea.
Siguiendo este flujo de trabajo se introduce un nmero incontable de bugs, y se invierten cientos de
horas preguntndonos por qu algo no funciona. Todo el debug se produce en el lugar equivocado
(dentro de al aplicacin), cuando en realidad escribiendo algunos tests unitarios segmentaremos las
unidades a debuggear y sers capaz de resolver el problema mucho antes.
Adems de escribir tests unitarios, recomiendo encarecidamente instalar XDebug en tu ordenador.
Inmediatamente dejars de perder el tiempo con trazas futiles y errores sin sentido. Y entonces, como
ya te he dicho: encuentra un tutor que te ensee a escribir mejores tests.

Hay algn libro, post o artcuo que te haya abierto los ojos?
Bien, he aprendido un montn de Growing Object-Oriented Software, Guided by Tests. Bridging
the Communication Gap: Specification by Example and Agile Acceptance Testing tambin es
una lectura interesante. Algunos artculos de Robert Martin contienen un montn de reflexiones
http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627/ref=pd_sim_b_6?ie=UTF8&refRID=
0WBMXN6M4DA6H3FP0XMR
http://www.amazon.com/Bridging-Communication-Gap-Specification-Acceptance/dp/0955683610/ref=sr_1_fkmr0_1?s=books&ie=
UTF8&qid=1405161330&sr=1-1-fkmr0&keywords=Gojko+Ad%C5%BEi%C4%87
http://blog.8thlight.com/uncle-bob/archive.html

Matthias Noback

73

interesantes sobre testing.

Escribes tests de los tests?


No, sin embargo, dependiendo de la complejidad de tus objetos fake (que es una clase especial de
test double), puede ser aconsejable escribir un test para ellos tambin. Algunas veces contienen
cierta lgica que por si misma puede introducir bugs en los casos de test. Para evitar escribir tests
para los tests no uses ningn tipo de cdigo complejo (como por ejemplo for o if) en los casos de
test. La lgica de programacin es susceptible de generar problemas (incluso si crees que eres un
programador avanzado), as que un caso de test no es el lugar ms apropiado para ello. En vez de
esto, mueve este cdigo complejo fuera del caso de test y escribe tambin un test para ello. Esto te
permitir utilizar este cdigo en tus tests con confianza.

Por qu todo los tests acaban siendo cdigo spaguetti?


Hay dos situaciones en las que la gente tiende a escribir cdigo menos limpio de lo que suele: dentro
de las clases de test o cuando usan por primera vez un micro-framework. Aunque esto sucede por
distintas razones: la gente est ms preparada para escribir cdigo de produccin que cdigo de test.
Los tests necesitan esctuctura y tambin hay que refactorizar esa estructura, pero en mi experiencia
slo te enfrentas a cambiar esa estructura cuando sabes cmo funciona realmente algo. En otras
palabras: cuando te sientes libre de mover las cosas de sitio. En general la gente se siente ms libre y
con ms facilidad cuando trabajan con cdigo de produccin que cdigo de test. Esto podra explicar
por qu el cdigo de test acaba siendo spaguetti.
El remedio es forzarse a limpiar el cdigo de test, de la misma forma que limpiamos el cdigo de
produccin, siguiendo los mismos pequeos pasos de refactorizacin. Tambin ayuda tener alguna
clase de revisin por parte de un compaero. Si el revisor no es capaz de leer rpidamente el test
y entender lo que hace, es tiempo de refactorizarlo. Por otro lado, tu mismo puedes ser el revisor,
dejando pasar unas semanas.

Qu piensas de escribir los tests despus del cdigo de


produccin?
Bien, esto sucede todo el tiempo y es algo bastante aceptable. Yo mismo suelo hacerlo cuando he
hecho test de pequeas partes del cdigo pero no he probado el sistema al que pertenecen. Esta clase
de tests son ms del tipo de integracin o funcionales. Escribir un test unitario a posteriori no ayuda,
en mi opinin. Te vas a sentir ralentizado porque el cdigo no sea testeable desde el principio. Si no
tienes un test para un trozo de cdigo, entoces el mejor momento para escribirlo es cuando vayas
a refactorizar ese trozo de cdigo (o cuando aadas features). Escribir los tests desde el principio
de dar seguridad para refactorizar el cdigo de produccin. Tambin asegura que al aadir nueva
funcionalidad no influye ocultamente en la funcionalidad existente.

Pawe Jdrzejewski lider del proyecto


Sylius.org
Sylius es una solucin de comercio electrnico basada en Symfony2 y que hace uso intensivo de las
prcticas de desarrollo de BDD. El proyecto ha atraido mucha atencin porque puede ser usado como
solucin de comercio electrnico completa o como componentes independientes reutilizables. An
est en sus primeras fases aunque me consta que hay tiendas en produccin montadas. A diferencia
de Magento, Sylius est ms pensado para desarrolladores puesto que hace que sea ms sencillo
personalizar una tienda.

Quien es Pawe?
Soy un emprendedor y desarrollador de software que vive en Polonia con mi adorable pareja. He
creado el proyecto Sylius y he empezado Lakion, que es una compaa provee todo tipo de servicios
comerciales.

Cual es tu experiencia con el cdigo PHP y su calidad?


Empec usando PHP a la edad de 14 y obviamente he pasado por todas las fases, desde la
programacin estructural, hasta la pseudo orientacin a objetos hasta llegar a lo que estoy haciendo
ahora. Todos sabemos que puedes llegar a escribir cdigo realmente malo con PHP, cosa que es
cierta para cualquier tipo de lenguaje de programacin. La idea de que PHP es malo de por s
viene debido a su popularidad. Es de lejos el lenguaje para la web ms empleado y hay muchos
jvenes desarrolladores sin experiencia trabajando con el. Para escribir un gran programa Java,
necesitas tener unos conocimientos slidos de programacin orientada a objetos y al menos algunos
conocimientos sobre patrones de diseo. PHP es muy fcil para empezar y esta es la razn por la
que solemos ver cdigo de baja calidad en el mundo PHP. Hay que decir que las cosas estn yendo
cada vez a mejor.

Qu piensas sobre la calidad de los proyectos PHP en


general?
Estoy realmente emocionado con la revolucin que est sucediendo en nuestra comunidad. Es
magnfico que estemos poniendo ms atencin en las metodologas de desarrollo, testing y en general
http://www.sylius.org/
http://lakion.com/

Pawe Jdrzejewski lider del proyecto Sylius.org

75

aprendiendo de otros, lenguajes ms empresariales, que estn mucho ms maduros en trminos


de orientacin a objetos.
los proyectos ms viejos todava estn luchando con un montn de cdigo heredado, pero estoy muy
contento de ver aplicaciones como Drupal o eZ Publish avanzando hacia arquitecturas ms nuevas
con Symfony y alejndose de l la forma vieja de hacer PHP. En lo que respecta a las nuevas
plataformas, como Sylius, Akeneo, OroCRM, el futuro de PHP se presenta brillante y no slo para
estos grandes programas, veo una gran cultura de testing aflorando entre mantenedores de pequeas
libreras.

Qu es Sylius?
Sylius es un proyecto completamente open source (con licencia MIT) de plataforma de comercio
electrnico para PHP y que intenta ser la solucin definitiva para desarrolladores trabajando en
plataformas de venta online.

Es Sylius una aplicacin como Magento, PrestaShop,


etc o es un framework?
Es ambas cosas. Debido a su arquitectura, puede ser usado como framework. Nuestro objetivo es
hacer una base todava mejor y terminar con una plataforma completa de comercio electrnico. Est
construido a partir de componentes desacoplados y bundles. Finalmente, es una aplicacin Symfony
completa y debera resultarle familiar a cualquier desarrollador que suela trabajar con Symfony.

Cuntas personas hay colaborando en Sylius?


Soy el desarrollador principal y trabajo con un increble equipo en el ncleo, que consiste en siete
personas esparcidas por todo el planeta, desde San Diego a Singapur. Tenemos un creciente nmero
de contribuidores, alrededor de unas 150 personas han enviado parches y cerca de otras 190 trabajan
en las traducciones. Tambin tenemos un montn de simpatizantes enviando reportes de fallo,
aportando feedback y promoviendo el proyecto en sus comunidades.

Sueles hacer BDD, TDD o algo parecido? Qu significa


esto en tu flujo de trabajo?
Mi primer contacto con el testing fue, obviamente, usando PHPUnit. Es una gran herramienta y la he
usado mucho, pero en algn momento se estrell contra un muro. Quera cambiar mi flujo de trabajo
completo y necesitaba una herramienta que soportarse este tipo de cambio. Tuve bastante suerte al

Pawe Jdrzejewski lider del proyecto Sylius.org

76

encontrarme con gente como Kostantin Kudryshov, que me inspir para sumergirme en el BDD y
volverme un apasionado de la calidad. Lo hice y ahora no puedo volver atrs. En mi trabajo siempre
uso BeHat y PHPSpec para prcticamente todo y con grandes resultados. Todo comienza escribiendo
una funcionalidad y describirla usando Gherkin, viendo el color rojo y entonces escribiendo las
especificaciones en cdigo para soportar esa funcionalidad. Tan pronto como veo el color verde,
podemos refactorizar el cdigo para hacerlo ms elegante y continuar. Algunas veces me encuentro
a mi mismo implementando una funcionalidad al completo sin tan siquiera abrir un navegador.

Sinceramente, empezaste haciendo testing desde el


principio de Sylius?
No, los primeros bits del cdigo de Sylius fueron escritos all por 2011 y eran simplemente un
pequeo conjunto de bundles de Symfony. En algn momento, cre una aplicacin de prueba que
serva como ejemplo de integracin de los componentes de comercio electrnico dentro de una
aplicacin. En aquel momento slo tena unos pocos tests unitarios y eso era todo. Cuando decid
abandonar la aplicacin de prueba y empezar a trabajar en una plataforma completa de comercio
electrnico basada en Symfony, quise hacerlo bien con SpecBdd y StoryBDD. Desde entonces, Sylius
ha cambiado mucho de la mayora del cdigo que tu ves desarrollado con TDD.

Cmo puedo ejecutar los tests de Sylius?


Para ejecutar las especificaciones de PHPSpec simplemente necesitas clonar el proyecto, instalar los
vendors con Composer y ejecutar un simple comando:
1

$bin/phpspec run -fpretty

Los escenarios de BeHat, requieren inicializar la base de datos, pero una vez que lo has hecho los
puedes ejecutar simplemente con:
1

$ bin/behat --suite=account

Tus colaboradores siguen el BDD?


Nuestra gua para contribuidores tiene una parte dedicada al TDD y mucha gente actualmente
contribuye siguiendo la metodologa apropiada. Por supuesto, algunas veces la gente simplemente
escribe el cdigo, pero entonces nosotros les ayudamos hacer el parche merge-ready con una revisin
intensiva del cdigo y discutindolo. Usamos escenarios con Gherkin como forma de comunicar y
explicar los requerimientos de las nuevas funcionalidades o incluso reproducir fallos.

Pawe Jdrzejewski lider del proyecto Sylius.org

77

100% de cobertura de cdigo?


Cuando usas PHPSpec y BDD, piensas en el cdigo y diseo de tus clases antes de escribirlo. La gente
que usa PHPUnit a menudo llama a los mtodos de test despus del nombre del mtodo de la clase
de test que se supone que estn probando. Con nuestra aproximacin, describes el comportamiento
completo de la clase y la cobertura del cdigo no tiene mucho sentido. Si realmente haces BDD, todo
est cubierto. Creo que Marcello Duarte lo explica mejor en su respuesta a una pregunta surgida
en StackOverflow al respecto de los informes de cobertura en PHPSpec

Puedes recomendar un libro, post o artculo


relacionado con la calidad del cdigo que haya
cambiado tu forma de pensar?
En mi caso fue la gente la que me expir a introducirme y poner el foco en el testing y el
diseo emergente en mi flujo de trabajo. Creo que no es una transformacin que nos sucede de la
noche a la maana, es tu evolucin como programador profesional. Puedo recomendar ver algunas
presentaciones inspiradoras:
Marcello Duarte - Pair Programming, TDD and Other Impractical Things
Konstantin Kudryashov - Behat by example

Por qu PHPSpec y BeHat? Cuntanos lo mejor y lo


peor de estas herramientas
Estas son las herramientas que soportan un flujo de trabajo, que en mi opinin es el que mejor
funciona para mi: BDD. Parecen un poco complicadas para alguien nuevo en el testing y en el BDD,
pero han estado ah fuera por mucho tiempo, mientras t pones el foco en el diseo del cdigo
y los requerimientos de negocio. Te enamorars de PHPSpec, cuando te des cuenta que te ayuda a
disear mejor el cdigo y que el testing es simplemente un efecto colateral. Mi caracterstica favorita
es que PHPSpec usa la frustracin para dirigirte a una mejor aproximacin. Simplemente no te deja
hacer ciertas cosas o te hace muy difcil conseguirlas mostrndote que lo que t estas intentando
de implementar no es el mejor diseo. Hacer mocks con Prophecy es a su vez magnifico y simple.
Realmente, me cuesta pensar en cosas negativas sobre esta herramienta.
Escribir escenarios con BeHat es una gran mejora en la comunicacin entre todas las partes interesadas y tambin funciona perfectamente con los proyectos open source. StoryBDD en general, permite
http://stackoverflow.com/questions/11783854/phpspec-and-coverage-report
https://www.youtube.com/watch?v=XcGlVB7MBos
http://youtu.be/QnPmbQbsTV0

Pawe Jdrzejewski lider del proyecto Sylius.org

78

que todo el mundo se encuentre en la misma pgina sobre los requerimientos de negocio. Slo los
tontos pensarn que escribir cdigo es la parte ms difcil de nuestro trabajo. Lo es comprender
y enfocarnos en las funcionalidades que debemos de entregar, que ofrezcan el mayor valor para
nuestro cliente. Desde un punto de vista tcnico, BeHat es una herramienta que permite trabajar
realmente bien con esto. Si t tienes un montn de escenarios y pasos complejos, el rendimiento
puede ser un problema, pero esto slo afecta a conjuntos muy grandes de funcionalidades. Con la
versin tres de BeHat, hay una mejora significativa y cuando la ejecucin en paralelo llegue a estar
estable entonces el problema debera estar bastante bien solucionado.

Por qu no Codeception o PHPUnit?


PHPUnit es una gran herramienta y todos deberamos estarle agradecidos por ser el pionero de
la cultura de testing en el mundo de PHP. He disfrutado mucho usandola, pero necesitamos
herramientas diferentes, que me ayuden a transformar mi flujo de trabajo en una aproximacin
mejor de TDD. Nunca he usado Codeception y no conozco realmente mucho sobre como funciona
internamente. Recuerdo que usaba una sintaxis algo extraa para escribir los tests, una mezcla de
lenguaje de Gherkin pero con trozos de cdigo, que en mi opinin mata el objetivo de poder emplear
un lenguaje entendible de negocio.

Est muerto el TDD?


Creo que la discusin alrededor de este tema ha sido buena y espero que haya ayudado a alguna
gente a estructurar sus propias opiniones al respecto del testing. Obviamente mi respuesta es no,
porque estoy usando TDD con grandes resultados y veo a mucha gente alrededor mo beneficindose
tambin de esta metodologa. Creo que se pierde totalmente el norte si simplemente tienes ms lneas
de tests y escribes los test simplemente por el hecho de tener una cobertura del 100%. Para mi TDD
es una forma de disear mis clases, pensando en el cdigo y escribiendo el cdigo que le importa a
la gente que lo usara. Creo que BDD es la forma de mantener el foco en la direccin adecuada.

De PHPSpec + Behat a Codeception


Durante un tiempo he estado usando PhpSpec para la especificacin de mis clases y Behat para hacer
BDD. En mi ltimo proyecto, que apenas estaba iniciado, he decidido pasar desde estas soluciones
de testing a Codeception.
Codepcetion es una herramienta de testing todo en uno, que sive para hacer testing unitario,
funcional y de aceptacin. Puede usar una sintaxis al estilo de PhpUnit (lo emplea por debajo) o
tipo spec, como PhpSpec pero ms similar a Jasmine. La sintaxis de BDD de codeception no se
basa en Gherkin y est ms cercana a un lenguaje de programacin.
Dicho esto, y visto que al final podemos hacer testing de la misma forma, paso a explicar por puntos
mi experiencia en este proyecto concreto.

Nivel humano
Este proyecto lo desarrollo con otras dos personas, una es un programador que ya tena experiencia
con Codeception y con otra, que aunque no tiene un perfil tcnico, es capaz de leer pequeos
fragmentos de cdigo.
BDD trata sobre comunicacin y de expresar en un lenguaje comn de alto nivel y claro las
especificaciones que debe cumplir el software. Estas especificaciones deben poder comprobarse de
forma automtica. Como el software no es para ningn cliente externo, y entre los 3 somos capaces
de leer bien los test de aceptacin, estamos cumpliendo el objetivo de la comunicacin. El lenguaje,
an siendo PHP, resulta bastante claro de leer y la salida por pantalla es muy legible.
1
2
3
4
5
6

<?php
$I = new AcceptanceTester($scenario);
$I->am('Account Holder');
$I->wantTo('withdraw cash from an ATM');
$I->lookForwardTo('get money when the bank is closed');
?>

Ciertamente es ms legible el texto en Gherkin, pero no resulta un problema leer en PHP.


http://codeception.com/
http://codeception.com/docs/06-UnitTests
https://github.com/Codeception/Specify
http://jasmine.github.io/
http://codeception.com/docs/04-AcceptanceTests
http://docs.behat.org/en/latest/guides/1.gherkin.html

De PHPSpec + Behat a Codeception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

80

Feature: ls
In order to see the directory structure
As a UNIX user
I need to be able to list the current directory's contents
Scenario:
Given I am in a directory "test"
And I have a file named "foo"
And I have a file named "bar"
When I run "ls"
Then I should get:
"""
bar
foo
"""

Comodidad del lenguaje


Las especificaciones escritas en Behat son ms legibles que el cdigo en PHP aunque sea similar.
Adems, las especificaciones de Behat son ms cmodas de escribir a priori y sto es importante si
estamos siguiendo lo que sea driven development.
Ya me va sucediendo varias veces que cuando escribo una especificacin necesito (o al menos eso me
pide el cuerpo) usar variables y bucles. Con Behat no he conseguido hacer esto de manera cmoda.
Especialmente cuando la variable es compleja (un objeto) y acabo haciendo mtodos complejos en
los context de Behat. Con Codeception sto no es un problema porque es PHP.
Es posible que alguien me replique que no se deberan usar variables complejas (objetos), puesto que
a travs de un navegador no obtienes este tipo de variables. Pero si quiero hacer que mis tests sean
un poco ms independientes de los otros, no quiero vincularme completamente a hacer los tests a
travs del navegador.
Hasta ahora me he referido a la comodidad del lenguaje referido a Gherkin Vs Codeception. Con
PhpSpec me pasa un poco lo contrario, es mucho ms cmodo y sencillo escribir las aserciones que
con PhpUnit
1

$this->shouldHaveType('Movie');

En PhpUnit sera algo as:


1

$this->assertInstanceOf('Movie');

De PHPSpec + Behat a Codeception

81

Tampoco es una gran diferencia, pero resulta ms cmodo con PhpSpec. Adems, cuando se genera
algn error de PHP, se gestiona mejor con PhpSpec.
En la versin 4.5 de PhpUnit se incorpora Prophecy como framework de mocking de objetos, que es
el mismo que usa PhpSpec, as que no hay diferencia a la hora de crear mocks entre Codeception y
PhpSpec

Documentacin
PhpSpec y Behat son bastante nuevos y a la hora de buscar documentacin se nota. Hay mucha gente
que los est usando pero falta una documentacin ms extensa y avanzada. Hay miles de ejemplos
sencillos pero cuando avanzas te sientes un poco perdido.
La documentacin de Codeception est bastante bien, ms o menos al nivel de la de PhpSpec y Behat,
pero como los tests unitarios los haces con PhpUnit y ste es todo un veterano, no tienes problema en
encontrar lo que necesites. Con la parte de los tests de aceptacin, al estar escritos en PHP normal,
resulta ms sencillo poder buscarte la vida.

Extensibilidad
Se me ha dado el caso de querer hacer unos tests muy parecidos para diferentes clases con PhpSpec.
Como los tests tambin es algo que hay que mantener, he querido usar traits (si, no pasa nada por
usar traits, no vas al infierno de los programadores). Habl con el autor de PhpSpec al respecto y
aunque reconoci que para mi caso podan ser necesarios, me confirm que no poda usarlos por
temas de la carga de clases, as que acab haciendo copy/paste de unas cuantas lneas de cdigo.
Con Codeceptin no sucede este problema. Desde el principio te explican en la documentacin dnde
debes incluir tus mtodos y clases para poder reutilizar cdigo.
La experiencia con Behat ha sido parececida en este sentido.

No hago lo que sea driven development


Despus de un tiempo escribiendo tests tengo que reconocer que no hago TDD ni BDD ni xDD. Al
menos no lo hago todo el tiempo. Eso no quiere decir que no escriba tests ni que no dedique un
tiempo importante a pensar lo que quiero que hagan mis clases.
La mayora de las veces escribo un mtodo de una clase y acto seguido escribo su test y no por esto
considero que mi funcionalidad est peor probada que si hubiese escrito el test primero. Consigo
igualmente el objetivo de tener un sistema de verificacin automtica.
Alguna vez escribo los tests antes, sobre todo en el caso de los tests de aceptacin.

De PHPSpec + Behat a Codeception

82

Behat y PhpSpec me han resultado mucho ms cmodos que Codeception para escribir los tests a
priori. Behat porque el lenguaje Gherkin es casi como escribir en castellano lo que quieres hacer y
PhpSpec porque te ayuda con la creacin de las clases y mtodos que debes implementar crendolos
por ti a partir los tests.
En este sentido Codeceptin es ms incmodo si vas a hacer lo que sea driven development, pero me
ha resultado el ms cmodo al escribir los tests a posteriori.

Tests funcionales
Este es el punto clave. En un test de aceptacin se interacta completamente a travs de un
navegador. Por lo tanto no hay posibilidad de manejar variables tipo objeto ni debes tener aserciones
sobre aspectos tcnicos como un objeto de base de datos. Por ejemplo, en un test funcional usas un
falso navegador y compruebas el resultado de tu accin mirando en la base de datos.
En este proyecto no me encargo del frontend y adems es algo que va cambiando. Me resulta difcil
escribir test que busquen un id de html, o cadenas de texto en el navegador. No me gusta estar
cambiando los tests cada vez que alguien decide que un texto pasa de ser un listado de tags a un
listado de categoras o porque la persona que gestiona el frontend ha decidido cambiar el id. A fin
de cuentas mi funcionalidad no ha cambiado en absoluto.
Codeception tiene un mdulo de Symfony que nos permite acceder al contenedor de dependencias
y al kernel de forma sencilla resultando fcil hacer comprobaciones de BD, emails etc.
Behat tiene una extensin para Symfony que tambin nos permite acceder al kernel de una
aplicacin Symfony. Lo que sucede es que su utilidad no la veo tan clara puesto que la interaccin
de los tests de aceptacin debe ser a traves del navegador. Viene bien, por ejemplo, para saber si se
ha enviado un mail o simular un login de usuario modificando la sesin directamente.

Es Codeception la solucin definitiva?


Yo creo que no. No existen las soluciones definitivas, hay muchos tonos de gris entre el blanco y
el negro. En este proyecto estoy contento con Codeception y creo que estoy siendo ms productivo
que con Behat y PhpSpec. Considero que mi proyecto est bien testeado y que la comunicacin a
traves de los tests es buena. Hay cosas que echo de menos de PhpSpec y Behat. Concretamente que
los tests son ms legibles.
Me resulta muy cmodo emplear una sola herramienta en vez de herramientas separadas. Incluso
hay veces que usando PhpSpec y Behat me he visto empujado a usar PhpUnit para tests funcionales.
Ahora lo tengo todo mucho ms sencillo.
http://codeception.com/docs/05-FunctionalTests#Symfony2
https://github.com/Behat/Symfony2Extension/blob/master/doc/index.rst

De PHPSpec + Behat a Codeception

83

Si hago otro proyecto en el que fuese a hacer BDD/TDD estricto desde el principio, seguramente
volvera a usar PhpSpec y Behat. Si tuviera un proyecto en el que el cliente fuese a leerse los tests
de aceptacin y a colaborar en su creacin, usara Behat.
No s cmo encajan estas soluciones de testing con toda la revolucin de frontend que tenemos hoy
en da. Tenemos verdaderas aplicaciones en frontend hechas con Javascript y que a su vez usan sus
propios frameworks de testing.

You might also like