PHPUnit y buenas prácticas

Hace ya unas semanas realicé unos post sobre como instalar PHPUnit para empezar con TDD. Hace unos días me llegó desde twitter un post sobre “Ser profesional” (http://plqd.blogspot.com.es/2013/07/ser-profesional.html) En el Pepe Doval cuenta experiencias tratando con personas más o menos profesional. Uno de los puntos clave que cuenta Doval es que el mundo de la programación es muy cambiante, llegan nuevos lenguajes y tecnologías cada día, por ello lo mejor la mejor manera de ser un profesional es aplicar buenas prácticas, desde comentar bien el código, hacer buenos commit, practicar TDD. Por eso llegado a este punto voy a escribir sobre buenas prácticas con PHPUnit.

He de decir que no soy un experto en la materia, esto son solo una serie de recomendaciones extraídas de Internet y que yo intento utilizar en la medida de lo posible, aunque para ser sincero no siempre consigo utilizarlas 😉

Vamos a basarnos en el ejemplo de la cuenta bancaria, es el mismo del post anterior sobre PHPUnit y el ejemplo clave de la documentación de PHPUnit (http://phpunit.de/manual/current/en/index.html)

Sé descriptivo acerca de lo que estás probando

Los test que escribimos deben ser autodescriptivos y con solo un vistazo al código debemos saber que se está probando. Esto puede ser fácil de decir pero bastante difícil de hacer, por ello en PHPUnit están las anotaciones como @Covers.

@Covers sirve para especificar qué método se está probando en un test.

<?php
 require_once 'BankAccount.php';
  
 class BankAccountTest extends PHPUnit_Framework_TestCase
 {
     protected $ba;
  
     protected function setUp()
     {
         $this->ba = new BankAccount;
     }
  
     /**
      * @covers BankAccount::getBalance
      */
     public function testBalanceIsInitiallyZero()
     {
         $this->assertEquals(0, $this->ba->getBalance());
     }
}

Utiliza las aserciones específicas que puedas

Utiliza aserciones simples, siempre debemos intentar que el código de los tests sea entendible, eso no significa muchas líneas de código, sino que es mejor utilizar el método empty para comprobar que un array esta vacío. Veamos un ejemplo.

<?php
 
 class Test extends PHPUnit_Framework_TestCase
 {
     public function testSomething()
     {
        $array = array(1);     
         $this->assertTrue(empty($array));
     }
}

Unos ejemplos más:

$this->assertCount(1, array(‘foo’));       vs.          $this->assertEquals(1, count(array(‘foo’)));
$this->assertInstanceOf(‘Foo’, $foo);    vs.          $this->assertTrue($foo instanceof Foo);

Desvincular dato y pruebas

Los test pueden aceptar argumentos, estos argumentos deben ser proporcionados por un método proveedor, por ello es una buena formula etiquetar a los métodos proveedores con la notación @dataProvider.

<?php
  
 class DataTest extends PHPUnit_Framework_TestCase
 {
     /**
      * @dataProvider provider
      */
  
     protected function testAdd($a, $b, $c),
     {
         $this-> assertEquals($c, $a, $b);
     }
      public function provider()
     {
        return array(
             ‘a’ => array(0, 0, 0),
             ‘b’ => array(0, 1, 1),
             ‘c’ => array(1, 0, 1),
             ‘d’ => array(1, 1, 3)
         );
     }
}

Mantener siempre separados código y test

La mejor manera de tener siempre controlados código y test es mantener la misma estructura de carpetas tanto en código como en tests, así no nos perderemos buceando por carpetas.

Un test solo prueba una cosa

Si parece algo obvio, pero cuando nos ponemos a escribir test es algo habitual (al menos en mí) poner más de un assert en el código. Tenemos que hacer que nuestros tests sean lo suficientemente unitarios.

<?php
  
 class DataTest extends PHPUnit_Framework_TestCase
 {
     public function testPushAndPopWorks()
     {
         $stack = array();
          array_push($stack, ‘foo’);
          $this->asserEquals( ‘foo’, $stack[count($stack) – 1]);
          $this->asserNotEmpty($stack);
          $this->asserEquals( ‘foo’, array_pop($stack));
          $this->assertEmpty($stack);
    }
}

Esto quedaría mejor así ¡no?

class StackTest extends PHPUnit_Framework_TestCase
{
public function testStackIsInitiallyEmpty()
{
$stack = array();
$this->assertEmpty($stack);
return $stack;
}
/**
* @depends testStackIsInitiallyEmpty
*/
public function testPushingAnElementOntoTheStackWorks(array $stack)
{
array_push($stack, 'foo');
$this->assertEquals('foo', $stack[count($stack)-1]);
return $stack;
}
/**
* @depends testPushingAnElementOntoTheStackWorks
*/
public function testPoppingAnElementOffTheStackWorks(array $stack)
{
$this->assertEquals('foo', array_pop($stack));
$this->assertEmpty($stack);
}
}        

Estos son solo algunas de las mejores prácticas que se pueden seguir si estamos creando test unitarios o utilizando TDD. Existen muchísimas buenas prácticas más, así que vamos a crear aquí una pequeña lista de buenas prácticas recomendadas.

  • Tener en carpetas diferentes el código y los tests, si se puede segur la mima estructura en ambos perfecto.
  • Sigue los pasos de TDD, comprueba que el método falla justo después de crearlo.
  • Refactoriza a menudo, sino el código duplicado no te dejará dormir por las noches.
  • Minimiza el número de assert en cada test.
  • Todas las pruebas deben funcionar antes de escribir la siguiente.
  • No introducir dependencias entre pruebas, la frase “como el test XXX viene antes podemos probar YYY así” no es buena.
  • Utilizar mocks y stubs (esto me lo apunto para otro post 😉

Referencias

http://phpunit.de/manual

http://stevedaskam.wordpress.com/2011/07/16/tdd-best-practices/

http://es.slideshare.net/Edorian/phpunit-best-practices-16329959

http://stackoverflow.com/questions/3697815/best-practices-for-database-testing-with-phpunit?rq=1

http://cdn.oreillystatic.com/en/assets/1/event/45/PHPUnit%20Best%20Practices%20Presentation.pdf

Anuncios

Un comentario en “PHPUnit y buenas prácticas

Comenta la entrada

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s