14. Pruebas positivas, negativas y casos borde

14.1 Introducción

Cuando escribimos pruebas unitarias, no alcanza con probar solo el caso más cómodo. Una unidad puede comportarse bien con entradas normales y fallar con entradas inválidas o valores justo en el límite de una regla.

Una forma práctica de organizar los casos es distinguir entre pruebas positivas, pruebas negativas y casos borde.

En este tema veremos qué significa cada categoría, cómo elegir ejemplos útiles y cómo evitar una cantidad excesiva de pruebas sin criterio.

14.2 Qué es una prueba positiva

Una prueba positiva verifica que la unidad funcione correctamente cuando recibe datos válidos o condiciones esperadas.

Ejemplos:

  • Una edad válida permite registrarse.
  • Un descuento válido se aplica correctamente.
  • Una contraseña que cumple las reglas es aceptada.
  • Un retiro menor al saldo se realiza correctamente.

Las pruebas positivas confirman el camino esperado del comportamiento.

14.3 Ejemplo de prueba positiva

Supongamos que una persona puede registrarse si tiene al menos 18 años.

def puede_registrarse(edad):
    return edad >= 18


def test_persona_de_20_anios_puede_registrarse():
    assert puede_registrarse(20) == True

Esta es una prueba positiva porque usa una edad válida y espera que la operación sea aceptada.

14.4 Qué es una prueba negativa

Una prueba negativa verifica que la unidad rechace correctamente datos inválidos o condiciones no permitidas.

Ejemplos:

  • Una edad menor a la permitida no puede registrarse.
  • Una contraseña demasiado corta es rechazada.
  • Un retiro mayor al saldo lanza un error.
  • Un porcentaje de descuento negativo no se acepta.

Las pruebas negativas son importantes porque el software no solo debe funcionar con datos correctos; también debe manejar datos incorrectos de forma segura y previsible.

14.5 Ejemplo de prueba negativa

Siguiendo con la regla de edad, una persona de 17 años no debería poder registrarse.

def test_persona_de_17_anios_no_puede_registrarse():
    assert puede_registrarse(17) == False

Esta prueba comprueba que la unidad rechaza un caso no permitido.

14.6 Qué es un caso borde

Un caso borde, o caso límite, se encuentra justo alrededor del punto donde una regla cambia de resultado.

Si la regla dice "a partir de 18", los valores cercanos son 17, 18 y 19. Si una contraseña debe tener al menos 8 caracteres, los valores cercanos son 7, 8 y 9 caracteres.

Los errores suelen aparecer en los bordes porque allí se usan comparaciones como >, >=, < y <=.

14.7 Ejemplo de casos borde

Para la regla de edad mínima de 18 años, podemos probar los valores alrededor del límite.

def test_edad_17_no_puede_registrarse():
    assert puede_registrarse(17) == False


def test_edad_18_puede_registrarse():
    assert puede_registrarse(18) == True


def test_edad_19_puede_registrarse():
    assert puede_registrarse(19) == True

La prueba más importante suele ser la del valor exacto del límite, en este caso 18. Los valores vecinos ayudan a confirmar la transición.

14.8 Positivo, negativo y borde en una misma regla

Un mismo conjunto de pruebas puede incluir las tres categorías.

Caso Tipo Resultado esperado
Edad 20 Positivo Puede registrarse.
Edad 17 Negativo y borde inferior cercano No puede registrarse.
Edad 18 Borde exacto y positivo Puede registrarse.

Las categorías no siempre son excluyentes. Un caso puede ser positivo y borde al mismo tiempo.

14.9 Ejemplo con contraseña

Supongamos que una contraseña debe tener al menos 8 caracteres.

def password_tiene_longitud_valida(password):
    return len(password) >= 8


def test_password_de_7_caracteres_no_es_valido():
    assert password_tiene_longitud_valida("abcdefg") == False


def test_password_de_8_caracteres_es_valido():
    assert password_tiene_longitud_valida("abcdefgh") == True


def test_password_de_9_caracteres_es_valido():
    assert password_tiene_longitud_valida("abcdefghi") == True

Estos casos prueban justo alrededor del límite de longitud. Si el código usa una comparación incorrecta, alguno de ellos fallará.

14.10 Ejemplo con monto mínimo

Ahora pensemos en una regla de envío gratis para compras de 5000 o más.

def tiene_envio_gratis(total):
    return total >= 5000


def test_total_4999_no_tiene_envio_gratis():
    assert tiene_envio_gratis(4999) == False


def test_total_5000_tiene_envio_gratis():
    assert tiene_envio_gratis(5000) == True


def test_total_5001_tiene_envio_gratis():
    assert tiene_envio_gratis(5001) == True

El valor 5000 es el borde exacto. Probarlo evita ambigüedades sobre si la regla incluye o excluye el límite.

14.11 Pruebas negativas con excepciones

Una prueba negativa puede esperar un valor de retorno o una excepción. Depende del diseño de la unidad.

import pytest


def retirar(saldo, importe):
    if importe > saldo:
        raise ValueError("Saldo insuficiente")
    return saldo - importe


def test_retirar_mas_del_saldo_lanza_error():
    with pytest.raises(ValueError):
        retirar(100, 150)

Este caso negativo espera una excepción porque la operación no debe aceptarse.

14.12 Pruebas negativas con valores de retorno

En otros diseños, una entrada inválida devuelve un valor especial.

def obtener_descuento(porcentaje):
    if porcentaje < 0:
        return 0
    return porcentaje


def test_descuento_negativo_devuelve_cero():
    assert obtener_descuento(-10) == 0

La prueba sigue siendo negativa porque usa un dato inválido, aunque el comportamiento esperado no sea una excepción.

14.13 No probar solo el camino feliz

El "camino feliz" es el caso donde todo sale bien: datos válidos, condiciones correctas y resultado esperado. Es necesario probarlo, pero no alcanza.

Si solo probamos el camino feliz, pueden quedar defectos importantes en:

  • Entradas inválidas.
  • Valores mínimos y máximos.
  • Campos vacíos.
  • Errores esperados.
  • Condiciones justo en el límite.

Una unidad robusta debe comportarse bien tanto cuando se la usa correctamente como cuando recibe datos problemáticos.

14.14 No probar todas las combinaciones sin criterio

El extremo opuesto también es un problema. Intentar probar todas las combinaciones posibles puede generar una suite enorme, lenta y difícil de mantener.

La clave es elegir casos representativos:

  • Un caso positivo típico.
  • Un caso negativo importante.
  • Los valores alrededor de los límites.
  • Casos especiales que históricamente generaron errores.

No buscamos cantidad por cantidad. Buscamos cubrir riesgos relevantes.

14.15 Valores especiales frecuentes

Además de positivos, negativos y bordes, conviene prestar atención a valores especiales:

  • Cero.
  • Uno.
  • Números negativos.
  • Texto vacío.
  • Texto con espacios.
  • Lista vacía.
  • Valor máximo permitido.
  • Valor mínimo permitido.

Estos valores suelen descubrir suposiciones ocultas en el código.

14.16 Ejemplo con lista vacía

Una función que calcula el promedio de una lista debe definir qué ocurre si la lista está vacía.

def promedio(numeros):
    if len(numeros) == 0:
        return 0
    return sum(numeros) / len(numeros)


def test_promedio_de_lista_vacia_es_cero():
    assert promedio([]) == 0


def test_promedio_de_lista_con_numeros():
    assert promedio([10, 20, 30]) == 20

El primer caso prueba una situación especial. El segundo prueba el comportamiento normal.

14.17 Tabla de selección de casos

Tipo de caso Qué verifica Ejemplo
Positivo La unidad acepta datos válidos. Edad 20 puede registrarse.
Negativo La unidad rechaza datos inválidos. Edad 17 no puede registrarse.
Borde inferior Valor justo antes del límite. 4999 para mínimo 5000.
Borde exacto Valor del límite definido. 5000 para mínimo 5000.
Borde superior cercano Valor justo después del límite. 5001 para mínimo 5000.
Especial Valor que suele generar errores. Lista vacía o texto vacío.

14.18 Cómo nombrar estos casos

El nombre de la prueba debe dejar claro qué tipo de caso estamos verificando.

def test_total_4999_no_tiene_envio_gratis():
    assert tiene_envio_gratis(4999) == False


def test_total_5000_tiene_envio_gratis():
    assert tiene_envio_gratis(5000) == True

Los valores aparecen en el nombre porque son importantes para entender el límite. Esto mejora la lectura y el diagnóstico cuando una prueba falla.

14.19 Lista de comprobación

Al elegir casos para una unidad, revisa:

  • ¿Hay al menos un caso positivo representativo?
  • ¿Hay un caso negativo importante?
  • ¿Se probaron los límites de las reglas?
  • ¿El valor exacto del límite está cubierto?
  • ¿Existen valores especiales como cero, vacío o lista vacía?
  • ¿Las pruebas elegidas cubren riesgos reales sin multiplicar casos innecesarios?

14.20 Qué debes recordar de este tema

  • Las pruebas positivas verifican datos válidos y caminos esperados.
  • Las pruebas negativas verifican rechazo de datos inválidos o condiciones no permitidas.
  • Los casos borde están alrededor del punto donde una regla cambia.
  • Un mismo caso puede ser positivo y borde al mismo tiempo.
  • No conviene probar solo el camino feliz.
  • Tampoco conviene probar combinaciones sin criterio.
  • Los nombres de pruebas deben indicar claramente el caso elegido.

14.21 Conclusión

Clasificar casos en positivos, negativos y borde ayuda a diseñar pruebas unitarias más completas. Esta clasificación obliga a pensar no solo en lo que debería funcionar, sino también en lo que debería rechazarse y en los límites exactos de cada regla.

El objetivo no es escribir muchas pruebas, sino elegir casos que aporten información. Una buena selección de casos detecta errores importantes con una suite clara y mantenible.

En el próximo tema veremos cómo seleccionar casos de prueba relevantes de forma más general, considerando riesgo, intención y valor de cada prueba.