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://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
Un comentario en “PHPUnit y buenas prácticas”