zagales / establo

Un espacio para aprovechar (o no) el tiempo siendo exentos del servicio militar.
1 stars 0 forks source link

Writing Tests for PHPUnit #6

Open valentinboyanov opened 4 years ago

valentinboyanov commented 4 years ago

Buscando en la documentación de PHPUnit información sobre la organización de tests, he visto esta sección Writing Tests for PHPUnit, donde se describe en qué consiste escribir tests en PHPUnit. Me ha gustado como empieza y he decidido hacer los ejemplos.

Voy a usar esta issue para compartir dudas, asombros y "cosas" :)

valentinboyanov commented 4 years ago

Percatación: un test es otro objeto que ejercita nuestro objeto (el que queremos testear)!

Usamos clases para implementar el test. Esto supone que PHPUnit, para ejecutar el test, va a instanciar un objeto, el test, a partir de la clase que hemos definido. PHPUnit nos proporciona una "forma comoda" para "crear" esos objetos test y un "runner" que sabe interactuar con ellos para "instanciar" los objetos de tests, "lanzarlos", "interpretar" lo que los objetos han "hecho" y "ofrecer los resultados" a los que los necesitamos en "diferentes formatos".

Por qué es comodo? Porque en vez de crear las clases para los objetos de test desde cero, podemos extender el PHPUnit\Framework\TestCase, una clase con la que podemos instanciar objetos bastante inteligentes en el dominio de los Tests. Asi podemos empezar a ejercer nuestro objeto delegando en la sabiduria para "comprobaro cosas" de PHPUnit\Framework\TestCase a través de sus métodos $this->assert*(). Otro motivo por el cual es comodo es que respetando unas poquisimas convenciones, como que los métodos de nuestros objetos testers empiezen con el prefijo test_* y el nombre de la clase el sufijo class *Test // *Test.php podemos usar el test runner de phpunit y sus formateadores de resultados.

El ejemplo de ejercitar un array es muy ilustrativo, con PHPUnit podemos crear la clase para el objeto test de esta forma:

<?php declare(strict_types=1);

class StackTest extends PHPUnit\Framework\TestCase
{
    public function test_push_and_pop()
    {
        $stack = [];
        $this->assertSame(0, count($stack));

        array_push($stack, 'foo');
        $this->assertSame('foo', $stack[count($stack)-1]);
        $this->assertSame(1, count($stack));

        $this->assertSame('foo', array_pop($stack));
        $this->assertSame(0, count($stack));
    }
}

Usar al runner de PHPUnit para ejecutarlo:

⇒  ./vendor/bin/phpunit --colors --testdox StackTest.php

Y disfrutar de los resultados

⇒  ./vendor/bin/phpunit --colors --testdox StackTest.php
PHPUnit 9.1.1 by Sebastian Bergmann and contributors.

Stack
 ✔ Push and pop

Time: 25 ms, Memory: 4.00 MB

OK (1 test, 5 assertions)

Algo "parecido" sin PHPUnit:

<?php declare(strict_types=1);

class StackNoPHPUnitTest
{
    public $name = self::class;
    public $testsCount = 0;
    public $assertsCount = 0;

    public function run()
    {
        $this->testsCount=1;
        $this->test_push_and_pop();
    }

    public function test_push_and_pop()
    {
        $stack = [];
        $this->assertSame(0, count($stack));

        array_push($stack, 'foo');
        $this->assertSame('foo', $stack[count($stack)-1]);
        $this->assertSame(1, count($stack));

        $this->assertSame('foo', array_pop($stack));
        $this->assertSame(0, count($stack));
    }

    private function assertSame($a, $b)
    {
        $this->assertsCount++;

        if ($a !== $b) {
            throw new \Exception(sprintf("%s is not %s", $a, $b));
        }
    }
}

$test = new StackNoPHPUnitTest();

try {
    $test->run();
} catch(\Exception $e) {
    echo sprintf(
        '%s failed (%d test, %d assertions)', 
        $test->name,
        $test->testsCount,
        $test->assertsCount
    );
    echo PHP_EOL;
    echo $e->getMessage();
    echo PHP_EOL;
    exit(1);
}

echo sprintf(
    '%s success (%d test, %d assertions)', 
    $test->name,
    $test->testsCount,
    $test->assertsCount
);
echo PHP_EOL;
exit(0);

Ejecutarlo:

⇒  php -f StackNoPHPUnitTest.php

Resultados:

⇒  php -f StackNoPHPUnitTest.php
StackNoPHPUnitTest success (1 test, 5 assertions)

Conclusión

Gracias a phpunit podemos crear de forma "fácil", extendiendo el TestCase, objetos que ejercen otros objetos (los testean). No tenemos que crear y MANTENER nuestros propios runner y reporter de resultados.

Parte negativa

Debemos seguir las convenciones de phpunit, esto significa crear más archivos y más clases, lo que incrementa la complejidad del proyecto y el coste de escribir tests.

Seria interesante ver como lo hacen otras librerias, si hay alguna que requiera menos estructura y esfuerzo a la hora de escribir tests.