Hace unos días hemos empezado a trabajar con TDD y Silex en un proyectos bastante especial, “Como utilizar Tdd con Silex utilizando PhpUnit” para ello ya tenemos configurado todo el entorno par a poder empezar a hacer test, escribir código y refactorizar.
Básicamente nuestro proyecto es la API de una calculadora 😉 es decir es una calculadora con la que podremos realizar operaciones aritméticas de 2 operandos con tan solo hacer peticiones GET a una dirección web. Para ello los pasos que daremos serán, refactorizar la aplicación actual para poder ampliarla con la nueva funcionalidad, crear una librería que realice las operaciones aritméticas, modifica el routing para poder hacer las operaciones haciendo peticiones GET desde un Navegador.
Refactorizar el proyecto de Silex con TDD para ampliar la aplicación.
Ahora que tenemos test (aunque solo sean un par) podemos hacer cambios y asegurarnos de que todo sigue funcionando. Lo primero que haremos será mover toda la lógica de la App. Pero antes que nada debemos asegurarnos de que todo está correcto, así que antes de tocar nada lanzamos nuestros tests.
Ahora creamos una nueva carpeta llamada src dentro de esta creamos una nueva carpeta llamada Tutorial En esta carpeta esta´ra toda la lógica de este pequeño tutorial sobre TDD con Silex y PhpUnit. Dentro de la Carpeta Tutorial creamos otra llamada Controller será aquí donde crearemos moveremos la lógica que imprime por pantalla y escribe en el log.
Aquí vemos como tiene que quedar el proyecto en cuanto a carpetas y archivos. No tenemos que preocuparnos por el archivo README (es para mi cuenta de github), ni tampoco del .gitignore
Moviendo la lógica en Silex con la ayuda de PHPUnit
Dentro de la carpeta Controller creamos un archivo llamado Status.php donde vamos a crear la clase Status, esta clase se encarga de comprobar imprimiendo por pantalla que todo sigue funcionando, es decir, hay log y el routing funciona. Para ello tenemos la función index que es la que se encarga de todo esto.
<?php
namespace Tutorial\Controller;
use Silex\Application;
class Status
{
/**
* Indicates application status
*/
public function index(Application $app)
{
$app['monolog']->addInfo('Logging example in the status route');
return 'Running with log';
}
}
Ahora tenemos que eliminar la funcionalidad del archivo bootstrap.php e indicarle a Silex ejecute la función index cuando la ruta a la que se accede sea la correcta.
Por ello el archivo bootstrap.php debe quedar así:
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
use Silex\Provider\MonologServiceProvider;
$app = new Silex\Application;
$app['debug'] = true;
$app->register(new MonologServiceProvider(), array(
'monolog.logfile' => __DIR__ . '/../logs/dev.log'
));
$app
->match('/', 'Tutorial\Controller\Status::index')
->method('GET|POST');
return $app;
Como podemos observar hemos sustituido la función que teníamos por solo una llamada a la misma función. La función index la hemos definido en la clase Status.
Ahora podemos volver a ejecutar los tests para ver si todo sigue funcionando.
Si no confiamos en los tests siempre podemos abrir el navegador y probar con http://testingsilex.dev
Una vez echo esto podemos mover el archivo de test par a mantener la coherencia entre el código y los tests.
Creando nuestra librería para sumar
Para seguir teniendo separada toda la lógica, vamos a crear una librería con la que realizar las operaciones aritméticas, es decir vamos a crear una calculadora utilizando TDD. Como hemos visto antes las especificaciones son sencillas, en un primer momento vamos a crear solo la funcionalidad necesaria para sumar 2 números y devolver el resultado.
Creando el archivo de tests (CalculatorTest)
Para ello lo primero que haremos será dentro de la carpeta test crearemos una nueva carpeta llamada lib aquí crearemos los tests para nuestra calculadora. Así que crearemos un archivo llamado CalculatorTest.php Esta será nuestra clase para utilizar PHPUnit y hacer TDD.
En un primer momento solo crearemos un test básico, para comprobar que todo funciona, podemos copiar todo el archivo Test.php (acuérdate de renombrar la clase con por ejemplo class CalculatorTest
)
Creando la funcionalidad para sumar (CalculatorLib)
Ahora vamos a crear la funcionalidad para poder sumar, para ello creamos dentro de la carpeta Tutorial una nueva carpeta llamada lib (es una buena práctica tener la misma estructura de archivos para funcionalidad y test). Ahora solo tenemos que crear un nuevo archivo llamado CalculatorLib.php en el que crearemos la clase class CalculatorLib
Esta es la clase CalculatorLib
<?php
namespace Tutorial\lib;
class CalculatorLib {
}
Esta sería la clase de CalculatorTest
<?php
namespace Tutorial;
use \Tutorial\lib\CalculatorLib;
class CalculatorTest extends \PHPUnit_Framework_TestCase {
public function testNothing()
{
$this->assertTrue(true);
}
}
Y así quedarán todos la estructura de archivos:
Dándole caña a TDD en Silex con PhpUnit
Primer test
Ya tenemos la estructura básica para crear test y empezar a crear funcionalidad, lo primero es crear un test donde dados dos números, por ejemplo 1 y 2 nos devuelva la suma. Para ello lo primero será instanciar el objeto calculator
y después llamar al método add. Más o menos tendremos algo como esto. Al ejecutar este test veremos que es FAIL, no debemos preocuparnos, ahora tenemos que escribir la funcionalidad necesaria para crear el test.
Sólo tenemos que crear la función add
dentro de nuestra clase Calculator
class CalculatorLib {
public function add( $number1, $number2 )
{
return $number1 + $number2;
}
}
Ahora volvemos a ejecutar los test y deben pasar OK
Creamos otro test para poder sumar String en los que dichos String sean numéricos, así que creamos un nuevo test como este
public function testaddString()
{
$this->calculator = new CalculatorLib();
$equals = $this->calculator->add("1", "2");
$this->assertEquals(3, $equals);
}
Refactorizando los test
Como vemos estamos instanciando un nuevo objeto calculator en cada test, así que lo mejor sería REFACTORIZAR creando un método setup que se ejecutará antes de cada test. Finalmente nos quedará una clase como esta
class CalculatorTest extends \PHPUnit_Framework_TestCase {
protected $calculator;
public function setup()
{
$this->calculator = new CalculatorLib();
}
public function testadd()
{
$equals = $this->calculator->add(1, 2);
$this->assertEquals(3, $equals);
}
public function testaddString()
{
$equals = $this->calculator->add("1", "2");
$this->assertEquals(3, $equals);
}
}
Volvemos a ejecutar los tests para ver si todo sigue en Verde.
Finalizando con el TDD
Seguimos haciendo test para validar que las variables de entrada no sean nulas y sean numéricas.
Con esto ya tenemos nuestra librería montada, ahora vamos a por el routing, para ello solo tenemos que modificar el archivo bootstrap.php y crear el Controlador.
TDD con los Controller
Como estamos desarrollando todo el proyecto utilizando TDD lo primero que vamos a implementar son los tests y en concreto un test para el Controlador de la calculadora.
A grandes rasgos estamos siguiendo un modelo MVC donde tenemos un controlador es el encargado de instanciar la librería y devolver la respuesta.
Como ya hemos visto antes, sería conveniente tener la misma estructura de carpetas en el código que en los tests. Dentro de la carpeta test/Tutorial/Controller creamos nuestro test para el controlador de la librería llamado CalculatorControllerTest.php. Con esta clase de test diseñaremos que respuestas debemos dependiendo de las request que nos lleguen.
Así pues nuestro primer test será comprobar que podemos realizar una suma pasando 2 parámetros a y b en la request
<?php
namespace CalculatorTest;
use Silex\WebTestCase;
class CalculatorTest extends WebTestCase
{
public function createApplication()
{
$app = require __DIR__ . '/../../../app/bootstrap.php';
$app['debug'] = true;
return $app;
}
public function testcalculatorAddOk()
{
$client = $this->createClient();
$client->request('GET', '/calculator/add/2/3');
$this->assertTrue($client->getResponse()->isOk());
$this->assertEquals("5", $client->getResponse()->getContent());
}
}
Ya tenemos el test, así que siguiendo el ciclo de TDD lo ejecutamos y comprobamos que falla. Ahora vamos a escribir el mínimo código para que pase la prueba.
Para ello lo primero es modificar el archivo bootstrap.php para indicar que peticiones aceptamos, así quedará nuestra aplicación:
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
use Silex\Provider\MonologServiceProvider;
$app = new Silex\Application;
$app['debug'] = true;
$app->register(new MonologServiceProvider(), array(
'monolog.logfile' => __DIR__ . '/../logs/dev.log'
));
$app
->match('/calculator/add/{a}/{b}', 'Tutorial\Controller\Calculator::executeAdd')
->method('GET|POST');
$app
->match('/', 'Tutorial\Controller\Status::executeIndex')
->method('GET|POST');
return $app;
Ahora escribimos el mínimo código para hacer que el test pase.Como podemos ver a medida que vamos haciendo tests vamos tomando decisiones de diseño. Desde cual será la estructura de carpeta, a como vendrán los parámetros (GET o POST),…
<?php
namespace Tutorial\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Tutorial\lib\CalculatorLib;
class Calculator {
public function executeAdd(Application $app, Request $request)
{
$log = 'class Calculator:Running AddAction';
$app['monolog']->addInfo($log);
$a = $request->get('a');
$b = $request->get('b');
$calculator = new CalculatorLib();
$text = $calculator->add($a, $b);
return new Response($text, 200);
}
}
Listo ya tenemos nuestra aplicación funcionando 🙂 ahora solo nos queda ampliarla, seguir testeando el código y continuar aprendiendo.
Como vemos hemos creado un método executeAdd que es el encargado de recibir la request, recoger los parámetros y llamar a la librería.
Ahora solo nos queda ejecutar los tests de nuevo y ver que todo funciona, así que lanzamos PHPUnit y…
También podemos probar nuestra aplicación en un navegador, aunque no es gran cosa en cuanto a UI, podemos ver que lo que hemos hecho está funcionando correctamente 😉
Conclusiones
Esto es solo un pequeño ejemplo de lo mucho que podemos hacer con TDD, PHPUnit, además Silex es un framework muy versátil con el que podemos tener una aplicación funcionando en poco tiempo.
Hay que tener en cuenta que Silex sea demasiado versátil y sin casi sin esfuerzo podemos tener un código espagueti que no entendamos, por lo que debemos ser exigentes con nosotros mismos a la hora de organizar el código, utilizar naming tanto en variables como en métodos porque sino es muy probable que nos liemos.
Por otro lado es importante aprender patrones para saber como afrontar las pruebas y como probar las distintas partes de código, poco a poco iremos aprendiendo más sobre como trabajar con TDD y realizar testing con PhpUnit, pero antes seguro que me he dejado alguna cosa en el tintero y no la he explicado del todo, así que ¿me echas un cable con alguna sugerencia?
BOLA EXTRA:
El a veces odiado, archivo .htaccess para que funcione el proyecto
Si intentamos acceder a distintas rutas del proyecto utilizando el navegador (por ejemplo http://testingsilex.dev/aaa) puede que nos aparezcan errores como estos:
Podría ser debido a que no tenemos configurado Apache, Nginx, o el servidor de aplicaciones que estemos utilizando. En mi caso utilizo Apache 2.2.6, por lo que puede ser necesario añadir los archivos .htaccess al proyecto. Existen muchas configuraciones distintas para estos archivos, aquí dejamos una configuración básica para que el proyecto de Silex con TDD funcione.
Serán necesarios 2 archivos .htaccess uno dentro del directorio raíz, en nuestro caso dentro del directorio testingsilex.dev y otro archivo dentro del directorio web
Este es el archivo .htaccess para el directorio raíz del proyecto:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ web/$1 [QSA,L]
También necesitamos otro archivo .htaccess para el directorio web con el siguiente contenido:
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
RewriteCond %{THE_REQUEST} ^(GET|HEAD)\ /web/
RewriteRule ^(.*)$ /$1 [L,R=301]
El tema está muy detallado, super útil, me lo guardo
Pero por favor, revisa la ortografía!, hay faltas por todos sitios (te comes letras, unes palabras, etc. Eso podría llegar a ser perdonable, pero «bamos» con B… TIO! por favor, revisa esto porque quedas muuuy mal.
Saludos
Me gustaMe gusta
Muchas gracias por el comentario. Le he dado un poco de cariño al post y he corregido todas las faltas que he encontrado.
Me gustaMe gusta