Introducción

Durante mucho tiempo estuve del lado de los que ni querían mirar, ni saber nada sobre unit testing bajo el pretexto de que había que escribir mucho más código.
En una oportunidad, colaborando con la gente de Sheetsu, me hicieron el pedido de realizar todos los unit tests para la librería que cree y a la cual le doy soporte para trabajar con su API en PHP y así fue como decidí sumergirme por completo en el mundo del Unit Testing.

Espera, ¿Unit qué?

Si bien este artículo no pretende ahondar en los why’s del unit testing (dado que hay muchísimo material al respecto en la web), vamos a tratar de establecer un conocimiento en común para todos rápidamente.

¡Poderes de wikipedia, vengan a mi!

En programación, una prueba unitaria es una forma de comprobar el correcto funcionamiento de una unidad de código. Por ejemplo en diseño estructurado o en diseño funcional una función o un procedimiento, en diseño orientado a objetos una clase. Esto sirve para asegurar que cada unidad funcione correctamente y eficientemente por separado. Además de verificar que el código hace lo que tiene que hacer, verificamos que sea correcto el nombre, los nombres y tipos de los parámetros, el tipo de lo que se devuelve, que si el estado inicial es válido entonces el estado final es válido.

Básicamente lo que nos dice este párrafo es que cada unit test nos permite corroborar que la unidad de código, o unidad funcional, este correctamente definida, no tenga errores de sintaxis, y realice las operaciones esperadas. A medida que nos adentremos en esta serie de artículos tengo por seguro que irás entendiendo el porqué es fundamental realizar los unit tests (tal como lo comprendí yo en su momento) y no será siquiera necesario explicar los beneficios de tal práctica.

Y PHPUnit, ¿qué es?

PHPUnit es un framework que nos brinda todo lo necesario para montar nuestros unit tests, podemos hacer comparaciones de valores, imitar a otros objetos e inclusive nos permite y nos asiste en tener un enfoque de desarrollo enfocado en los tests, llamado test-driven development y mucho más (ya entraremos en todos estos conceptos, así que no te preocupes si aún no te queda claro de qué estoy hablando)

Antes de empezar

Este artículo presume que tienes conocimientos de POO y cierta experiencia con el lenguaje en cuestión. A su vez, también supone que tienes mínimamente PHP5.6 instalado junto a Composer, la super herramienta estrella para manejo de dependencias hoy día en el mundo PHP.
Si no posees conocimiento en programación orientada a objetos y/o posees poca experiencia en PHP, te aconsejaría que primero ahondes por allí y luego vuelvas recién a este artículo.
Si no tienes PHP5.6 o no tienes Composer instalado, ve a instalar todo y dejar tu entorno de desarrollo listo para poner manos a la obra!

Todo el código y la implementación que veremos está en PHP5, esto es porque para la librería que usaremos de caso testigo era necesario mantener la compatibilidad con esta versión, sino mi consejo es que TODOS YA se pasen a PHP7 y dejen que la versión 5 caiga en el oblivion (guiño para los amantes de The Elder’s Scroll)


Instalando PHPUnit

Gracias a la magia de Composer instalar PHPUnit es MUY fácil, las opciones son dos:
Editar el archivo composer.json y agregar esta línea

"require-dev": {
"phpunit/phpunit": "5.7"
},

Luego de esto debes ejecutar el siguiente comando en tu terminal, que actualizará únicamente las dependencias para tu entorno de desarrollo que están incluídas dentro del atributo require-dev del objeto json que tienes

composer update --dev

Otra opción es ejecutando el siguiente comando

composer require phpunit/phpunit:5.7

Este comando agregará PHPUnit a tu archivo composer.json y lo instalará todo en un solo paso.
Tu archivo composer.json finalmente debería verse así:

Además de la dependencia de PHPUnit, en este caso establecimos como requerido una versión de php 5.6 y en el caso de la librería para Sheetsu, estamos utilizando cURL por lo que también está indicada esta dependencia

Ahora si, manos en el código

Perdón, admito que ese subtítulo fue engañoso, todavía no vamos a tocar nada de código puesto que lo primero que tenemos que hacer, es pensar y plantear una estructura de carpetas y archivos que nos faciliten y ordenen el trabajo.
Como convención, PHPUnit sugiere que todos nuestros unit test estén separados del código, lo recomendable es colocarlos todos dentro de una carpeta tests en la raíz de nuestro proyecto.

Además de crear la carpeta, tenemos que recordar indicarle a Composer los namespaces que elijamos, de modo que cuando nos genere los autoloaders para las clases todo funcione en orden.

En este caso nuestro namespace raíz es el nombre del servicio (Sheetsu) por lo cual los unit test los ubicamos en el namespace Sheetsu\Tests y le indicamos la ruta relativa al archivo composer.json, es decir, a la raíz de nuestro proyecto

De este modo, nuestra estructura de archivos para esta librería, queda así:

src/
tests/
vendor/
composer.json

Configuración de PHPUnit desde un .xml

Cada unit test puede ser ejecutado por separado, e inclusive desde la consola podemos indicar que test queremos ejecutar. Por lo general (excepto que estemos armando la suite para un proyecto MUY grande) lo más cómodo será correr todos los tests juntos, y para esto, podemos crear un xml e indicarle a PHPUnit donde está nuestra Test Suite

Si fueron perspicaces, se darán cuenta que podemos definir n cantidad de test suite’s, lo que nos permite crear una configuración granulada, muy útil sobre todo en proyectos grandes. Si necesitaras agregar otra, solo la agregas de este modo:

La configuración de PHPUnit es amplia y aquí puedes tener una referencia completa pero los dos puntos importantes aquí son 2, el primero que mencionamos es donde le indicamos nuestro test suite y su ubicación en el directorio:

<testsuite name="Sheets Library Test Suite">
<directory>./tests/</directory>
</testsuite>

Y por otro lado esto:

<phpunit colors="true">

Le indica a PHPUnit que nos muestre los resultados de los unit test separados por color. De este modo, cuando corramos los tests, podremos ver los mismos que pasaron bien en verde y los que no pasaron en rojo.

Convenciones

Como mencione al principio, PHPUnit posee algunas cuantas convenciones y antes de comenzar con nuestro primer test, vamos a repasar las que creo más convenientes seguir de forma más o menos estricta.
Cada archivo que tengas en tu código tiene que tener su equivalente en la test suite, y toda la estructura de carpetas debe ser idéntica a la de tu código:

En este caso, como las interfaces no pueden ser testeadas directamente, esta sería la única carpeta que no debe estar en nuestra carpeta tests

Clases

Cada clase debe tener el mismo nombre que el código original y debe tener como sufijo la palabra Test, siempre utilizando Camel Case (en algún lugar puede que lo veas nombrado como Studly Case, sobretodo cuando la primer letra está en mayúscula. Laravel, un framework muy popular de php, hace esta distinción de modo que quedan así: camelCase y StudlyCase).

Métodos

La única convención estricta que tienen los métodos es que deben comenzar su nombre con la palabra “test” y luego, en CamelCase, el resto del nombre del método.

Además, es buena práctica utilizar parte del nombre original del método que está siendo testeado en tu test, como así también agregar cualquier otro detalle que tenga que ver con lo que se está testeando. Cuando te encuentras testeando una codebase grande, con muchos tests, vas a agradecer que los métodos de las test suite’s sean claros, explicativos y que no sean nombres pensados para ahorrar caractéres.

En nuestro caso de estudio, la clase Response posee un método “getModel” que, a partir de una respuesta obtenida de la API (es un objeto json) debe poder devolver un objeto tipo Model. De modo que el método de la test suite que lo testea es el siguiente:

public function testGetModelGetsAModelObjectFromHttpResponse()

Como verán, el nombre es increíblemente explícito, en los test’s no es lugar para ahorrar caracteres ni para volverse creativos con los nombres, sean explícitos y claros. Aquí no queda duda de qué está testeando este método, para los que no saben inglés, traduzco de forma aproximada “testear que getModel obtiene un objeto model de la respuesta http”.

¿Algo más antes de empezar?

Solo nos quedan dos cosas antes de poder sumergirnos en el código, cada método de las clases de testeo debe ser público, y cada clase de testeo debe extender a “PHPUnit_Framework_TestCase”.
Además, los métodos que vayamos a testear han de ser públicos también.

Y ahora sí, nuestro primer test… *tambores suenan de fondo*

En el caso de la librería de Sheetsu que estamos utilizando como caso de estudio, lo más sencillo era comenzar por la clase que está más al fondo en los niveles de abstracción. En este caso le toca a la clase Connection, que funciona como un wrapper para cURL. Repasemos las responsabilidades de esta clase:

  1. Setear una configuración para la conexión en atributos del objeto Connection instanciado
  2. Preparar las llamadas en función de la configuración recibida
  3. Realizar las llamadas
  4. Devolver un objeto Response con la devolución de la llamada

¿Ya se les ocurre qué es lo que deberíamos testear aquí? Los dejo pensar unos segundos…

  1. Testear que la configuración recibida se setee en el objeto Connection
  2. Testear que se prepare la llamada de forma correcta en función de las variaciones de configuraciones que puedan haber
  3. Testear que efectivamente se haga la llamada
  4. Testear que estemos devolviendo un objeto tipo Response válido

Como verán, para cada responsabilidad tenemos un test case. Lo más probable es que este sea el mayor de los casos, pero a veces tenemos más (ya que una responsabilidad puede estar expresada en más de un método en la clase).

Antes de sumergirnos en los test’s propiamente dichos, aquí tienen la API de la clase Connection que es lo que estaremos testeando (por las dudas, cuando me refiero a API me refiero a la interfaz pública de la clase que es lo que se puede utilizar y por tanto testear)

Solo están los métodos públicos. Esto es para que el gist no sea inmenso, abajo de todo en el artículo está el link al repositorio en github donde está todo el code base de esta librería con licencia GNU, asi que pueden usarla como gusten!

¿Y cómo se testea entonces?

PHPUnit nos deja hacer algo fabuloso, a grandes rasgos y ahora que recién estamos empezando, el framework nos deja hacer diversas comparaciones o asserts entre los valores esperados, que nosotros suponemos el método testeado debe devolver, y los valores realmente entregados.

Esto lo hace a través de una serie de métodos fabulosos llamados “assert”, hay una gran cantidad de ellos (son más de 20) pero la realidad es que por lo general usamos algunos pocos. De este modo, si nuestro método debe devolver un valor booleano (true/false), podemos hacer esto:

$this->assertTrue($valorObtenidoDelMetodo);

Cada método de nuestra test suite debe tener, al menos, una llamada a uno de los métodos tipo assert.

Veamos un ejemplo concreto:

Al igual que en otros casos, el gist no contiene la clase entera para mantener los gist’s pequeños, legibles y además mantener mayor sencillez en los ejemplos.

Aquí podemos ver el método público “testConstructSetsBasicHttpAuthWhenValidConfigurationGiven” que quiere decir algo así como “testear que construct setea la autenticación de http básica cuando se le da una configuracion válida”

Es un método sencillo, pero intentemos explicarlo:

  1. Seteo una configuración en un array $config
  2. Instancio un nuevo objeto Connection y a su constructor le paso $config
  3. Utilizo el método público getConfig para obtener la configuracion del objeto
  4. Si la configuración tiene un key y un secret, significa que posee autenticación. Por tanto testeamos que key y secret esten seteados efectivamente.

La magia de este método se encuentra en el assert, el assert lo que hace es decirle al framework “Oye, estoy seguro que key & secret están seteados y por tanto esta condición es true”. Si la condición fuese a arrojar false, el test fallaría dándonos el color rojo y si pasara, veríamos el color verde.

Ahora que tenemos nuestro método listo, vamos a ejecutar en la consola los test’s y ver si pasa o no. Para esto vamos a la consola, ingresamos a la carpeta de nuestro proyecto y ejecutamos en la consola el siguiente comando:

./vendor/bin/phpunit

¡VICTORIA! He aquí nuestro banderín verde

56 tests y 73 asserts. Esta es la suite COMPLETA de test’s de la librería de Sheetsu, a uds siguiendo el ejemplo de este tutorial les arrojará 1 test y 1 assert ;)

Resumiendo, que se hizo bastante largo…

En esta primera entrega pudimos instalar PHPUnit, entendimos algunos cuantos conceptos básicos sobre qué es unit testing y que son las test suite’s. Aprendimos a configurar nuestras test suite’s desde un xml, entendimos como son las convenciones sugeridas por PHPUnit para mantener coherencia a lo largo de nuestra test suite.
Utilizamos el concepto de asserts y realizamos nuestro primer test.

En la próxima entrega ahondaremos en otros asserts muy utilizados, presentaremos el concepto de dataProvider y seguiremos completando el caso de estudio sobre las mismas dos clases para ir llevandonos cada vez más a situaciones reales de unit test.

Como es prometido, aquí pueden acceder al repositorio de github de la librería con todo completo, por si les resulta de interés ;).



Soy Emiliano Zublena, un apasionado por el desarrollo de software, me dedico al diseño y desarrollo de sistemas online con un enfoque API-First y fuertemente sustentando prácticas de test-driven development en dosocials! un grupo interdisciplinario de apasionados de la web que trabajamos de manera freelance y remota en todo el mundo. Si querés contactarme puedes hacerlo en mi linkedin y si quieres ver algo de lo que hago y publico, puedes visitar mi github.