# ¿Y si tus pruebas necesitan ser probadas?

Una técnica de 1971 estuvo cincuenta años guardada porque su cuello de botella era juicio humano a escala. Eso es justo lo que cambió.

16 ago 2026 · https://e1i0.com/es/tus-pruebas-necesitan-ser-probadas.html

---

Siempre pasa lo mismo. Escribes pruebas, la cobertura sube, el dashboard se pone verde, y en algún momento te preguntas si esas pruebas son buenas o solo te están dando una sensación de seguridad.

Quise responderme eso. Hoy se escribe y se revisa código mucho más rápido con un modelo al lado, y lo difícil pasó a ser saber si lo que sale de ahí está bien.

Tienes esta función:

```python
def suma(a, b):
    return a + b
```

Y esta prueba:

```python
assert suma(2, 2) == 4
```

Pasa. Cobertura 100%.

Ahora cambia el `+` por un `*`. `2 * 2 = 4`. **La prueba sigue pasando.**

Tu suite no distingue una suma de una multiplicación. Y tampoco de una potencia, porque `2² = 4`.

## Quién vigila a los vigilantes

Una prueba afirma algo: *"si el código deja de hacer X, yo fallo"*. Esa afirmación casi nunca se verifica.

Es un detector de humo. Lo instalas y no suena. El silencio puede ser que no hay incendio o que la pila está muerta, y desde afuera se ven igual. Para saberlo hay que apretar el botón de prueba, que hace un incendio falso a propósito.

## Mutation testing es ese botón

La idea la propuso [Richard Lipton en 1971](https://en.wikipedia.org/wiki/Mutation_testing), siendo estudiante, y DeMillo, Lipton y Sayward la desarrollaron y publicaron en 1978. Es mecánica: la herramienta rompe una sola cosa de tu código, corre tu suite y anota si alguien se dio cuenta.

![El ciclo de la mutación: se rompe una cosa, se corre la suite, y el mutante queda muerto o vivo](https://e1i0.com/assets/images/mutation-testing-ciclo.png)

Cada ruptura es un mutante. La función de arriba genera seis, uno por cada operador que puede reemplazar al `+`.

![Los seis mutantes de suma(a, b) contra dos casos de prueba: con suma(2, 2) sobreviven el producto y la potencia, con suma(2, 3) mueren todos](https://e1i0.com/assets/images/mutation-testing-suma.png)

## Por qué estuvo cincuenta años guardada

Corres la herramienta sobre un archivo real y te devuelve seiscientos sobrevivientes, cada uno sin explicación. Ahí empieza el trabajo pesado, porque hay que leerlos uno por uno y decidir si falta una prueba, si es código muerto, o si es un cambio que no altera el comportamiento y da igual que sobreviva. Eso son días de una persona por cada módulo, y sospecho que por ahí va buena parte de que la técnica se quedara tanto tiempo en la academia.

**Y eso es lo que cambió.** Corrí ~3.400 mutantes sobre seis módulos de [Frauddi](https://frauddi.com) en una tarde. Le pasé los resultados a un modelo y me los agrupó: cuáles apuntaban a lo mismo, cuáles podía ignorar, qué revisar primero. El criterio lo puse yo, pero llegar a lo que importaba pasó de días a minutos.

## Qué encontró

Pruebas que no comprobaban lo que yo creía. Tres ejemplos, todos míos.

**La prueba que espera el valor por defecto.** Si una función termina devolviendo un valor por defecto y tu prueba espera justo ese valor, rompas lo que rompas ahí dentro la prueba pasa igual.

```python
def nivel(puntos):
    if puntos > 100:
        return "oro"
    if puntos > 50:
        return "plata"
    return "bronce"

assert nivel(10) == "bronce"
```

Esa prueba solo recorre la última línea. Cambia los `>` por `>=` y sigue en verde.

**El límite que nadie comprobaba.** Escribí 54 casos para un detector. Mis dos casos de una señal estaban tan lejos del límite que la activa que ninguno lo comprobaba.

```python
def es_sospechoso(ratio):
    return ratio > 0.20

assert es_sospechoso(0.71) is True
assert es_sospechoso(0.02) is False
```

Mueve ese `0.20` al `0.50` y las dos pruebas siguen pasando. Podía ponerlo donde quisiera con los 54 casos en verde.

**La operación que daba igual.** Una línea suma dos números, y en todas mis pruebas uno de ellos valía cero.

```python
def posicion(inicio, n):
    return inicio + n

assert posicion(0, 3) == 3
```

Con `inicio` en cero, cambiar ese `+` por `|` o por `^` da el mismo resultado. Es el `suma(2, 2)` del principio, en producción y escrito por mí.

De ahí salió la única regla que me llevo: **si sobrevive una operación matemática, revisa si tu caso de prueba la vuelve irrelevante.**

## El indicador que más me sirvió

Una función donde **no muere ni un solo mutante**: la puedes romper entera y nadie se queja. Apareció tres veces y las tres había algo que limpiar:

- Una función a la que ya nadie llamaba.
- Otra que la prueba ejecutaba sin comprobar nada de lo que devolvía.
- Una tercera que siempre recibía el mismo valor.

Esas pruebas las escribió un modelo y yo las di por buenas al revisarlas. Se leían bien y la cobertura subía, y ninguno de los dos se dio cuenta de que no estaban comprobando nada.

## Después de la primera pasada

Eso fue la primera pasada. Después extendí el ejercicio al resto de la aplicación, con [pruebas de punta a punta](https://es.wikipedia.org/wiki/Pruebas_de_software), [fuzzing](https://es.wikipedia.org/wiki/Fuzzing), [unitarias](https://es.wikipedia.org/wiki/Prueba_unitaria) e [integración](https://es.wikipedia.org/wiki/Pruebas_de_integraci%C3%B3n). De todas las técnicas, la mutación fue la que más sacó a la luz.

## Cuándo vale la pena

Sale caro en cómputo. Cada mutante es una corrida completa de la suite, y hacen falta miles para que aparezca un puñado. No va en CI, y no la corrí sobre todo el código.

Y tiene un orden. Primero subes cobertura, después mides si sirvió. Una línea sin cobertura tiene todos sus mutantes vivos por definición, así que medirla no te dice nada.

## Lo que esto dice de los LLMs

Se habla mucho de que los modelos nos van a traer técnicas nuevas. Aquí pasó otra cosa: **una técnica de hace cincuenta años se volvió práctica sin cambiar nada.**

Vale la pena mirar hacia atrás con eso en mente. Hay ideas que se archivaron porque pedían demasiadas horas de alguien leyendo y clasificando, y justo en esa parte un modelo te puede ayudar bastante.

Si te da curiosidad, agarra un módulo, uno solo, del código que más te importe, y córrele mutación. Lo que salga te va a decir más sobre tus pruebas que cualquier porcentaje de cobertura.
