Te decidiste: llegó el momento de implementar phpUnit en proyecto. Emocionante, ¿no? Por fin vas a poder considerarte un desarrollador profesional.
Ya instalaste las herramientas, te leiste unos cuantos tutoriales… todo listo.
Los primeros tests no fueron tan complicados. Llegar al esperado verde costó un poco al comienzo pero lo sacaste.
Y justo cuando la cosa se ponía interesante, te encontrás con:
<?php class MyClass { public function testableMethod(int $param) : int { if (rand(1, 100) < 50) { return $param * 2; } else { return $param * 3; } } }
¿Y ahora? ¿Cómo verificar que el método hace lo que tiene que hacer si el resultado final depende de algo que no podés controlar?
Está claro que TDD no funcionará para este caso… mejor dejar todo esto atrás y seguir trabajando como hasta ahora… al fin y al cabo, tan mal no te ha ido, ¿cierto?
Si estás pensando esto, te propongo que sigas leyendo. Hay una salida a este dilema.
La solución se basa en una característica muy popular, aunque muchas veces no muy comprendida, de la Programación Orientada a Objetos: la herencia.
¿Cómo? ¿Qué tiene que ver la herencia en todo esto? Veámoslo.
Preparando el código para las pruebas unitarias
Tu primera aproximación a un test probablemente se verá similar a:
<?php use PHPUnit\Framework\TestCase; use MyClass; class MyClassTest extends TestCase { public function testTestableMethod() { $sut = new MyClass(); $this->assertEquals(..., $sut->testableMethod(5)); } }
El problema es, precisamente, con qué rellenar los ...
. Pues bien, ahí es donde viene la magia.
¿Qué tal si en lugar de testar directamente MyClass
usaras una instancia de algo muy parecido a
? Por ejemplo, una clase derivada de MyClass
.MyClass
Vamos por partes mejor.
Comencemos por hacer a
un poco más test-friendly. testableMethod
Algo muy sencillo (y 100% seguro) de hacer es aislar la parte del método
que está impidiendo hacer el test. En este caso, el problema es la obtención del número aleatorio (testableMethod
rand(1, 100)
), así que, el primer paso sería hacer una nueva versión del método testableMethod
para que se vea así:
public function testableMethod(int $param) : int { if ($this->getRandomNumber() < 50) { return $param * 2; } else { return $param * 3; } }
Y el nuevo método getRandomNumber
se vería así:
protected function getRandomNumber(): int { return rand(1, 100); }
Es claro que a nivel funcional no ha cambiado nada. Sin embargo, como veremos pronto, esta sutil diferencia es crucial para el desarrollo de la prueba unitaria.
Un punto sumamente interesante es que este cambio está libre de riesgo en tanto que puede ser realizado en forma automática por un IDE.
Continuemos entonces con el test.
¿Qué tal si creamos una nueva clase TestableMyClass
que tenga exactamente el mismo comportamiento que MyClass
, salvo por la forma de responder a getRandomNumber
?
class TestableMyClass extends MyClass { private int $randomNumber; /** * @param int $randomNumber */ public function __construct(int $randomNumber) { $this->randomNumber = $randomNumber; } /** * @return int */ protected function getRandomNumber(): int { return $this->randomNumber; } }
De esta forma tenemos una nueva implementación de MyClass
que, en lugar de retornar números aleatorios, retornará un valor que nosotros controlamos y, de esa forma, podremos realizar el test que buscamos:
class MyClassTest extends TestCase { /** * @dataProvider randomNumberProvider * @param int $baseNumber * @param int $multiplier * @param int $randomNumber * @return void */ public function testTestableMethod(int $baseNumber, int $multiplier, int $randomNumber): void { $sut = new TestableMyClass($randomNumber); $this->assertEquals($baseNumber * $multiplier, $sut->testableMethod($baseNumber)); } /** * @return int[][] */ public function randomNumberProvider(): array { return [ [ 1, 2, 2 ], [ 20, 2, 40 ], [ 51, 3, 153 ], [ 60, 3, 180 ], ]; } }
Y voilà! Tenemos un test que garantiza la correctitud del método.
De hecho, para hacerlo todavía más interesante podríamos usar una clase que genere números aleatorios e inyectarla como colaborador de MyClass
pero bueno… tema para otro post.
- Cómo agregar una página de error 500 en un proyecto PHP - 31/10/2024
- ¿Cuántos contenedoresnecesita tu php? - 28/10/2024
- Cuál es el mejor framework PHP para hacer APIs REST - 25/10/2024