PHPUnit: cómo testear cualquier código legacy en PHP

2021-11-03 - Categorías: General / PHP

Este es un codekata para programar tests de las páginas web legacy. El tratar de testear código legacy en PHP puede parecer bien complicado. Puede no haber casi encapsulamiento en objetos, estando todo construido a base de scripts embebidos entre el HTML, CSS y JavaScript. Una web legacy es el caso más complejo, pero este codekata también sirve para cualquier otro código en servicios web, scripts sueltos, encapsulado en objetos, dividido por capas o arquitecturas hexagonales.

Estos códigos legacy se pueden testear de varias formas. Se pueden establecer tantos puntos de control como queramos. Como mínimo podemos crear tests de aceptación, funcionales, crawleando, creando tantos tests como queramos sobre los casos de uso. Pero si queremos obtener la cobertura visualizando cada línea de código por la que ha pasado la ejecución, no podemos crawlear la web y ya está. Así PHPUnit no obtendrá la cobertura del código ejecutado. Además, se puede también establecer puntos de control, atendiendo al contenido de las respuestas, navegando por el HTML de dichas respuestas.

En frameworks modernos como Symfony, se facilitan los tests funcionales. También hay herramientas que ayudan mucho como Selenium, Behat, Codeception.. Pero volviendo al caso más sencillo con PHPUnit, podemos simular este navegar, obteniendo el mismo resultado, además de visualizar toda la cobertura, construyendo así todos esos puntos de control que se establecen para garantizar que las cosas funcionan en la entrega.. y que siguen funcionando con cada nueva modificación.

Cómo crawlear una página para construir tests con cobertura de código

Para probar vamos a partir de un index/index.html que va a enviar la información de un formulario a un script PHP que queremos testear:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="post.php" method="post">
        <input type="text" name="texto1" id="texto1">
        <input type="text" name="texto2" id="texto2">
        <button type="submit">Enviar</button>
    </form>
</body>
</html>

Ahora el PHP del index/post.php donde se reciben los datos, con un simple bucle que queremos testear:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<?php

echo $_SERVER['REMOTE_ADDR'];

$texto1 = $_POST['texto1'];
$texto2 = $_POST['texto2'];

for ($i=0; $i < 100; $i++) { 
    echo $texto1.' '.$texto2.'<br>';
}
?>
</body>
</html>

Con todo esto ya podemos lanzar el servidor de desarrollo local entrando a index/, desde terminal o consola podemos hacer:

php -S localhost:8000

..y podríamos ir al navegador a la URL http://localhost:8000/. Si la funcionalidad es simple no tendría sentido programar un test. Pero si la lógica es compleja o crítica, conviene tenerlo programado para hacernos saltar las alarmas en caso de que algo se rompa.

La solución más simple es testearlo simulando un bot sencillo. Podríamos escribir por ejemplo un test en el fichero tests/PostTest.php dentro de nuestro directorio de tests/ del proyecto:

<?php

use PHPUnit\Framework\TestCase;

class PostTest extends TestCase
{
    public function testMain()
    {
        // Preparamos variables que vamos a enviar como si fuéramos el formulario..
        $_POST['texto1'] = 'abc';
        $_POST['texto2'] = 'def';
        $_SERVER['REMOTE_ADDR'] = '123.123.123.123'; // IP a simular que somos

        // Cargamos para testear el script destino del formulario..
        ob_start();
        include __DIR__.'/../index/post.php';
        $page = ob_get_contents();
        ob_end_clean();

        // Esto es el contenido de la respuesta del index/post.php..
        //echo $page;

        // Creamos tantos puntos de control como queramos.. con la respuesta,
        // consultando ficheros, la base de datos, etc.
        $this->assertTrue(!empty($page));
    }
}

A partir de aquí se podrían crear más casos de uso en el fichero tests/PostTest.php, uno por función, o varios ficheros con un test cada uno, tantas pruebas distintas como queramos. El porqué esto funciona es porque tenemos que simular lo que haría el navegador y servidor al enviar la información de la petición al script. Es decir, tenemos que escribir las variables en $_POST, $_GET, $_SERVER.. que queramos que el script reciba, simulando que somos un navegador que va a realizar el submit del formulario anterior.

Si nos fijamos en el HTML del index/index.html, lo que hacemos es enviar con el formulario dos input llamados texto1 y texto2, mediante el método POST. Entonces tenemos que escribir esas dos variables con su contenido en el test, en el array $_POST.

Por otro lado, por limpieza en salida por pantalla, necesitamos usar las funciones ob_* para no pintar por pantalla los resultados. Estos resultados se pueden almacenar como en el código anterior en $page para luego realizar de nuevo comprobaciones, a ver si el contenido de la página devuelta está bien.

Ahora lo siguiente es lanzarlo desde terminal:

XDEBUG_MODE=coverage php bin/phpunit --coverage-html=coverage/ --coverage-filter index/ tests/

..y si todo ha ido bien veremos una imagen como la del principio del post.

Testing con PHPUnit..

Ahora podemos ir a ver el resultado generado en coverage/index.html y si abrimos el informe de cobertura deberíamos de ver que ha ejecutado el 100% del código fuente en PHP:

Si entramos a ver el fichero el resultado del fichero post.php veremos algo como lo siguiente:

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

 

© 2021 JnjSite.com - MIT license

Sitio hecho con WordPress, diseño y programación del tema por Jnj.