Etiqueta: PHPUnit

  • Cómo usar PHPUnit

    Cómo usar PHPUnit

    Te decidiste. Llegó la hora de incorporar el testing automatizado a tus proyectos.

    Permitime que te felicite, es un gran paso hacia la generación de software de calidad superior.

    Después de hacer la debida investigación hay pocas dudas: PHPUnit es la herramienta que debes conocer.

    Para hacerte un poco más sencillo el camino, te dejo los lineamientos para dar tus primeros pasos.

    Cómo instalar PHPUnit

    Lo primero, como de costumbre, será instalar la herramienta.

    Si vas al sitio de phpunit.de encontrarás algo como:

    Nada mal para tener a mano pero, para empezar de cero… puede ser algo intimidante.

    Antes de continuar, una advertencia: las versiones de phpUnit están bastante ligadas a las de php. Esto significa que, para asegurarte de elegir una versión que puedas utilizar, debes saber qué versión de php tienes instalada (O piensas instalar, por ejemplo, si utilizarás docker).

    Seleccionar la versión de phpunit

    Asumamos que, para arrancar, usarás el php instalado en tu propio host.

    Un simple php -v alcanzará:

    Pues bien, en este caso, la versión que más conviene utilizar es la 11, como puede verse aquí:

    El siguiente paso: instalar la librería.

    Instalar phpunit mediante el phar

    La primera opción disponible es descargar el paquete cerrado (el archivo .phar) de aquí y hacerlo ejecutable.

    Esta opción puede resultar útil si quieres dejar una única versión de phpunit disponible para todos tus proyectos.

    Instalar phpunit usando composer

    Por lejos, la forma que recomiendo (y utilizo) es hacerlo a través de composer.

    De esta forma, la dependencia quedará circunscripta al proyecto en el que estás trabajando en este momento, a la vez que puedes compartir esta configuración con el resto de tu equipo.

    Usa composer require --dev phpunit/phpunit ^11 y composer se encargará de todo.

    Para verificar tu instalación usa el comando ./vendor/bin/phpunit --version

    Si ves algo como

    PHPUnit 11.4.4 by Sebastian Bergmann and contributors.

    PHPUnit 11.4.4 by Sebastian Bergmann and contributors.

    Estás listo para avanzar.

    Cómo escribir un test con PHPUnit

    Escribir un test de PHPUnit supone crear una nueva clase que extienda de TestCase:

    <?php declare(strict_types=1);
    use PHPUnit\Framework\TestCase;
    
    final class MyTest extends TestCase
    {
    }

    Para ejecutar tus tests puedes utilizar el comando ./vendor/bin/phpunit MyTest.php y obtendrás una salida como:

    PHPUnit 11.4.4 by Sebastian Bergmann and contributors.
    
    Runtime:       PHP 8.4.1
    
    There was 1 PHPUnit test runner warning:
    
    1) No tests found in class "MyTest".
    
    No tests executed!

    Bastante razonable ¿no? Al fin y al cabo, se ha definido un TestCase pero ningún test dentro. Corrijamos eso.

    Por convención, PHPUnit entenderá que cualquier método de una clase TestCase cuyo nombre comience por test debe ser ejecutado por él, es decir, lo próximo que deberías hacer es agregar un método como este:

    <?php declare(strict_types=1);
    use PHPUnit\Framework\TestCase;
    
    final class MyTest extends TestCase
    {
       public function testSomething(): void
       {
       }
    }

    Al ejecutar este test verás algo como:

    PHPUnit 11.4.4 by Sebastian Bergmann and contributors.
    
    Runtime:       PHP 8.4.1
    
    R                                                                   1 / 1 (100%)
    
    Time: 00:00.003, Memory: 8.00 MB
    
    There was 1 risky test:
    
    1) MyTest::testSomething
    This test did not perform any assertions
    
    /home/mauro/phpunit-poc/MyTest.php:6
    
    OK, but there were issues!
    Tests: 1, Assertions: 0, Risky: 1.

    Es decir, se ha ejecutado un test, sólo que este test no ha verificado nada, es decir, no hay realizado ningún assertion.

    Para que efectivamente aporte algo de información, dentro del cuerpo del test debe utilizarse alguno de los métodos assert* que provee phpUnit, por ejemplo:

    <?php declare(strict_types=1);
    use PHPUnit\Framework\TestCase;
    
    final class MyTest extends TestCase
    {
       public function testSomething(): void
       {
          $this->assertTrue(true);
       }
    }

    Y ahora sí, llegarás a una salida algo más parecida a lo deseable:

    PHPUnit 11.4.4 by Sebastian Bergmann and contributors.
    
    Runtime:       PHP 8.4.1
    
    .                                                                   1 / 1 (100%)
    
    Time: 00:00.003, Memory: 8.00 MB
    
    OK (1 test, 1 assertion)

    Por supuesto que este test en la realidad no aporta mucho. Depende de vos hacer tests que sean relevantes para tu aplicación.

    Ejemplo de un test con PHPUnit

    Veamos un ejemplo algo más realista de lo que se puede hacer con phpUnit.

    Imaginemos que tienes una función que, recibe una lista de números de teléfono y una lista de prefijos y retorna aquellos que comienzan por alguno de los prefijos indicados por el segundo argumento.

    Un test para dicha función podría ser este:

    <?php declare(strict_types=1);
    use PHPUnit\Framework\TestCase;
    
    final class MyTest extends TestCase
    {
            public function testPhoneFilter(): void
            {
                    $filteredPhoneNumbers = filter_phone_numbers(
                            [ '(34) 665-55-22-112', '34 992 11 22 33', '+54 9 11 5494 2211', '054 121 123123' ],
                            [ '34', '054' ],
                    );
    
                    $this->assertEquals( [ '34 992 11 22 33', '054 121 123123'] , $filteredPhoneNumbers );
            }
    }

    Por dónde continuar aprendiendo

    PHPUnit ofrece una cantidad de posibilidades realmente interesante pero, para no marearte con todo al comienzo te recomiendo continuar aprendiendo sobre dobles de test y sobre la configuración de phpUnit.

    Ya habrá tiempo para lo demás.

  • Cómo testear una aplicación PHP que no usa objetos

    Cómo testear una aplicación PHP que no usa objetos

    PHPUnit, al igual que la mayoría de los frameworks de testing, se basa fuertemente en el supuesto de que la aplicación a verificar está desarrollada bajo el paradigma de Orientación a Objetos.

    Sin embargo, es muy común en nuestros días encontrarnos con aplicaciones tipo spaghetti… ¿es posible hacer testing automatizado sobre ellas?

    La respuesta es sí.

    Claro que las respuestas a qué testear y cómo testear son un poco diferentes.

    Qué puede testearse en una aplicación que no usa objetos

    Obviamente, no será posible verificar una clase porque… la aplicación no tiene clases.

    De modo que podemos testear:

    • La página que se presentará al usuario (Lo que podríamos asemejar a un test funcional)
    • El resultado de ejecutar alguna función en particular
    • El resultado de correr algún script

    Cómo testear el resultado de una página php

    Para este escenario nos tendremos que valer de un pequeño truco: las funciones ob_start y ob_get_clean (Además de tener instalado phpUnit, claro).

    La idea es muy simple en realidad.

    Se crea un caso de test, se abre un buffer y se incluye el archivo que queremos validar.

    A continuación se levantan los contenidos del buffer y se examinan usando assertions.

    Veamos un ejemplo simple:

    El archivo que queremos validar se llama wrong.php

    <html>
    <body>
    <p><?php echo 'Bye bye world'; ?></p>
    </body>
    </html>

    Y este sería el caso de test:

    <?php
    
    use PHPUnit\Framework\TestCase;
    
    class PageTest extends TestCase
    {
            public function testGreeting()
            {
                    ob_start();
                    require_once 'wrong.php';
                    $this->assertRegExp('/<p>Hello World!<\/p>/', ob_get_clean());
            }
    }

    Para correr el test usamos el comando vendor/bin/phpunit PageTest.php y la salida será:

    PHPUnit 9.3.7 by Sebastian Bergmann and contributors.
    
    F                                                                   1 / 1 (100%)
    
    Time: 00:00.004, Memory: 4.00 MB
    
    There was 1 failure:
    
    1) PageTest::testGreeting
    Failed asserting that '<html>\n
    <body>\n
    	<p>Bye bye world</p>\n
    </body>\n
    </html>\n
    ' matches PCRE pattern "/<p>Hello World!<\/p>/".
    
    /home/mauro/Code/testing/PageTest.php:11
    
    FAILURES!
    Tests: 1, Assertions: 1, Failures: 1.

    Cómo testear el resultado de una función php

    Este caso es bastante similar al anterior, aunque un poco más simple.

    Aquí lo que haremos será, en lugar de validar la salida completa, verificaremos qué sucedió como resultado de ejecutar la función.

    Empecemos por modificar el archivo a testear:

    <?php
    
    function duplicate(int $p) : int
    {
            return $p * 3;
    }

    Y ahora hagamos un nuevo test:

    <?php
    
    use PHPUnit\Framework\TestCase;
    
    class FunctionTest extends TestCase
    {
            public function testFunction()
            {
                    ob_start();
                    require_once 'wrong.php';
    
                    $this->assertEquals(4, duplicate(2));
            }
    }

    El resto sigue igual al caso anterior

    Cómo testear la ejecución de un script php

    Por último, podríamos requerir testear el funcionamiento de un script de línea de comandos (Un cronjob por ejemplo).

    Imaginemos un script como este:

    <?php
    
    echo 'Bye bye '.$argv[1].'!';

    Y este test:

    <?php
    
    use PHPUnit\Framework\TestCase;
    
    class ScriptTest extends TestCase
    {
            public function testGreeting()
            {
                    $this->assertEquals('Hello World!', shell_exec('php script.php World'));
            }
    }

    Nos dará este resultado:

    PHPUnit 9.3.7 by Sebastian Bergmann and contributors.
    
    F                                                                   1 / 1 (100%)
    
    Time: 00:00.026, Memory: 4.00 MB
    
    There was 1 failure:
    
    1) ScriptTest::testGreeting
    Failed asserting that two strings are equal.
    --- Expected
    +++ Actual
    @@ @@
    -'Hello World!'
    +'Bye bye World!'
    
    /home/mauro/Code/testing/ScriptTest.php:9
    
    FAILURES!
    Tests: 1, Assertions: 1, Failures: 1.

    Conclusión

    En defintiva, no es cierto que es imposible testear aplicaciones php que no se basen en POO… lo que sí es cierto es que los tests van a ser más engorrosos y menos informativos.

    Claro que eso es consecuencia de un diseño poco modular de la aplicación que estamos testeando… ¡pero esa fue precisamente la premisa del ejercicio!

    Espero te haya dado alguna idea nueva, ¡espero tus comentarios!