Cuando una unidad acepta muchos valores posibles, no podemos probarlos todos. Las clases de equivalencia nos ayudan a agrupar valores que deberían comportarse de la misma manera.
La idea es elegir uno o pocos valores representativos de cada grupo. Si todos los valores de una clase deberían producir el mismo tipo de resultado, probar un representante puede ser suficiente para esa clase.
Esta técnica permite diseñar pruebas unitarias más inteligentes: menos casos, pero mejor seleccionados.
Una clase de equivalencia es un conjunto de valores que, según la regla que estamos probando, deberían recibir el mismo tratamiento.
Por ejemplo, si una persona puede registrarse desde los 18 años, podemos agrupar edades así:
Dentro de la primera clase, 10, 15 y 17 deberían comportarse igual. Dentro de la segunda, 18, 25 y 60 también deberían comportarse igual.
La función podría ser:
def puede_registrarse(edad):
return edad >= 18
Podemos seleccionar un representante de cada clase:
def test_edad_menor_a_18_no_puede_registrarse():
assert puede_registrarse(15) == False
def test_edad_mayor_o_igual_a_18_puede_registrarse():
assert puede_registrarse(25) == True
Estas pruebas cubren las dos clases generales. Luego podemos agregar casos límite como 17 y 18, que estudiaremos más en el próximo tema.
Las clases de equivalencia pueden ser válidas o inválidas.
| Tipo | Significado | Ejemplo |
|---|---|---|
| Clase válida | Conjunto de valores que la unidad debe aceptar. | Edad 18 o mayor. |
| Clase inválida | Conjunto de valores que la unidad debe rechazar o manejar como error. | Edad negativa o edad menor a 18, según la regla. |
Es importante cubrir ambos tipos. Probar solo clases válidas deja sin verificar cómo se manejan datos incorrectos.
Para encontrar clases de equivalencia, primero leemos la regla. Luego preguntamos qué grupos de datos deberían comportarse igual.
Ejemplo de regla: "una contraseña es válida si tiene al menos 8 caracteres".
Clases posibles:
Con esta clasificación ya podemos elegir representantes.
def password_tiene_longitud_valida(password):
return len(password) >= 8
def test_password_corta_no_es_valida():
assert password_tiene_longitud_valida("abc") == False
def test_password_larga_es_valida():
assert password_tiene_longitud_valida("abcdefghi") == True
La prueba con "abc" representa la clase de contraseñas cortas. La prueba con "abcdefghi" representa la clase de contraseñas válidas.
Después podemos agregar los valores de borde 7 y 8 si queremos comprobar el límite exacto.
Una clase de equivalencia no es un único valor. Es un grupo de valores que se consideran similares para la regla.
Por ejemplo, si la regla solo distingue si una edad es menor o mayor a 18, entonces 20, 30 y 40 pertenecen a la misma clase válida. No hace falta probar todos esos valores si no hay otra condición que los diferencie.
La técnica nos ayuda a evitar pruebas duplicadas.
Supongamos esta regla:
def obtener_descuento(tipo_cliente):
if tipo_cliente == "vip":
return 15
if tipo_cliente == "empleado":
return 20
return 0
Aquí cada tipo de cliente con resultado diferente puede considerarse una clase relevante.
def test_cliente_comun_no_tiene_descuento():
assert obtener_descuento("comun") == 0
def test_cliente_vip_tiene_descuento_15():
assert obtener_descuento("vip") == 15
def test_cliente_empleado_tiene_descuento_20():
assert obtener_descuento("empleado") == 20
def test_tipo_de_cliente_desconocido_no_tiene_descuento():
assert obtener_descuento("invitado") == 0
Cada prueba representa una clase de comportamiento. No necesitamos probar muchos textos desconocidos si todos se tratan igual, salvo que existan reglas adicionales.
Muchas reglas dividen valores numéricos en rangos. Cada rango puede ser una clase de equivalencia.
def clasificar_monto(monto):
if monto < 1000:
return "bajo"
if monto <= 10000:
return "medio"
return "alto"
Clases:
def test_monto_bajo():
assert clasificar_monto(500) == "bajo"
def test_monto_medio():
assert clasificar_monto(5000) == "medio"
def test_monto_alto():
assert clasificar_monto(12000) == "alto"
Estos casos representan cada rango. Los valores exactos de borde, como 999, 1000, 10000 y 10001, se analizan con la técnica de valores límite.
Las colecciones también pueden dividirse en clases. Por ejemplo, una función que calcula el promedio puede tener estas clases:
def promedio(numeros):
if len(numeros) == 0:
return 0
return sum(numeros) / len(numeros)
Estas clases son relevantes porque la lista vacía requiere un tratamiento distinto.
def test_promedio_de_lista_vacia_es_cero():
assert promedio([]) == 0
def test_promedio_de_lista_con_un_elemento():
assert promedio([10]) == 10
def test_promedio_de_lista_con_varios_elementos():
assert promedio([10, 20, 30]) == 20
Cada prueba representa una clase diferente de entrada. Esto da más información que probar varias listas de tres elementos sin una razón específica.
Además de entradas válidas, debemos identificar clases inválidas si la unidad tiene responsabilidad de rechazarlas.
Ejemplo: una cantidad debe ser positiva.
def validar_cantidad(cantidad):
if cantidad <= 0:
raise ValueError("La cantidad debe ser mayor que cero")
return True
Clases:
import pytest
def test_cantidad_positiva_es_valida():
assert validar_cantidad(5) == True
def test_cantidad_cero_lanza_error():
with pytest.raises(ValueError):
validar_cantidad(0)
def test_cantidad_negativa_lanza_error():
with pytest.raises(ValueError):
validar_cantidad(-3)
Estas pruebas cubren la clase válida y dos clases inválidas. Cero y negativo pueden separarse porque muchas reglas tratan el cero como un caso especial.
Estos pasos ayudan a evitar tanto la falta de pruebas como el exceso de pruebas repetidas.
| Regla | Clases de equivalencia | Representantes |
|---|---|---|
| Edad mínima 18 | Menor a 18, mayor o igual a 18 | 15, 25 |
| Password mínimo 8 caracteres | Corta, válida | "abc", "abcdefghi" |
| Monto bajo, medio, alto | <1000, 1000-10000, >10000 | 500, 5000, 12000 |
| Promedio de lista | Vacía, un elemento, varios elementos | [], [10], [10, 20, 30] |
| Cantidad positiva | Positiva, cero, negativa | 5, 0, -3 |
Al aplicar clases de equivalencia, conviene evitar estos errores:
Antes de finalizar la selección de casos, revisa:
Las clases de equivalencia ayudan a diseñar pruebas unitarias con criterio. En lugar de elegir valores al azar, agrupamos entradas similares y seleccionamos representantes que cubran comportamientos importantes.
Esta técnica reduce pruebas repetidas y mejora la claridad de la suite. Es especialmente útil cuando una unidad acepta muchos valores posibles pero los trata en pocos grupos de comportamiento.
En el próximo tema estudiaremos valores límite, una técnica complementaria que se enfoca en los bordes exactos donde las reglas cambian.