[Fuente: http://net.tutsplus.com/tutorials/php/the-newbies-guide-to-test-driven-development/]
Probar el código es molesto, pero el impacto de no hacerlo puede crear molestias de magnitudes muy superiores!. En este artículo, utilizaremos programación orientada a objetos (test-driven development) para programar y probar nuestro código más eficientemente.
Qué es el TDD o Programación Orientada a Pruebas (POP)?
Desde el principio de los tiempos, los programadores y los fallos han luchado los unos con los otros. Incluso los mejores programadores tienen fallos. Ningún código es 100% seguro. Es por ello que hacemos testing. Los programadores, al menos los sanos, pruebas su código ejecutando en máquinas de desarrollo par estar seguros que el código hace lo que se supone que debe hacer.
Programador que prueba sus programas.
Image courtesy of http://www.youthedesigner.com
Programador que no prueba sus programas
Image courtesy of http://www.internetannoyanceday.com
“La programación orientada a objetos es una técnica de programación que requiere que se programen a la vez el código y sus pruebas automatizadas. Esto asegura que pruebas tu código – y te permite reprobar tu código rápida y fácilmente, ya que está automatizado”
Cómo funciona?
El desarrollo orientada a pruebas , o TDD como lo llamaremos desde ahora, recuerda como a un corto ciclo de desarrollo iterativo que va de la siguientes pasos:
- Antes de escribir cualquier código, debes primero escribir un test automatizado para el código. Mientras escribir el código automatizado, debes tener en cuenta todas las posibles entradas de datos, errores y salidas. De esta forma, tu mente no estará confundida con código que ya ha sido escrito.
- La primera vez que ejecutas las pruebas automatizados, el test debe fallar – indicando que el código no esta listo aún.
- Después, puedes empezara programar. Ya que hay un test automático, tan pronto como el código falle, eso significa que aún no esta listo. El código puede ser arreglado hasta que pase todos los assertions.
- Una vez que el código pasa el test, puedes empezar a limpiarlo, utilizando refactoring. Mientras el código siga pasando los test, significa que funcione. No te tienes que preocupar sobre los cambios que introduzcan nuevos fallos.
- Comienza desde el principio con cualquier otro método o programa.
Ok , pero por qué esto es mejor que las pruebas normales?
Has pasado a proposito de probar un programa porque:
- sientes que es una perdida de tiempo, ya que solo fue un cambio pequeño?
- te da pereza probar todo otra vez?
- no tienes tiempo suficiente para probar porque el gestor del proyecto lo quiere en producción ASAP?
- te dijiste a ti mismo que ya lo harías mañana?
- tenias que elegir entre pruebas a mano , o ver el último episodio de Barrio Sesamo?
La mayor parte del tiempo, nada sucede, y pones tu código en producción sin ningún problema. Pero algunas veces, después de que lo has puesto en producción, todo empieza a ir mal. Y estas acorralado arreglando multitud de errores en un barco que se hunde, con más errores apareciendo a cada momento. Seguro que no quieres verte en esta situación.
TDD fue creado para que no tengamos excusas. Cuando un programa ha sido desarrollado utilizando TDD, te permite hacer cambios y probarlo rápidamente y eficientemente. Todo lo que necesitamos es ejecutar los tests automatizados y voila! Si pasa todos los test automatizados, entonces vamos bien – si no, entonces hemos roto algo con los ultimos cambios que hemos hecho. Conociendo qué parte del código ha fallado, también te permite fácilmente averiguar en qué parte del código está el fallo, haciendo el arreglo de errores mucho más fácil.
Lo he vendido . ¿Cómo lo hago?
Hay muchos frameworks de testing automático para PHP que podemos utilizar, Uno de los más ampliamente utilizados es PHPUnit.
PHPUnit puede integrarse fácilmente en nuestros proyectos, u otros proyecto que han sido construidos sobre otros frameworks PHP populares.
Para nuestros propositos, no necesitamos la multitud de funciones que ofrece PHPUnit. En vez de ello, optaremos por crear nuestros propios test utilizando un framework de testing mucho más simple, llamado SimpleTest.
En los siguiente pasos, asumiremos que estamos programando una aplicación de libro de invitados donde cualquier usuario puede añadir y ver entradas del libro.
Asumiremos que ya tenemos una maqueta de la aplicación, y que simplemente estamos haciendo una clase que contiene la lógica de la aplicación, que es la que lee y escribe en la BBDD. La parte de lectura es lo que vamos a desarrollar y probar.
Primer Paso: Configurar SimpleTest
Descarga SimpleTest here, y extrae a la carpeta que quieras – preferiblemente a la carpeta donde vas a programar tu código , o en el include_path de PHP para más comodidad.Para esto tutorial , he configurado una carpeta como se ve aqui:
index.php ejecutará nuestro guestbook.php, e invoca el método view y mostrará las entradas. Dentro de la carpeta “classes” es donde pondremos la clase “guestbook.php”, y la carpeta test es donde colocaremos la libreria simpletest.
Segundo paso. Planeemos nuestro ataque
El segundo paso, que es de hecho el más importante, es empezar a crear tus pruebas. Para esto, realmente necesitas planificar y pensar qué hara tu función, qué posibles entradas recoge y que correspondientes salidas enviará. Este paso recuerda a jugar al ajedrez – necesitas saber todo sobre tu oponente (el programa), incluyendo todas sus debilidades (posibles errores) y fortalezas (lo que sucede si todo se ejecuta correctamente).
Asi que para nuestra aplicación guestbook , veamos cuál es el esquema:
View
- Esta función no tendrá ninguna entrada ya que recupera todas las entradas de la BBDD y las envía de vuelta para que sean escritas en pantalla
- Retornará un array de los registros del libro de invitados, conteniendo cada registro el nombre y su mensaje. Si no hay registros, entonces , debe devolver un array vacío.
- Si hay registros , el array tendrá uno o más valores dentro.
- Al mismo tiempo, el array tendrá un estructura específica , algo como esto:
-
Array ( [0] => Array ( ['name'] = "Bob" ['message'] = "Hi, I'm Bob." ) [1] => Array ( ['name'] = "Tom" ['message'] = "Hi, I'm Tom." ) )
Tercer paso: Escribe un test!
Ahora, podemos escribir nuestro test. Empecemos creando un fichero llamado guestbook_test.php dentro de la carpeta test.
<?php require_once(dirname(__FILE__) . '/simpletest/autorun.php'); require_once('../classes/guestbook.php'); class TestGuestbook extends UnitTestCase { function testViewGuestbookWithEntries() { $guestbook = new Guestbook(); // Add new records first $guestbook->add("Bob", "Hi, I'm Bob."); $guestbook->add("Tom", "Hi, I'm Tom."); $entries = $guestbook->viewAll(); $count_is_greater_than_zero = (count($entries) > 0); $this->assertTrue($count_is_greater_than_zero); $this->assertIsA($entries, 'array'); foreach($entries as $entry) { $this->assertIsA($entry, 'array'); $this->assertTrue(isset($entry['name'])); $this->assertTrue(isset($entry['message'])); } } function testViewGuestbookWithNoEntries() { $guestbook = new Guestbook(); $guestbook->deleteAll(); // Delete all the entries first so we know it's an empty table $entries = $guestbook->viewAll(); $this->assertEqual($entries, array()); } } ?>
assertTrue($x) |
Fail if $x is false |
assertFalse($x) |
Fail if $x is true |
assertNull($x) |
Fail if $x is set |
assertNotNull($x) |
Fail if $x not set |
assertIsA($x, $t) |
Fail if $x is not the class or type $t |
assertNotA($x, $t) |
Fail if $x is of the class or type $t |
assertEqual($x, $y) |
Fail if $x == $y is false |
assertNotEqual($x, $y) |
Fail if $x == $y is true |
assertWithinMargin($x, $y, $m) |
Fail if abs($x – $y) < $m is false |
assertOutsideMargin($x, $y, $m) |
Fail if abs($x – $y) < $m is true |
assertIdentical($x, $y) |
Fail if $x == $y is false or a type mismatch |
assertNotIdentical($x, $y) |
Fail if $x == $y is true and types match |
assertReference($x, $y) |
Fail unless $x and $y are the same variable |
assertClone($x, $y) |
Fail unless $x and $y are identical copies |
assertPattern($p, $x) |
Fail unless the regex $p matches $x |
assertNoPattern($p, $x) |
Fail if the regex $p matches $x |
expectError($x) |
Swallows any upcoming matching error |
assert($e) |
Fail on failed expectation object $e |
Assertion method list courtesy of http://www.simpletest.org/en/unit_test_documentation.html
Cuarto Paso: Tener errores para ganar
Esto sucede porque no hemos creado nuestra clase guestbook aún. Para hacerlo, crearemos guestbook.php dentro de la carpeta classes. La clase debe contener los métodos que estamos planeando utilizar, aunque de momento los ponemos vacios. Recuerda, estamos escribiendo los test antes que el código.
<?php class Guestbook { public function viewAll() { } public function add( $name, $message ) { } public function deleteAll() { } }
Si ahora ejecutamos el test otra vez, debe aparecer algo esto:
Como ves ahora, nuestro test esta ahora funcionando aunque falle!. Esto significa que nuestro test está listo para ser “respondido”.
Quinto Paso: Responde tu test escribiendo código
<?php class Guestbook { var $entries = array( array ( 'name' => 'Kirk', 'message' => 'Hi, I'm Kirk.' ), array ( 'name' => 'Ted', 'message' => 'Hi, I'm Ted.' ) ); // To save time, instead of creating and connecting to a database, we're going to // simulate a "database" by creating a static entries array here. // It will be like we have two entries in the table. function viewAll() { // Here, we should retrieve all the records from the database. // This is simulated by returning the $entries array return $this->entries; } function add($name, $message) { // Here, we simulate insertion into the database by adding a new record into the $_entries array // This is the correct way to do it: self::$_entries[] = array('name' => $name, 'message' => $message ); $this->entries [] = array('notname' => $name, 'notmessage' => $message ); //oops, there's a bug here somewhere return true; } function deleteAll() { // We just set the $_entries array to simulate $this->entries = array(); return true; } } ?>
Una vez que ejecutamos nuestros test , debemos ver algo como esto:
La salida del test nos muestra en qué test y en que assertions nuestro código falla. Vemos claramente que en las lineas 16 y 17 está la assertion que arrojó el error. (Se han introducido fallos a proposito en las lineas 16 y 17).
Sexto paso: Refactorizar y Refinar el código
Step 7. Aclarado y repetimos
Eventually, when your program requires new functionality, you’ll need to write new tests. That’s easy! Rinse and repeat the procedures from step two (since your SimpleTest files should already be set up), and start the cycle all over again.
Conclusion
There are a lot more in-depth test-driven development articles out there, and even more functionality to SimpleTest than what was displayed in this article—things like mock objects, stubs, which make it easier to create tests. If you’d like to read more, Wikipedia’s test-driven development page should set you on the right path. If you’re keen on using SimpleTest as your testing framework, browse the online documentation and be sure to review its other features.
Testing is an integral part of the development cycle, however, it’s too often the first thing to be cut when deadlines are imminent. Hopefully, after reading this article, you’ll appreciate how helpful it is to invest in test-driven development.
What are your thoughts on Test-Driven Development? Is it something you’re interested in implementing, or do you think it’s a waste of time? Let me know in the comments!