Doctest en PHP

dna
Dana prefiere Haskell

Doctest es un módulo de Python que permite incluir tests en los comentarios (en los llamados docstring para ser exactos). Pues bien, existe una implementación para PHP en PEAR.

Para desarrollos rápidos (o ágiles que se dice últimamente), Doctest representa una buena forma de escribir tests debido a que utilizar otras herramientas típicas como PHPUnit o Simpletest requiere una metodología de primero escribir los tests y luego implementar, que está muy bien pero la curva de aprendizaje no es despreciable y tiene más sentido en proyectos de cierto recorrido. En realidad los doctest no se suelen considerar una alternativa a los tests unitarios sino una opción más centrada en la documentación y más sencilla aunque menos potente.

Instalamos la herramienta vía PEAR:

pear install -f Testing_DocTest

A continuación un ejemplo de un doctest en php.

El doctest se incluye entre unas etiquetas <code>. La salida esperada para que el test sea satisfactorio se escribe tras “// expects:”.

<?php
/**
 * 
 * mostrarNumeros(3);
 *
 * // expects:
 * //0
 * //11
 * //222
 * //3333
 * 
 *
 **/
function mostrarNumeros($num) {
   for($i=0;$i<=$num;$i++) {
      for($j=0;$j<=$i;$j++) {
         echo $i;
      }
      echo "\n";
   }
}

Para ejecutar los tests :

phpdt numeros.php.

Dará como resultado:

Processing /tmp/numeros.php
[PASS]  function mostrarNumeros

Total time    : 0.0253 sec.
Passed tests  : 1
Skipped tests : 0
Failed tests  : 0

El doctest se suele incluir en la misma cabecera de comentario de phpdoc pero lo he omitido para que quede más claro lo necesario estrictamente para Doctest.

Doctest por defecto sólo considerará que el test ha sido satisfactorio si la salida del script es exactamente la misma que la salida que hemos escrito como esperada pero ¿y si queremos un comportamiento algo más flexible ?

Para ello podemos usar flags que alteren el comportamiento:

  • NORMALIZE_WHITESPACE: se ignoran los espacios en blanco.
  • CASE_INSENSITIVE: ignorar mayúsculas/minúsculas
  • SKIP: ignorar el test
  • ELLIPSIS: permitir utilizar el comodín […]. Útil para los casos en que parte de la salida es variable

A continuación un ejemplo que usa un flag de ELLIPSIS. También sirve para ver que podemos incluir varios tests en la misma cabecera.

<?php
/**
 * 
 * // Usamos ELLIPSIS porque hay una parte de la salida que no podemos predecir
 *
 * // flags: ELLIPSIS
 * echo testString();
 * // expects:
 * // Lo siguiente no se puede predecir [...].
 *
 * 
 *
 * 
 * // Este es el mismo test pero lo hacemos para ver que podemos dividir la salida con \
 *
 * // flags: ELLIPSIS
 * echo testString();
 * // expects:
 * // Lo siguiente \
 * // no se puede \
 * // predecir [...].
 *
 * 
 *
 */
function testString()
{
    return sprintf('Lo siguiente no se puede predecir %s', microtime());
}

Si la salida esperada para un test es demasiado grande podemos utilizar “expects-file:” en lugar de “expects :”. El ejemplo de mostrarNumeros quedaría así con expects-file:

<?php
/**
 * 
 * mostrarNumeros(3);
 *
 * // expects-file: mostrarNumeros.txt
 *
 * 
 *
 **/
function mostrarNumeros($num) {
   for($i=0;$i<=$num;$i++) {
      for($j=0;$j<=$i;$j++) {
         echo $i;
      }
      echo "\n";
   }
}

En el fichero mostrarNumeros.txt incluiremos la salida en bruto, es decir, sin comentar.

Existe también la posibilidad de incluir todo el test en un fichero externo:

 * 
 * // test-file: tests/test1.doctest
 * 

Por último, existe la posibilidad de insertar cierto código a interpretar antes que todo lo demás. Esto es útil para establecer variables de entorno o para simular una petición HTTP.

/**
 * This is a file level test.
 *
 * 
 * // setup:
 * // $_ENV['OSTYPE'] = 'linux';
 * echo OS_TYPE;
 * // expects:
 * // linux
 * 
 */
define('OS_TYPE', $_ENV['OSTYPE']);

Como conclusión, los doctest son una pequeña joya de Python que por suerte ha sido portada también a PHP. Permite incrustar los pequeños scripts que probablemente ya haces para probar tus clases y te obliga a ver tus clases desde el punto de vista del usuario de las mismas, de modo que te ayudará a pensar en si la encapsulación es correcta, en si tiene demasiadas dependencias, en si hace demasiadas cosas etc.. Sirve como complemento a los tests unitarios pero también como iniciación a la escritura de tests.

Se suele decir que el código fácil de testear es propio de un software bien diseñado.

Enlaces:

http://code.google.com/p/testing-doctest/
http://pear.php.net/package/Testing_DocTest
http://en.wikipedia.org/wiki/Doctest
Para ver más ejemplos de uso, lo mejor es ver el fichero de pruebas de Testing-DocTest, disponible en el repositorio:
http://svn.php.net/repository/pear/packages/Testing_DocTest/trunk/tests/test1.php

Published by

Ivan Mosquera Paulo

Software Engineer

3 thoughts on “Doctest en PHP”

  1. Aupa Ivan!

    No tenía ni idea de que PHP soportara doctest🙂 No obstante, a mi no me gusta mucho abusar de ellos, ya que el código se ve un poco ‘sucio’ y no es tan potente… pero para proyectos pequeños / poco tiempo es una opción más que interesante.

    Gran post!

  2. Sí que es verdad que hay que intentar que sean tests de pocas líneas para que no supongan demasiado ruido.
    Además de para proyectos pequeños, lo veo interesante para meter tests en proyectos existentes con poco esfuerzo. En plan, “a ver si realmente funciona este método”, pues hago una prueba y se queda en la cabecera, así de paso está documentado. Lo veo como una mejora importante frente a no tener ningún test escrito para el proyecto.
    ¿Sueles hacer tests unitarios?
    Yo hice en su día pero en Java y me daba la impresión de que tienen más gracia si te los planteas desde el inicio. Es decir, primero te haces el test y luego vas haciendo los métodos que lo cumplan.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s