Lección 3: TDD
TDD (Test Driven Development) es la práctica de programación sobre testing más utilizada por desarrolladores. Consiste en seguir un diseño trabajo opuesto al testing tradicional escribiendo primero la prueba y solo añadir nuevo código si falla. Posiblemente no tendrás claro su razón de ser por lo abstracto de su naturaleza. Veamos el ciclo con la creación de una espada.
- Creamos la funda donde debe encajar.
- Forjamos una espada e intentamos introducirla. La primera ocasión no lo hará.
- Realizamos los golpes mínimos para darle la forma adecuada.
- Volvemos a intentar encajarla. En caso de que continúe sin entrar seguimos trabajando sobre ella.
- Ya entra suavemente.
- Refactorizamos quitando las impurezas, marcas del trabajo y le damos brillo.
Flujo
Más que un framework TDD es una metodología o guia para realizar buen testing. Posee un flujo de trabajo muy estricto que nos empuja a dar una prioridad mayor a la prueba que al código, donde en cada pasada se crea otra prueba hasta que no puedes añadir más validaciones.
- Creas un test.
- Ejecutas todos los test anteriores y el nuevo. En la primera ocasión fallará.
- Escribes un simple código que lo pase.
- Ejecuta todos los test. Si falla volvemos al paso anterior.
- Refactoriza ejecuta los test en cada cambio: moviendo el código a donde debe estar, quitando código repetido, documentando, dividiendo las funciones en otras más pequeñas, etc. Si falla volvemos al paso anterior.
- Creamos el test de la próxima nueva característica, volviendo a empezar el ciclo.
Ejemplo
Vamos a escribir un método que nos diga el estado del agua (sólido, líquido o gaseoso) dependiendo de la temperatura que le demos como parámetro.
1. Inicias una función
Creo un archivo llamado Agua.php
con mi código mínimo.
<?php
//======================================================================
// Clase para Agua
//======================================================================
class Agua
{
//-----------------------------------------------------
// GET
//-----------------------------------------------------
/**
* Método que te indica el estado del agua
* @param {float} $temperatura - Temperatura
* @return {string} - 'Sólido', 'Líquido' o 'Gaseoso'. Devuelve NULL si tiene un argumento inválido.
*/
function getEstado(float $temperatura): string
{
return '';
}
}
2. Creas el test.
Creo un archivo llamado AguaTest.php
con un test. Es este caso comprobaremos si nos devuelve Sólido entre -273 grados centígrados (el mínimo) y 0.
<?php
use PHPUnit\Framework\TestCase;
require_once('Agua.php');
class AguaTest extends TestCase
{
private $miAgua = null;
public function setUp(): void
{
$this->miAgua = new Agua();
}
public function testSolido(): void
{
foreach (range(-273, 0) as $temperatura) {
$estado = $this->miAgua->getEstado($temperatura);
$this->assertSame('Sólido', $estado, "Temperatura $temperatura no es Sólida");
}
}
}
3. Ejecutas el test.
./phpunit AguaTest.php
4. ¿Falla? Siguiente punto.
5. Refactorizas o cambias el código.
Refactorizo mi código para que pase el test.
<?php
//======================================================================
// Clase para Agua
//======================================================================
class Agua
{
//-----------------------------------------------------
// GET
//-----------------------------------------------------
/**
* Método que te indica el estado del agua
* @param {float} $temperatura - Temperatura
* @return {string} - 'Sólido', 'Líquido' o 'Gaseoso'. Devuelve NULL si tiene un argumento inválido.
*/
function getEstado(float $temperatura): string
{
if ($temperatura <= 0) return 'Sólido';
}
}
6. Ejecutas el test.
./phpunit AguaTest.php
¡Lo pasa! Ahora hago el siguiente test. ¿Líquido?
<?php
use PHPUnit\Framework\TestCase;
require_once('Agua.php');
class AguaTest extends TestCase
{
private $miAgua = null;
public function setUp(): void
{
$this->miAgua = new Agua();
}
public function testSolido(): void
{
foreach (range(-273, 0) as $temperatura) {
$estado = $this->miAgua->getEstado($temperatura);
$this->assertSame('Sólido', $estado, "Temperatura $temperatura no es Sólida");
}
}
public function testLiquida(): void
{
foreach (range(1, 99) as $temperatura) {
$estado = $this->miAgua->getEstado($temperatura);
$this->assertSame('Líquido', $estado, "Temperatura $temperatura no es Líquido");
}
}
}
Ejecuto el test…
./phpunit AguaTest.php
…y falla, lógico. Refactorizamos de nuevo.
<?php
//======================================================================
// Clase para Agua
//======================================================================
class Agua
{
//-----------------------------------------------------
// GET
//-----------------------------------------------------
/**
* Método que te indica el estado del agua
* @param {float} $temperatura - Temperatura
* @return {string} - 'Sólido', 'Líquido' o 'Gaseoso'. Devuelve NULL si tiene un argumento inválido.
*/
function getEstado(float $temperatura): string
{
if ($temperatura <= 0) return 'Sólido';
if (0 < $temperatura && $temperatura < 100) return 'Líquido';
}
}
Ejecuto otra vez el test.
./phpunit AguaTest.php
¡Lo pasa! A por el siguiente test… la rueda continua girando hasta tener todos.
7. ¿No puedes hacer más test? Siguiente punto.
Mis pruebas están completas.
<?php
use PHPUnit\Framework\TestCase;
require_once('Agua.php');
class AguaTest extends TestCase
{
private $miAgua = null;
public function setUp(): void
{
$this->miAgua = new Agua();
}
public function testSolido(): void
{
foreach (range(-273, 0) as $temperatura) {
$estado = $this->miAgua->getEstado($temperatura);
$this->assertSame('Sólido', $estado, "Temperatura $temperatura no es Sólida");
}
}
public function testLiquida(): void
{
foreach (range(1, 99) as $temperatura) {
$estado = $this->miAgua->getEstado($temperatura);
$this->assertSame('Líquido', $estado, "Temperatura $temperatura no es Líquido");
}
}
public function testGaseoso(): void
{
foreach (range(100, 500) as $temperatura) {
$estado = $this->miAgua->getEstado($temperatura);
$this->assertSame('Gaseoso', $estado, "Temperatura $temperatura no es Gaseoso");
}
}
public function testTipoFloatOInt(): void
{
$estado = $this->miAgua->getEstado($temperatura);
$this->assertSame('Gaseoso', $estado, "Temperatura $temperatura no es Gaseoso");
}
}
8. Fin de la función.
Mi clase esta terminada y testeada.
<?php
//======================================================================
// Clase para Agua
//======================================================================
class Agua
{
//-----------------------------------------------------
// GET
//-----------------------------------------------------
/**
* Método que te indica el estado del agua
* @param {float} $temperatura - Temperatura
* @return {string} - 'Sólido', 'Líquido' o 'Gaseoso'. Devuelve NULL si tiene un argumento inválido.
*/
function getEstado(float $temperatura): string
{
if (is_float($temperatura) || is_int($temperatura)) {
if ($temperatura <= 0) return 'Sólido';
if (0 < $temperatura && $temperatura < 100) return 'Líquido';
if (100 <= $temperatura) return 'Gaseoso';
} else {
return NULL;
}
}
}
JavaScript
Al igual que PHP debemos utilizar alguna herramienta. La más conocida en el ecosistema JavaScript es Jest
.
La instalamos.
npm install jest
Definimos un fichero de JavaScript
llamado operaciones.js
. Será el código que queremos testear. Dentro albergará 2 funciones.
function sumar(num1, num2) {
return num1 + num2;
}
function restar(num1, num2) {
return num1 - num2;
}
// Exportamos para que pueda ser invocado desde otros lugares
module.exports = {
sumar,
restar
};
Ahora vamos a crear los tests. Creamos un fichero llamado operaciones.test.js
con el siguiente contenido.
const {sumar, restar} = require('./operaciones');
test('1 + 2 debe ser 3', () => {
expect(sumar(1, 2)).toBe(3);
});
test('5 - 1 debe ser 4', () => {
expect(restar(5, 1)).toBe(4);
});
Modificamos package.json
indicando como debe ejecutar el testing.
{
"scripts": {
"test": "jest"
}
}
Y ejecutamos.
npm run test
Si hubiera algún problema nos lo marcaría en rojo con la descripción de lo que esperaba y lo que se ha encontrado.
Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.
¿Me invitas a un café? ☕
Puedes hacerlo usando el terminal.
ssh customer@andros.dev -p 5555
Comentarios
Nuevo comentario
Nueva replica {{ formatEllipsisAuthor(replyComment.author) }}
Escribe el primer comentario
{{ comments.length }} comentarios