Pruebas

Higgs se ha creado para que las pruebas tanto del marco como de la aplicación sean lo más sencillas posible. Soporte para PHPUnit<https://phpunit.de/> __ está integrado y el marco proporciona una serie de prácticas Métodos de ayuda para que probar cada aspecto de su aplicación sea lo más sencillo posible.

Configuración del sistema

Instalación de PHPUnit

Higgs usa PHPUnit<https://phpunit.de/> __ como base para todas sus pruebas. Hay dos formas de instalar PHPUnit para usar dentro de su sistema.

Compositor

El método recomendado es instalarlo en su proyecto usando Composer<https://getcomposer.org/> __. Si bien es posible Para instalarlo globalmente no lo recomendamos, ya que puede causar problemas de compatibilidad con otros proyectos en su sistema a medida que pasa el tiempo.

Asegúrese de tener Composer instalado en su sistema. Desde la raíz del proyecto (el directorio que contiene el directorios de aplicación y sistema) escriba lo siguiente desde la línea de comando:

el compositor requiere --dev phpunit/phpunit

Esto instalará la versión correcta para su versión actual de PHP. Una vez hecho esto, puede ejecutar todos los pruebas para este proyecto escribiendo:

proveedor/bin/phpunit

Si está utilizando Windows, utilice el siguiente comando:

proveedor\bin\phpunit

far

La otra opción es descargar el archivo .phar desde PHPUnit<https://phpunit.de/getting-started/phpunit-9.html> __ sitio. Este es un archivo independiente que debe colocarse dentro de la raíz de su proyecto.

Probando su aplicación

Configuración de PHPUnit

En la raíz de su proyecto Higgs, se encuentra el archivo phpunit.xml.dist. Este controla las pruebas unitarias de su aplicación. Si proporciona su propio phpunit.xml, anulará esto.

De forma predeterminada, los archivos de prueba se colocan en el directorio pruebas en la raíz del proyecto.

La clase de prueba

Para aprovechar las herramientas adicionales proporcionadas, sus pruebas deben extenderse Higgs\Prueba\CIUnitTestCase.

No existen reglas sobre cómo se deben colocar los archivos de prueba. Sin embargo, recomendamos que usted establece reglas de ubicación de antemano para que pueda comprender rápidamente dónde se encuentran los archivos de prueba.

En este documento, colocaremos los archivos de prueba correspondientes a las clases en el directorio app en el directorio tests/app. Para probar una nueva biblioteca, app/Libraries/Foo.php, crearías un nuevo archivo en pruebas/app/Libraries/FooTest.php:

<?php

namespace App\Libraries;

use Higgs\Test\CIUnitTestCase;

class FooTest extends CIUnitTestCase
{
    public function testFooNotBar()
    {
        // ...
    }
}

Para probar uno de tus modelos, app/Models/UserMode.php, podrías terminar con algo como esto en tests/app/Models/UserModelTest.php:

<?php

namespace App\Models;

use Higgs\Test\CIUnitTestCase;

class UserModelTest extends CIUnitTestCase
{
    public function testFooNotBar()
    {
        // ...
    }
}

Puede crear cualquier estructura de directorio que se ajuste a sus necesidades/estilo de prueba. Al asignar espacios a los nombres de las clases de prueba, Recuerde que el directorio app es la raíz del espacio de nombres App, por lo que cualquier clase que utilice debe tener el espacio de nombres correcto en relación con App.

Nota

Namespaces are not strictly required for test classes, but they are helpful to ensure no class names collide.

Al probar los resultados de la base de datos, debe utilizar DatabaseTestTrait en tu clase.

Puesta en escena

La mayoría de las pruebas requieren cierta preparación para ejecutarse correctamente. TestCase de PHPUnit proporciona cuatro métodos para ayudar con la puesta en escena y la limpieza:

función estática pública setUpBeforeClass(): nula
función estática pública tearDownAfterClass(): nula

configuración de función protegida(): nula
función protegida desgarro(): nulo

Los métodos estáticos setUpBeforeClass() y tearDownAfterClass() se ejecutan antes y después de todo el caso de prueba, mientras que los métodos protegidos setUp() y tearDown() se ejecutan. entre cada prueba.

Si implementa alguna de estas funciones especiales, asegúrese de ejecutarlas padre también para que los casos de prueba extendidos no interfieran con la puesta en escena:

<?php

namespace App\Models;

use Higgs\Test\CIUnitTestCase;

final class UserModelTest extends CIUnitTestCase
{
    protected function setUp(): void
    {
        parent::setUp(); // Do not forget

        helper('text');
    }

    // ...
}

Rasgos

Una forma común de mejorar sus pruebas es utilizar rasgos para consolidar la puesta en escena en diferentes Casos de prueba. CIUnitTestCase detectará cualquier rasgo de clase y buscará métodos de preparación para ejecutar el nombre del rasgo en sí (es decir, setUp{NameOfTrait}() y tearDown{NameOfTrait}()).

Por ejemplo, si necesitara agregar autenticación a algún de sus casos de prueba, podría crear un rasgo de autenticación con un método de configuración para falsificar un usuario registrado:

<?php

namespace App\Traits;

trait AuthTrait
{
    protected function setUpAuthTrait()
    {
        $user = $this->createFakeUser();
        $this->logInUser($user);
    }

    // ...
}
<?php

namespace Tests;

use App\Traits\AuthTrait;
use Higgs\Test\CIUnitTestCase;

final class AuthenticationFeatureTest extends CIUnitTestCase
{
    use AuthTrait;

    // ...
}

Afirmaciones adicionales

CIUnitTestCase proporciona afirmaciones de pruebas unitarias adicionales que pueden resultarle útiles.

afirmarLogged($nivel, $mensaje esperado)

Asegúrese de que algo que esperaba que se registrara se haya registrado realmente:

afirmarLogContains($nivel, $logMessage)

Asegúrese de que haya un registro en los registros que contenga una parte del mensaje.

<?php

$config = new \Config\Logger();
$logger = new \Higgs\Log\Logger($config);

// check verbatim the log message
$logger->log('error', "That's no moon");
$this->assertLogged('error', "That's no moon");

// check that a portion of the message is found in the logs
$exception = new \RuntimeException('Hello world.');
$logger->log('error', $exception->getTraceAsString());
$this->assertLogContains('error', '{main}');
afirmarEventoTriggered($nombredelevento)

Asegúrese de que el evento que esperaba que se desencadenara realmente fuera:

<?php

use Higgs\Events\Events;

Events::on('foo', static function ($arg) use (&$result) {
    $result = $arg;
});

Events::trigger('foo', 'bar');

$this->assertEventTriggered('foo');
afirmarHeaderEmitted($encabezado, $ignoreCase = falso)

Asegúrese de que realmente se haya emitido un encabezado o cookie:

<?php

$response->setCookie('foo', 'bar');

ob_start();
$this->response->send();
$output = ob_get_clean(); // in case you want to check the actual body

$this->assertHeaderEmitted('Set-Cookie: foo=bar');

Nota

the test case with this should be run as a separate process en unidad PHP<https://docs.phpunit.de/en/9.6/annotations.html#runinseparateprocess> _.

afirmarHeaderNotEmitted($encabezado, $ignoreCase = falso) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^

Asegúrese de que no se haya emitido un encabezado o cookie:

<?php

$response->setCookie('foo', 'bar');

ob_start();
$this->response->send();
$output = ob_get_clean(); // in case you want to check the actual body

$this->assertHeaderNotEmitted('Set-Cookie: banana');

Nota

the test case with this should be run as a separate process en unidad PHP<https://docs.phpunit.de/en/9.6/annotations.html#runinseparateprocess> _.

afirmarCloseEnough($esperado, $actual, $mensaje = “”, $tolerancia = 1) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^

Para pruebas de tiempo de ejecución extendido, prueba que la diferencia absoluta entre el tiempo esperado y el real está dentro de la tolerancia prescrita:

<?php

use Higgs\Debug\Timer;

$timer = new Timer();
$timer->start('longjohn', strtotime('-11 minutes'));
$this->assertCloseEnough(11 * 60, $timer->getElapsedTime('longjohn'));

La prueba anterior permitirá que el tiempo real sea 660 o 661 segundos.

afirmarCloseEnoughString($esperado, $actual, $mensaje = “”, $tolerancia = 1) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^

Para pruebas de tiempo de ejecución extendido, prueba que la diferencia absoluta entre la hora esperada y la real, formateada como cadenas, está dentro de la tolerancia prescrita:

<?php

use Higgs\Debug\Timer;

$timer = new Timer();
$timer->start('longjohn', strtotime('-11 minutes'));
$this->assertCloseEnoughString(11 * 60, $timer->getElapsedTime('longjohn'));

La prueba anterior permitirá que el tiempo real sea 660 o 661 segundos.

Acceso a propiedades protegidas/privadas

Al realizar pruebas, puede utilizar los siguientes métodos de establecimiento y obtención para acceder a métodos protegidos y privados y propiedades en las clases que está probando.

getPrivateMethodInvoker($instancia, $método)

Le permite llamar a métodos privados desde fuera de la clase. Esto devuelve una función que se puede llamar. La primera El parámetro es una instancia de la clase a probar. El segundo parámetro es el nombre del método que desea llamar.

<?php

use App\Libraries\Foo;

// Create an instance of the class to test
$obj = new Foo();

// Get the invoker for the 'privateMethod' method.
$method = $this->getPrivateMethodInvoker($obj, 'privateMethod');

// Test the results
$this->assertEquals('bar', $method('param1', 'param2'));
getPrivateProperty($instancia, $propiedad)

Recupera el valor de una propiedad de clase privada/protegida de una instancia de una clase. El primer parámetro es un instancia de la clase a probar. El segundo parámetro es el nombre de la propiedad.

<?php

use App\Libraries\Foo;

// Create an instance of the class to test
$obj = new Foo();

// Test the value
$this->assertEquals('bar', $this->getPrivateProperty($obj, 'baz'));
setPrivateProperty($instancia, $propiedad, $valor)

Establezca un valor protegido dentro de una instancia de clase. El primer parámetro es una instancia de la clase a probar. El segundo El parámetro es el nombre de la propiedad para establecer el valor. El tercer parámetro es el valor para establecerlo:

<?php

use App\Libraries\Foo;

// Create an instance of the class to test
$obj = new Foo();

// Set the value
$this->setPrivateProperty($obj, 'baz', 'oops!');

// Do normal testing...

Servicios de burla

A menudo encontrará que necesita simular uno de los servicios definidos en app/Config/Services.php para limitar sus pruebas solo al código en cuestión, mientras simula varias respuestas de los servicios. Esto es especialmente Es cierto al probar controladores y otras pruebas de integración. La clase Servicios proporciona los siguientes métodos para simplificar esto.

Servicios::injectMock()

Este método le permite definir la instancia exacta que devolverá la clase Servicios. Puedes usar esto para establecer propiedades de un servicio para que se comporte de cierta manera, o reemplazar un servicio con una clase simulada.

<?php

namespace Tests;

use Higgs\HTTP\CURLRequest;
use Higgs\Test\CIUnitTestCase;
use Config\Services;

final class SomeTest extends CIUnitTestCase
{
    public function testSomething()
    {
        $curlrequest = $this->getMockBuilder(CURLRequest::class)
            ->onlyMethods(['request'])
            ->getMock();
        Services::injectMock('curlrequest', $curlrequest);

        // Do normal testing here....
    }
}

El primer parámetro es el servicio que está reemplazando. El nombre debe coincidir con el nombre de la función en los Servicios. clase exactamente. El segundo parámetro es la instancia con la que reemplazarlo.

Servicios::restablecer()

Elimina todas las clases simuladas de la clase Servicios y la devuelve a su estado original.

También puede utilizar el método $this->resetServices() que proporciona CIUnitTestCase.

Nota

This method resets the all states of Services, and the RouteCollection no tendrá rutas. Si desea utilizar sus rutas para cargar, debe llame al método loadRoutes() como Services::routes()->loadRoutes().

Servicios::resetSingle(cadena $nombre)

Elimina cualquier instancia simulada y compartida de un único servicio, por su nombre.

Nota

The Cache, Email and Session services are mocked by default to prevent intrusive testing behavior. To prevent these from mocking remove their method callback from the class property: $setUpMethods = ['mockEmail', 'mockSession'];

Burlarse de instancias de fábrica

De manera similar a Servicios, es posible que necesite proporcionar una instancia de clase preconfigurada durante las pruebas que se utilizarán con Factories. Utilice las mismas Factories::injectMock() y Factories::reset() métodos estáticos como Servicios, pero toman un parámetro anterior adicional para el Nombre del componente:

<?php

namespace Tests;

use App\Models\UserModel;
use Higgs\Config\Factories;
use Higgs\Test\CIUnitTestCase;
use Tests\Support\Mock\MockUserModel;

final class SomeTest extends CIUnitTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        $model = new MockUserModel();
        Factories::injectMock('models', UserModel::class, $model);
    }
}

Nota

All component Factories are reset by default between each test. Modify your test case’s $setUpMethods if you need instances to persist.

Pruebas y tiempo

Probar código que depende del tiempo puede ser un desafío. Sin embargo, al utilizar el Clase Hora, la hora actual se puede fijar o cambiar a voluntad durante la prueba.

A continuación se muestra un código de prueba de muestra que fija la hora actual:

<?php

namespace Tests;

use Higgs\I18n\Time;
use Higgs\Test\CIUnitTestCase;

final class TimeDependentCodeTest extends CIUnitTestCase
{
    protected function tearDown(): void
    {
        parent::tearDown();

        // Reset the current time.
        Time::setTestNow();
    }

    public function testFixTime(): void
    {
        // Fix the current time to "2023-11-25 12:00:00".
        Time::setTestNow('2023-11-25 12:00:00');

        // This assertion always passes.
        $this->assertSame('2023-11-25 12:00:00', (string) Time::now());
    }
}

Puede fijar la hora actual con el método Time::setTestNow(). Opcionalmente, puede especificar una configuración regional como segundo parámetro.

No olvide restablecer la hora actual después de la prueba llamándola sin parámetros.

Prueba de salida CLI

StreamFilterRasgo

Nuevo en la versión 7.0.0.

StreamFilterTrait proporciona una alternativa a estos métodos auxiliares.

Es posible que necesite probar cosas que son difíciles de probar. A veces, capturar una secuencia, como el propio STDOUT o STDERR de PHP, podría ser útil. StreamFilterTrait le ayuda a capturar el resultado de la secuencia de su elección.

Resumen de métodos

  • StreamFilterTrait::getStreamFilterBuffer() Obtiene los datos capturados del búfer.

  • StreamFilterTrait::resetStreamFilterBuffer() Restablecer los datos capturados.

Un ejemplo que demuestra esto dentro de uno de sus casos de prueba:

<?php

namespace Tests;

use Higgs\CLI\CLI;
use Higgs\Test\CIUnitTestCase;
use Higgs\Test\StreamFilterTrait;

final class SomeTest extends CIUnitTestCase
{
    use StreamFilterTrait;

    public function testSomeOutput(): void
    {
        CLI::write('first.');

        $this->assertSame("\nfirst.\n", $this->getStreamFilterBuffer());

        $this->resetStreamFilterBuffer();

        CLI::write('second.');

        $this->assertSame("second.\n", $this->getStreamFilterBuffer());
    }
}

El StreamFilterTrait tiene un configurador que se llama automáticamente. Ver Prueba de rasgos .

Si anula los métodos setUp() o tearDown() en su prueba, entonces debe llamar a parent::setUp() y Métodos parent::tearDown() respectivamente para configurar StreamFilterTrait.

CITestStreamFilter

CITestStreamFilter para uso manual/único.

Si necesita capturar transmisiones en una sola prueba, en lugar de usar el rasgo StreamFilterTrait, puede hacerlo manualmente. agregar un filtro a las transmisiones.

Resumen de métodos

  • CITestStreamFilter::registration() Registro de filtro.

  • CITestStreamFilter::addOutputFilter() Agregar un filtro al flujo de salida.

  • CITestStreamFilter::addErrorFilter() Agregando un filtro al flujo de errores.

  • CITestStreamFilter::removeOutputFilter() Eliminando un filtro del flujo de salida.

  • CITestStreamFilter::removeErrorFilter() Eliminando un filtro de la secuencia de errores.

<?php

namespace Tests;

use Higgs\CLI\CLI;
use Higgs\Test\CIUnitTestCase;
use Higgs\Test\Filters\CITestStreamFilter;

final class SomeTest extends CIUnitTestCase
{
    public function testSomeOutput(): void
    {
        CITestStreamFilter::registration();
        CITestStreamFilter::addOutputFilter();

        CLI::write('first.');

        CITestStreamFilter::removeOutputFilter();
    }
}

Prueba de entrada CLI

PHPStreamWrapper

Nuevo en la versión 7.0.0.

PhpStreamWrapper proporciona una forma de escribir pruebas para métodos que requieren la entrada del usuario. como CLI::prompt(), CLI::wait() y CLI::input().

Nota

The PhpStreamWrapper is a stream wrapper class. Si no conoce el contenedor de flujo de PHP, ver La clase streamWrapper<https://www.php.net/manual/en/class.streamwrapper.php> _ en el manual de PHP.

Resumen de métodos

  • PhpStreamWrapper::register() Registre PhpStreamWrapper en el protocolo php.

  • PhpStreamWrapper::restore() Restaura el contenedor del protocolo php nuevamente al contenedor integrado de PHP.

  • PhpStreamWrapper::setContent() Establece los datos de entrada.

Importante

The PhpStreamWrapper is intended for only testing php://stdin. Pero cuando lo registras, maneja todo el protocolo php<https://www.php.net/manual/en/wrappers.php.php> _ corrientes, como php://stdout, php://stderr, php://memory. Por lo tanto, se recomienda encarecidamente registrar o cancelar el registro de PhpStreamWrapper. sólo cuando sea necesario. De lo contrario, interferirá con otras secuencias PHP integradas. mientras está registrado.

Un ejemplo que demuestra esto dentro de uno de sus casos de prueba:

<?php

namespace Tests;

use Higgs\CLI\CLI;
use Higgs\Test\CIUnitTestCase;
use Higgs\Test\PhpStreamWrapper;

final class SomeTest extends CIUnitTestCase
{
    public function testPrompt(): void
    {
        // Register the PhpStreamWrapper.
        PhpStreamWrapper::register();

        // Set the user input to 'red'. It will be provided as `php://stdin` output.
        $expected = 'red';
        PhpStreamWrapper::setContent($expected);

        $output = CLI::prompt('What is your favorite color?');

        $this->assertSame($expected, $output);

        // Restore php protocol wrapper.
        PhpStreamWrapper::restore();
    }
}