Construir casos de prueba eficaces de forma sencilla

Construir casos de prueba eficaces ayuda a que tu código funcione correctamente en diferentes escenarios. Permite detectar errores antes de hacer la entrega final de tu código. Ayuda a validar requisitos, mejora la comprensión del problema y fomenta el pensamiento crítico. También ayuda a evitar errores cuando haces cambios en tu código incluyendo o modificando funcionalidades existentes. Finalmente, simplifica el mantenimiento y aumenta la confianza en la calidad del software.

Qué es un caso de prueba

Un caso de prueba es un ejemplo concreto que usamos para verificar si una parte del código funciona correctamente. Cada caso de prueba debe incluir:

  • una entrada: datos que damos al programa,
  • un resultado esperado: lo que debería devolver
  • el resultado real: lo que realmente devuelve al ejecutarlo.

Para que sea eficaz, un caso de prueba debe cumplir los siguientes requisitos:

  1. Claridad: Ser fácil de entender y describir qué está probando.
  2. Relevancia: Probar algo importante, como un requisito o posible error.
  3. Variedad: Considerar entradas normales, casos extremos y datos inesperados.
  4. Previsibilidad: Tener un resultado esperado claro y comprobable.

A continuación te muestro una guía sencilla y práctica sobre cómo puedes construir intuitivamente casos de prueba eficaces.

1. Entender los requisitos del problema

  • Pregunta clave: ¿Qué se espera que haga el código?
  • Divide los requisitos en casos específicos y explícitos. Por ejemplo, si una función debe verificar si un número es primo:
    • Debe devolver true para números primos (2, 3, 5…).
    • Debe devolver false para números no primos (4, 6, 8…).
    • Debe manejar correctamente casos extremos como 0, 1 o números negativos.

2. Identificar tipos de entradas

Considera todos los distintos tipos de entradas para cubrir posibles escenarios:

  • Entradas válidas comunes: Valores que cumplen las condiciones esperadas.
  • Casos extremos o límites: Valores mínimos, máximos o límites cercanos. Por ejemplo, si el código maneja edades, probar con 0, 1, y el máximo permitido (e.g., 120).
  • Entradas inválidas: Datos inesperados como null, cadenas vacías o números fuera del rango.

3. Comparar resultados esperados y reales

  • Antes de ejecutar el código debes ser capaz de predecir el resultado esperado de cada caso.
  • Compara la salida real con la esperada para identificar errores.

4. Usar una tabla para planificar pruebas

Organizar las pruebas en una tabla te ayudará a estructurar el razonamiento:

EntradaDescripciónResultado esperadoResultado real
"Password1"Contraseña válidatrue?
"pass1"Muy cortafalse?
nullValor nulofalse?

5. Escribir pruebas con propósito

Cada caso de prueba debe:

  • Verificar un aspecto del código (e.g., longitud, mayúsculas, etc.).
  • Ser claro y conciso. Un caso = una prueba.

6. Pensar en «qué pasa si…»

  • ¿Qué pasa si la entrada está vacía?
  • ¿Qué pasa si el número es negativo?
  • ¿Qué pasa si hay caracteres especiales?

Esto fomenta el pensamiento crítico y anticipa errores.


7. Usar la regla del 80/20

  • El 80% de los errores suelen detectarse con el 20% de las pruebas más obvias.
  • Asegúrate de cubrir los casos principales antes de preocuparte por escenarios raros.

8. Revisar y ajustar las pruebas

  • Si el código cambia, los casos de prueba deben actualizarse para reflejar los nuevos requisitos.
  • Revisa las pruebas para encontrar posibles errores o escenarios faltantes.

Ejemplo aplicado: Función que verifica si un número es par

Requisitos:

  • Devuelve true si el número es par.
  • Devuelve false si es impar.

Ten en cuenta que, para crear los casos de prueba siguiendo esta guía, no es necesario conocer el código. Estamos creando casos de prueba de caja negra.

Casos de prueba organizados:

EntradaDescripciónResultado esperado
2Número partrue
3Número imparfalse
0Caso extremo (par)true
-2Número par negativotrue
-3Número impar negativofalse

Ten en

javaCopiar código
public class TestEven {    public static void main(String[] args) {
        System.out.println(isEven(2));  // true
        System.out.println(isEven(3));  // false
        System.out.println(isEven(0));  // true
        System.out.println(isEven(-2)); // true
        System.out.println(isEven(-3)); // false
    }

    public static boolean isEven(int number) {
        return number % 2 == 0;
    }
}

Ejemplo aplicado: Validar contraseñas

La siguiente función estática verifica si una contraseña es suficientemente fuerte.

Código base:

public class PasswordValidator {
    public static boolean isValidPassword(String password) {
        if (password == null || password.length() < 8) {
            return false;
        }
        boolean hasUppercase = false;
        boolean hasLowercase = false;
        boolean hasDigit = false;

        for (char c : password.toCharArray()) {
            if (Character.isUpperCase(c)) hasUppercase = true;
            if (Character.isLowerCase(c)) hasLowercase = true;
            if (Character.isDigit(c)) hasDigit = true;
        }

        return hasUppercase && hasLowercase && hasDigit;
    }

Definir casos de prueba:

EntradaDescripciónResultado esperadoResultado real
"Password1"Contraseña válidatrue?
"Test1234"Contraseña válidatrue?
"Pass1"Muy cortafalse?
"password"Solo minúsculasfalse?
"PASSWORD"Solo mayúsculasfalse?
"12345678"Solo númerosfalse?
nullValor nulofalse?

Probar el código haciendo uso de la consola:

Ahora vamos a implementar distintos casos de prueba usando mensajes por consola:

public static void main(String[] args) {
    // Casos de prueba
    System.out.println("Testing valid passwords:");
    System.out.println("Password1: " + isValidPassword("Password1")); // true
    System.out.println("Test1234: " + isValidPassword("Test1234"));   // true

    System.out.println("\nTesting invalid passwords:");
    System.out.println("Pass1: " + isValidPassword("Pass1"));         // false
    System.out.println("password: " + isValidPassword("password"));   // false
    System.out.println("PASSWORD: " + isValidPassword("PASSWORD"));   // false
    System.out.println("12345678: " + isValidPassword("12345678"));   // false
    System.out.println("null: " + isValidPassword(null));             // false
}

Cómo funciona:

  1. Casos de prueba predefinidos: Los casos están escritos directamente en el método main. Puedes observar los resultados esperados en los comentarios y compararlos con la salida real.
  2. Pruebas paso a paso: ejecuta el programa varias veces. Comprueba cómo cambia el resultado modificando las entradas en el método main.
  3. Resultados en consola: la salida del programa muestra claramente qué contraseñas pasan y cuáles fallan.

Variaciones para el ejercicio:

  1. Extender el validador: modifica el método isValidPassword para agregar nuevas reglas, como exigir caracteres especiales (!, @, #, etc.).
  2. Crear más casos de prueba: escribe más llamadas a isValidPassword con contraseñas diferentes para comprobar su robustez.
  3. Depuración guiada: si alguna contraseña válida no pasa la validación, usa impresiones adicionales dentro del bucle. Así podrás observar el estado de las variables hasUppercase, hasLowercase y hasDigit.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.