Nuevo en Symfony 2.8: Creando tests sensibles al tiempo
Los denominados "transient tests" son aquellos tests que fallan aleatoriamente dependiendo de factores externos sobre los que no tienes ningún control, como por ejemplo la carga de CPU del sistema en el que se ejecutan. Estos tests son muy peligrosos porque hacen que tu suite de tests no sea fiable (¿los tests fallan por mi culpa o es un error transitorio que se corregirá solo?).
Los tests relacionados con el tiempo son algunos de los tests más comunes de este tipo. Considera por ejemplo el siguiente test:
use Symfony\Component\Stopwatch\Stopwatch; class MyTest extends \PHPUnit_Framework_TestCase { public function testSomething() { $stopwatch = new Stopwatch(); $stopwatch->start(); sleep(10); $duration = $stopwatch->stop(); $this->assertEquals(10, $duration); } }
Se trata de un test tan sencillo que parece imposible que pueda fallar. Sin
embargo, si el ordenador está muy cargado, la variable $duration
puede ser
10.00000023
en vez de los 10
exactos que esperamos y este error fallaría sin
ningún motivo aparente.
Estos errores son comunes cuando utilizas servicios de integración continua públicos, como Travis CI, donde la carga de los servidores suele ser muy alta. De hecho, en Symfony tenemos un issue para capturar todos estos errores y tratar de solucionarlos.
Manipulando el tiempo
Para solucionar todos estos problema relacionados con el tiempo, el
PHPUnit Bridge ahora incluye una
clase llamada ClockMock
. Esta clase reemplaza las funciones time()
,
microtime()
, sleep()
y usleep()
de PHP por sus propias funciones.
Como las nuevas funciones se llaman igual y funcionan de la misma manera,
cuando utilices ClockMock
no hace falta que cambies nada en tu código
original. La única excepción es si utilizas new DateTime()
, que debes cambiarlo
por DateTime::createFromFormat('U', time())
para que use la función time()
nueva.
Después, añade una anotación @group
especial en aquellos tests en los que
quieras manipular el tiempo y usar estas nuevas funciones:
/** * @group time-sensitive */ class MyTest extends \PHPUnit_Framework_TestCase { public function testSomething() { $stopwatch = new Stopwatch(); $stopwatch->start(); sleep(10); $duration = $stopwatch->stop(); $this->assertEquals(10, $duration); } }
¡Y ya está! Este test ya nunca volverá a fallar por culpa de las diferencias de
tiempo. La llamada a sleep(10)
ahora siempre devuelve 10 segundos exactos,
por lo que el test siempre será correcto.
Una ventaja añadida de utilizar la clase ClockMock
es que el tiempo transcurre
instantáneamente. Si usas la función sleep(10)
original de PHP, tu test
esperará 10 segundos (más o menos). Sin embargo, la función sleep()
de la
clase ClockMock
avanza el tiempo el número de segundos indicado sin tener que
esperar. Así que este test ahora se ejecutará 10 segundos más rápido.
Otras formas de activar la manipulación el tiempo
La anotación @group time-sensitive
es la forma recomendada de usar la clase
ClockMock
, pero en tu aplicación podrías tener que usarla de otra manera. La
anotación funciona "por convención", ya que supone que el namespace de la
clase que se testea se puede obtener simplemente eliminando la parte \Tests\
del namespace de la clase de test.
Aunque esta es la práctica recomendada para aplicaciones Symfony, a lo mejor
tu aplicación no la sigue. En ese caso, puedes optar por configurar los
namespaces de las clases a manipular mediante el archivo de configuración
phpunit.xml
. Esto es por ejemplo lo que hacemos en el componente HttpKernel
de Symfony:
<listeners> <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener"> <arguments> <array> <element><string>Symfony\Component\HttpFoundation</string></element> </array> </arguments> </listener> </listeners>
También puedes activar esta funcionalidad de manera explícita. Para ello, llama
al método \Symfony\Bridge\PhpUnit\ClockMock::register()
dentro del setupBeforeClass()
de tu test y pásale la FQCN de la clase a manipular. Después, activa o desactiva
esta funcionalidad pasando un argumento booleano al método ClockMock::withClockMock()
.
Esta funcionalidad ha sido creada por Nicolas Grekas en el Pull Request #16194.
Fuente: New in Symfony 2.8: Clock mocking and time sensitive tests
Comentarios
Proyectos Symfony destacados
La plataforma de eCommerce 100% Symfony que rivaliza con Magento y PrestaShop. Ver más
Síguenos en @symfony_es para acceder a las últimas noticias.