Nivel inicial16 min de lectura · Publicado el

Pruebas unitarias o de unidad: qué son, ejemplo y FIRST

Prueba de unidad: tres bombillas probadas una a una, con la que falla marcada, frente a una lámpara entera apagada en la que no se ve qué pieza falla. Si falla, sabes qué piezarubentejera.com prueba de unidadsabes cuál falla?integración¿qué pieza falló?
Cada bombilla probada por separado es una prueba de unidad: si una no se enciende, sabes cuál es. La lámpara entera es la prueba de integración: también hace falta, pero cuesta más saber qué pieza falló.

Las pruebas unitarias, también llamadas pruebas de unidad o tests unitarios, son pruebas automáticas que comprueban por separado una pieza pequeña del código, como una función o una clase, y dicen en milisegundos si hace lo que debe.

Cambias una línea y no sabes qué más se ha roto. O tienes tests, pero cada vez que tocas el código fallan diez y tardas más en arreglarlos que en programar. Las dos cosas tienen la misma causa: pruebas que no ayudan.

Este artículo resume las ideas del capítulo 9 de Clean Code, de Robert C. Martin, dedicado a las pruebas de unidad, y las aplica a un ejemplo ejecutado en JavaScript. Las explicaciones son nuestras; no son citas del libro. Forma parte de la guía de código limpio (clean code), con todos los principios y un ejemplo de cada uno.

Qué son las pruebas unitarias

Una prueba unitaria comprueba una parte pequeña del programa por separado: una función, un método o una clase. Le das unos datos de entrada y comprueba que el resultado es el esperado. Se ejecuta sola, sin que nadie tenga que mirar la pantalla.

Es como probar cada bombilla antes de montar la lámpara: si algo falla, sabes exactamente qué pieza es.

Ejemplo de prueba unitaria en JavaScript

Un ejemplo. Esta función aplica un 10 % de descuento los sábados y domingos:

// precio.js
export function precioFinal(importe, fecha) {
  const esFinDeSemana = [0, 6].includes(fecha.getDay()); // 0 = domingo, 6 = sábado
  return esFinDeSemana ? importe * 0.9 : importe;
}

Y estas son sus pruebas de unidad, con el ejecutor de tests que ya trae Node (node:test), sin instalar nada:

// precio.test.js
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { precioFinal } from './precio.js';

// Fechas fijas: el test da lo mismo hoy que dentro de un año.
const SABADO = new Date(2026, 10, 7); // los meses empiezan en 0: 10 es noviembre
const LUNES = new Date(2026, 10, 9);

describe('precioFinal', () => {
  it('aplica un 10 % de descuento en fin de semana', () => {
    assert.equal(precioFinal(100, SABADO), 90);
  });

  it('cobra el precio completo entre semana', () => {
    assert.equal(precioFinal(100, LUNES), 100);
  });
});

Se ejecutan con node --test:

node --test precio.test.js

Salida real con Node.js 24.21.0:

▶ precioFinal
  ✔ aplica un 10 % de descuento en fin de semana (0.687556ms)
  ✔ cobra el precio completo entre semana (0.163709ms)
✔ precioFinal (1.914119ms)
ℹ tests 2
ℹ suites 1
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 74.838576

Si prefieres Jest, la estructura es la misma. Las partes de un test (describe, it, preparar y comprobar) en Composición de un test en TDD.

El mismo test unitario en Java con JUnit 6

En Java, la herramienta habitual es JUnit. La misma regla, con LocalDate, que no tiene el problema de los meses que empiezan en 0:

// Precio.java
import java.time.DayOfWeek;
import java.time.LocalDate;

public class Precio {
    public static double precioFinal(double importe, LocalDate fecha) {
        DayOfWeek dia = fecha.getDayOfWeek();
        boolean esFinDeSemana = dia == DayOfWeek.SATURDAY || dia == DayOfWeek.SUNDAY;
        return esFinDeSemana ? importe * 0.9 : importe;
    }
}
// PrecioTest.java
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.time.LocalDate;
import org.junit.jupiter.api.Test;

class PrecioTest {
    // Fechas fijas: el test da lo mismo hoy que dentro de un año.
    static final LocalDate SABADO = LocalDate.of(2026, 11, 7);
    static final LocalDate LUNES = LocalDate.of(2026, 11, 9);

    @Test
    void aplicaUn10PorCientoDeDescuentoEnFinDeSemana() {
        assertEquals(90.0, Precio.precioFinal(100, SABADO), 0.001);
    }

    @Test
    void cobraElPrecioCompletoEntreSemana() {
        assertEquals(100.0, Precio.precioFinal(100, LUNES), 0.001);
    }
}

El tercer argumento de assertEquals, 0.001, es el margen de error permitido. Con números decimales (double), las cuentas pueden arrastrar errores de redondeo minúsculos (0.1 + 0.2 da 0.30000000000000004), así que se comparan con un margen.

Sin Maven ni Gradle, basta el lanzador de consola de JUnit (junit-platform-console-standalone-6.1.3.jar, de Maven Central): se compila y se ejecuta así:

javac -d out -cp junit-platform-console-standalone-6.1.3.jar Precio.java PrecioTest.java
java -jar junit-platform-console-standalone-6.1.3.jar execute --class-path out --select-class PrecioTest --details=tree --disable-banner

Salida real con JUnit 6.1.3 y OpenJDK 25.0.5 (recortada al árbol de tests):

╷
├─ JUnit Platform Suite ✔
├─ JUnit Jupiter ✔
│  └─ PrecioTest ✔
│     ├─ cobraElPrecioCompletoEntreSemana() ✔
│     └─ aplicaUn10PorCientoDeDescuentoEnFinDeSemana() ✔
└─ JUnit Vintage ✔

En un proyecto real, JUnit se añade como dependencia de Maven o Gradle y los tests se lanzan con mvn test o gradle test.

Qué es una «unidad»: pruebas solitarias y sociables

No hay una definición única. Martin Fowler, en su artículo «UnitTest», explica que el término está mal definido y que lo que se repite en casi todas las definiciones es esto: la prueba se fija en una parte pequeña del sistema, la escribe quien programa con sus herramientas de siempre y es mucho más rápida que las demás.

Qué es una unidad depende del equipo. En código orientado a objetos suele ser una clase; en código funcional, una función. Fowler cuenta que a menudo trata como una sola unidad un grupo de clases muy relacionadas.

Una distinción más importante, según Fowler, es si la prueba es solitaria o sociable (términos que, según Fowler, acuñó Jay Fields).

  • Solitaria: la pieza se prueba sola. Si usa otras clases (colaboradores), se sustituyen por dobles de prueba, piezas falsas que responden lo que el test necesita. Si falla, el error está casi siempre en la pieza probada (o en cómo se ha configurado el doble).
  • Sociable: la pieza se prueba con sus colaboradores reales. El test sigue siendo de una unidad, porque comprueba su comportamiento y da por buenos los demás.

Es la diferencia entre probar el motor de un coche en un banco de pruebas, con todo lo demás simulado, o probarlo montado con su batería y su depósito de verdad. En los dos casos estás probando el motor.

Fowler explica que el estilo mockist exige pruebas solitarias y el clásico prefiere las sociables, y que incluso en el clásico se usan dobles cuando la colaboración es incómoda, como con un servicio remoto. El ejemplo de precioFinal no tiene colaboradores: la fecha se le pasa como dato, y por eso no necesita ningún doble.

Diferencia entre prueba unitaria y prueba de integración

Prueba unitaria Prueba de integración
Qué prueba Una unidad: una función, una clase o un grupo pequeño de clases relacionadas, con colaboradores reales o dobles Tu código junto con lo que no controlas (base de datos, ficheros, red u otro servicio) o varios módulos ya montados
Velocidad Milisegundos Más lenta: hay que preparar el entorno
Si falla Señala la pieza Sabes que algo no encaja, pero hay que buscar dónde
Cuántas Muchas Menos

Volviendo al símil: la prueba unitaria es probar cada bombilla; la de integración, encender la lámpara entera. Hacen falta las dos. Los demás niveles y cuántos tests conviene escribir de cada uno están en tipos de pruebas de software y la pirámide de tests. Y si las pruebas unitarias se escriben antes que el código, es TDD (desarrollo dirigido por tests).

Las tres leyes de TDD

En TDD (Test-Driven Development, desarrollo guiado por pruebas) escribes primero la prueba de unidad y después el código de producción que la hace pasar. Robert C. Martin lo resume en tres leyes. Esta es nuestra traducción de su artículo «The Cycles of TDD»:

  1. No escribas código de producción sin tener antes un test que falle.
  2. No escribas más test del necesario para que falle. Que no compile también cuenta como fallo.
  3. No escribas más código de producción del necesario para que pase el test que está fallando.

Las tres juntas te obligan a avanzar a pasos muy cortos: un poco de test, un poco de código, y vuelta a empezar. El ciclo completo está en TDD: desarrollo dirigido por test.

Pruebas limpias: el test también es código

Tener pruebas sucias es igual o peor que no tener ninguna. El motivo es sencillo: las pruebas tienen que cambiar a la vez que el código.

Cuando modificas el código de producción, algunos tests antiguos empiezan a fallar y hay que actualizarlos. Y ahí está el problema:

  • Cuanto menos limpias son las pruebas, más cuesta cambiarlas.
  • Cuanto más enrevesado es el código de los tests, más tiempo pasas entendiéndolos y modificándolos, y menos creando código nuevo.

Al final, el equipo deja de mantenerlos, los borra, y vuelve a tener miedo de tocar nada. Si los tests se rompen con cada refactor aunque el programa siga funcionando, el problema suele ser otro: están acoplados a cómo está hecho el código. Lo explica, con un ejemplo en Java, tests frágiles: por qué se rompen al refactorizar.

El código de los tests es tan importante como el de producción.

Cómo se consigue una prueba limpia

En una prueba de unidad, que se lea bien es igual o más importante que en el código de producción. Un test es también documentación: cuenta qué hace el código con un ejemplo concreto.

Para que una prueba sea legible tiene que ser:

  • Clara: se entiende qué se prueba sin leer nada más.
  • Simple: sin bucles, condiciones ni lógica que haya que descifrar.
  • Densa en expresión: dice mucho con pocas líneas. Si preparar los datos ocupa veinte, sácalo a una función con un nombre que se entienda.

Además, cada test debe probar un único concepto y ser independiente de los demás.

Mira la diferencia con el mismo código:

Test sucio

  • Se llama test1: no dice qué comprueba
  • Mezcla dos conceptos en un solo test
  • Usa la fecha de hoy: acepta 100 o 90, así que no comprueba nada
  • Lee la fecha de un texto y depende de tu zona horaria

Test limpio

  • El nombre explica la regla de negocio
  • Un concepto por test
  • Fechas fijas con nombre: SABADO, LUNES
  • Da el mismo resultado en cualquier máquina

Este es el test sucio:

// sucio.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { precioFinal } from './precio.js';

test('test1', () => {
  const r = precioFinal(100, new Date());
  assert.ok(r === 100 || r === 90);
  assert.equal(precioFinal(50, new Date('2026-11-07')), 45);
});

En Madrid pasa. Pero new Date('2026-11-07') es la medianoche del 7 de noviembre en hora UTC, y en México todavía es viernes 6 por la tarde. Lo hemos ejecutado cambiando solo la zona horaria:

TZ=Europe/Madrid node --test sucio.test.js
✔ test1 (1.105112ms)
TZ=America/Mexico_City node --test sucio.test.js
✖ test1 (1.403012ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  50 !== 45

El código no ha cambiado, y el test falla. Es justo lo que hace que un equipo deje de fiarse de sus pruebas. El test limpio de arriba pasa en las dos zonas horarias.

Las pruebas mejoran el código de producción

Las pruebas de unidad hacen que el código de producción sea flexible, mantenible y reutilizable. ¿Por qué?

Si tienes pruebas, no tienes miedo a cambiar el código. Sin ellas, cada cambio da miedo, porque puedes introducir errores que nadie detectará. Con ellas puedes mejorar la arquitectura y el diseño con tranquilidad: si rompes algo, te avisan en segundos.

Es como la red de un trapecista: no le hace mejor acróbata, pero le permite intentar el salto. El símil tiene un límite: la red solo cubre donde está tendida. Los tests solo protegen lo que comprueban; un error en una parte sin pruebas pasará igual.

Las 5 reglas FIRST de las pruebas limpias

FIRST es un acrónimo en inglés. Cada letra es una propiedad que debe cumplir un buen test:

Letra En inglés Qué significa En el ejemplo
F Fast Rapidez: se ejecutan en segundos. Si tardan, dejarás de lanzarlas a menudo. Dos tests en menos de 3 ms
I Independent Independencia: un test no depende de otro. Si uno falla, no arrastra a los demás y puedes ejecutarlos en cualquier orden. Cada test crea lo que necesita
R Repeatable Repetición: dan el mismo resultado en cualquier entorno: tu portátil, el de tu compañero o el servidor de integración continua. Si no, siempre tendrás una excusa para su fallo. Fechas fijas en vez de la fecha de hoy
S Self-validating Validación automática: el resultado es sí o no, pasa o falla. Nadie tiene que leer un log para decidirlo. assert.equal decide por ti
T Timely Puntualidad: se escriben justo antes del código de producción que los hace pasar. Si los escribes después, puede que ese código ya sea difícil de probar. Es lo que pide TDD

El test sucio incumple la R (falla según la zona horaria) y, en la práctica, la S: aceptar «100 o 90» no comprueba nada.

Para comprobar la independencia, Node puede ejecutar los tests en orden aleatorio con node --test --test-randomize. Si alguno falla solo en cierto orden, depende de otro. En Node 24 esta opción está en fase inicial de desarrollo, así que úsala como comprobación, no como base de tu integración continua.

Pruebas unitarias escritas por un agente de IA

Este apartado es criterio propio del enfoque IA Limpia, no algo que diga Clean Code.

Un agente de IA escribe pruebas unitarias en segundos, y eso tienta a aceptarlas sin leerlas. Pero un test generado puede tener los mismos defectos que el test sucio de arriba: nombres vacíos, varios conceptos mezclados, la fecha de hoy o comprobaciones que aceptan cualquier resultado. Y un test que pasa siempre da una falsa sensación de seguridad, como un detector de humo sin pila.

Dos criterios separan un test útil de uno de adorno:

Que falle primero. Un test que nunca has visto en rojo no ha demostrado nada. Si el agente escribe el test antes que el código, debe fallar al principio. Si el código ya existe, rómpelo a propósito y comprueba que el test lo detecta. Con el ejemplo de arriba, quitando el descuento (esFinDeSemana ? importe : importe), Node 24.21.0 da (salida recortada, sin el detalle del fallo ni la traza):

▶ precioFinal
  ✖ aplica un 10 % de descuento en fin de semana (1.35557ms)
  ✔ cobra el precio completo entre semana (0.258929ms)
✖ precioFinal (2.697165ms)
ℹ tests 2
ℹ suites 1
ℹ pass 1
ℹ fail 1

El test del fin de semana detecta el fallo. Si los dos siguieran en verde, ese test no protegería nada.

Que no compruebe la implementación. Un test debe comprobar qué hace el código (el resultado, la regla de negocio), no cómo lo hace por dentro: qué funciones privadas llama, en qué orden o con qué variables. Si no, se rompe con cada refactor aunque el programa funcione, como se explica en tests frágiles. Hay un caso peor: que el agente copie la fórmula del código en el test (assert.equal(precioFinal(100, SABADO), 100 * 0.9)). Si la fórmula está mal, test y código fallan igual y el test sigue en verde. El valor esperado se escribe a mano: 90.

Para el resto, la misma lista que para cualquier test:

  1. ¿El nombre de cada test explica una regla, no un detalle de implementación?
  2. ¿Prueba un solo concepto?
  3. ¿Depende de la fecha, la hora, la red o el orden de ejecución?
  4. ¿Falla si rompes a propósito el código que prueba?

Cómo revisar el resto del cambio que trae el agente, empezando por sus tests, está en cómo revisar código generado por IA.

Fuentes

Comprobado en noviembre de 2026:

  • Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship (Prentice Hall, 2008), capítulo 9, «Unit Tests». Las ideas se resumen con nuestras palabras; no se cita el texto literal.
  • Robert C. Martin, «The Cycles of TDD» (17 de diciembre de 2014): enunciado de las tres leyes de TDD. La traducción es nuestra.
  • Tim Ottinger, «FIRST», en Agile in a Flash (febrero de 2009): origen del acrónimo, atribuido a Ottinger y Brett Schuchert.
  • Martin Fowler, «UnitTest» (5 de mayo de 2014): el término es difuso, qué es una unidad y pruebas solitarias y sociables. Resumido con nuestras palabras.
  • Documentación de node:test para Node 24: describe e it, búsqueda de ficheros de test y estabilidad de la ejecución en orden aleatorio.
  • JUnit 6.1.3 en Maven Central: lanzador de consola junit-platform-console-standalone.

Los ejemplos de JavaScript se han ejecutado el 14 de noviembre de 2026 con Node.js 24.21.0 en Linux, con node --test y la variable TZ para cambiar la zona horaria (Europe/Madrid y America/Mexico_City). El de Java, con JUnit 6.1.3 y OpenJDK 25.0.5 en la imagen oficial eclipse-temurin:25 de Docker.