El desarrollo guiado por pruebas (TDD) es una filosofía que impulsa a escribir pruebas antes del código. Esto no solo asegura software funcional, sino que promueve un diseño limpio y mantenible. A continuación, exploraremos cómo aplicarlo en PHP, Node.js y Python con ejemplos concisos.
1. Divide y vencerás
Antes de empezar, identifica funcionalidades mínimas. No intentes abordar todo de golpe; resuelve problemas pequeños y específicos.
Ejemplo en PHP:
Queremos implementar una función para sumar dos números.
Test inicial:
class CalculatorTest extends PHPUnit\Framework\TestCase {
public function testSumTwoNumbers() {
$this->assertEquals(5, Calculator::sum(2, 3));
}
}
Código mínimo para pasar el test:
class Calculator {
public static function sum($a, $b) {
return $a + $b;
}
}
Ejemplo en Node.js (con Jest):
Queremos verificar que una función de suma funcione correctamente.
Test inicial:
test("sum of 2 and 3 should return 5", () => {
const sum = require("./calculator").sum;
expect(sum(2, 3)).toBe(5);
});
Código mínimo:
module.exports.sum = (a, b) => a + b;
Ejemplo en Python (con pytest):
Queremos implementar la misma función básica de suma.
Test inicial:
def test_sum_two_numbers():
from calculator import sum
assert sum(2, 3) == 5
Código mínimo:
def sum(a, b):
return a + b
2. Escribe el test más simple posible
Comienza con lo trivial. Resolver primero los casos simples permite construir una base sólida.
Ejemplo en PHP:
Para una función que verifica si un número es par.
Test inicial:
class MathTest extends PHPUnit\Framework\TestCase {
public function testIsEvenNumber() {
$this->assertTrue(Math::isEven(2));
}
}
Código mínimo:
class Math {
public static function isEven($num) {
return $num % 2 === 0;
}
}
Ejemplo en Node.js:
Queremos comprobar si un número es par.
Test inicial:
test("2 should be even", () => {
const isEven = require("./math").isEven;
expect(isEven(2)).toBe(true);
});
Código mínimo:
module.exports.isEven = (num) => num % 2 === 0;
Ejemplo en Python:
El mismo caso aplicado en Python.
Test inicial:
def test_is_even_number():
from math_utils import is_even
assert is_even(2) is True
Código mínimo:
def is_even(num):
return num % 2 == 0
3. Refactoriza después de que pase el test
Cuando el test pasa, mejora el código sin alterar su funcionalidad.
Ejemplo en Node.js:
Después de cubrir casos simples, refactoriza para generalizar.
Nuevo test para múltiples casos:
test("isEven should return correct results", () => {
const isEven = require("./math").isEven;
expect(isEven(2)).toBe(true);
expect(isEven(3)).toBe(false);
expect(isEven(0)).toBe(true);
});
Refactorización (si fuera necesario):
module.exports.isEven = (num) => {
return num % 2 === 0;
};
El código ya es óptimo, no requiere cambios.
4. Cubre casos límite uno a la vez
Agrega tests para situaciones excepcionales o límites, pero no te apresures a abordarlas todas a la vez.
Ejemplo en PHP:
Validar divisiones, asegurándote de manejar divisiones por cero.
Test para caso límite:
class MathTest extends PHPUnit\Framework\TestCase {
public function testDivisionByZeroThrowsException() {
$this->expectException(DivisionByZeroError::class);
Math::divide(10, 0);
}
}
Código necesario:
class Math {
public static function divide($a, $b) {
if ($b === 0) {
throw new DivisionByZeroError();
}
return $a / $b;
}
}
5. Resiste la tentación de sobrescribir
Escribe solo el código necesario para superar los tests actuales. No anticipes requisitos futuros.
Ejemplo incorrecto en Python:
def sum(a, b, c=0, d=0):
return a + b + c + d # C y D no son necesarios aún.
Ejemplo correcto:
def sum(a, b):
return a + b
Este es solo el inicio del camino hacia un desarrollo más sólido y eficiente mediante TDD. En próximos artículos, profundizaremos en patrones avanzados, buenas prácticas y cómo integrar TDD en equipos ágiles. ¡No te lo pierdas!