2.08 · Testing: Pruebas de Escritorio y assert
🎯 Objetivo de aprendizaje
Al terminar este tema vas a poder:
- Explicar qué es el testing de software y por qué es la skill #1 de un programador profesional.
- Hacer pruebas de escritorio (trazas) sobre programas con bucles y condicionales.
- Diseñar casos de prueba que cubran casos normales, borde y extremos.
- Usar `assert` para verificar invariantes en C++ y C.
- Escribir tests básicos con assert() para validar funciones.
- Identificar los 3 tipos de errores: compilación, ejecución, lógico.
- Aplicar la técnica "dividir y conquistar" para encontrar bugs en programas grandes.
El testing es lo que separa a un junior que "le funciona en mi máquina" de un junior que "puede demostrar que funciona". En cualquier empresa seria, el testing es OBLIGATORIO.
🧠 Teoría y conceptos — 2.8.1 — Qué es el testing y por qué importa

La definición
Testing de software es el proceso de verificar que un programa hace lo que debe hacer (y lo que NO debe hacer). Involucra ejecutar el programa con datos de prueba y comparar el resultado real con el esperado.
El costo de NO testear
Un bug en producción puede costar:
- $0.10 si lo encuentras tú mismo al escribir el código.
- $10 si lo encuentra un compañero en code review.
- $100 si lo encuentra QA (tester).
- $1,000 si lo encuentra un cliente.
- $10,000 si lo descubre un usuario en producción (y se hace viral el error).
El testing es la skill más rentable que puedes aprender. Un junior que testea su propio código vale 10x más que uno que solo programa.
El mito del "funciona en mi máquina"
"El código compila y se ejecuta. Debe estar bien."
No. Compilar es solo el 30% de la calidad. Que ejecute sin errores es el 50%. Que produzca el resultado correcto es el 100%. El testing es lo que te lleva del 50% al 100%.
la última vez que un programa tuyo "funcionó en tu máquina pero no en la del profesor". ¿Qué pasó? ¿Cómo lo detectaste?
2.8.2 — Los 3 tipos de errores

Error 1: Error de compilación
Cuándo: antes de ejecutar, al compilar. Quién lo detecta: el compilador. Cómo se ve: mensaje de error con archivo, línea y tipo. Ejemplo: cout << "Hola" // Falta ;
Acción: lee el mensaje, corrige, recompila.
Error 2: Error de ejecución (runtime)
Cuándo: durante la ejecución. Quién lo detecta: el sistema operativo o la VM. Cómo se ve: mensaje de error, terminación abrupta. Ejemplo: división entre 0, archivo no encontrado, NULL pointer.
Acción: identifica la línea, valida los datos, agrega manejo de errores.
Error 3: Error lógico
Cuándo: el programa ejecuta "bien" pero da resultado incorrecto. Quién lo detecta: TÚ (o el usuario, peor). Cómo se ve: ningún mensaje; el programa "funciona" pero mal. Ejemplo: querías sumar pero usaste multiplicar; querías i < n pero pusiste i <= n.
Acción: prueba de escritorio con datos conocidos, asserts, tests automatizados.
Distinguir los 3 tipos
los 3 tipos con un ejemplo de cada uno de tus programas anteriores.
2.8.3 — Pruebas de escritorio: trazas con datos de prueba

Qué es
Una prueba de escritorio (o traza) es simular la ejecución del programa en papel, paso a paso, anotando el estado de cada variable.
Ya hiciste esto en los Temas 1.3 y 2.4. Aquí lo formalizamos como técnica de testing.
El proceso
- Elige datos de prueba (3-5).
- Para cada dato, haz una tabla con columnas = variables + condición + salida.
- Recorre el programa línea por línea, actualizando el estado.
- Verifica que la salida final coincide con lo esperado.
Ejemplo: función máximo
int maximo(int a, int b) {
if (a > b) return a;
return b;
}
Traza con (3, 7): a=3, b=7 → 3 > 7 es false → return 7. Resultado: 7. ✓ Traza con (7, 3): a=7, b=3 → 7 > 3 es true → return 7. Resultado: 7. ✓ Traza con (5, 5): a=5, b=5 → 5 > 5 es false → return 5. Resultado: 5. ✓
Ejemplo: suma hasta centinela
int suma = 0, num;
cin >> num;
while (num != 0) {
suma += num;
cin >> num;
}
cout << suma << endl;
Traza con 10, 20, 30, 0:
Salida esperada: 60. ✓
Diagrama de flujo de la técnica de prueba de escritorio
┌─────────────┐
│ INICIO │ ⬭
└──────┬──────┘
│
▼
╔════════════════╗
║ Elegir datos ║ ▭
║ de prueba ║
║ (3-5 casos) ║
╚════════╤═══════╝
│
▼
◆───────────────◆ ◇
╱ ╲
¿Hay más No ──────►
datos? │
───────── │
│ │ │
Sí │ │ No ▼
│ │ ╔══════════╗
▼ │ ║ RESULTADO ║
╔══════════╗ ║ ¿Coincide?║ ▭
║ Trazar ║ ╚════╤═════╝
║ programa ║ │
║ línea ║ ├──Sí→ OK
║ por ║ │
║ línea ║ └──No→ BUG
╚════════╝ │
│ ▼
└────────── (vuelve) │
[CORREGIR]
2.8.4 — Diseño de casos de prueba: normal, borde, extremo

Los 3 tipos de casos
- Caso normal: el caso típico, con datos esperados.
// Suma(3, 5) = 8
// Máximo(10, 20) = 20
// EsPar(4) = true
- Caso borde: el límite exacto.
// Suma(0, 0) = 0
// Máximo(5, 5) = 5 (empate)
// EsPar(0) = true (cero es par)
- Caso extremo: datos fuera de lo común o inválidos.
// Suma(-5, 5) = 0
// Máximo(INT_MAX, INT_MIN) = ?
// EsPar(-3) = false
Para cada programa, diseña al menos:
- 2 casos normales (los más comunes).
- 1 caso borde (límite exacto).
- 1 caso extremo (valor unusual o inválido).
Tabla de casos de prueba para una función "factorial"
para una de tus funciones del LAB anterior, diseña una tabla de 5 casos de prueba cubriendo normal, borde y extremo.
2.8.5 — La macro assert() en C/C++

Qué es
assert() es una macro que verifica que una condición sea verdadera. Si NO lo es, el programa termina inmediatamente con un mensaje de error indicando el archivo y la línea.
#include <cassert>
int x = 5;
assert(x == 5); // Pasa, no hace nada
assert(x > 10); // Falla, el programa termina
Cuándo usar assert
✅ Invariantes internas que SIEMPRE deben ser ciertas:
- "El índice de un arreglo está en rango".
- "Un puntero no es null antes de dereferenciarlo".
- "El factorial de 0 es 1".
❌ NO para validar entrada del usuario (eso va con if + mensaje).
Sintaxis
#include <cassert>
int dividir(int a, int b) {
assert(b != 0); // Precondición: b no puede ser 0
return a / b;
}
Si llamas dividir(10, 0), el programa se aborta con:
Assertion failed: b != 0, file main.cpp, line 4
Activar y desactivar asserts
#define NDEBUG // Desactivar todos los asserts
#include <cassert>
// Ahora assert() no hace nada
Por defecto, los asserts están ACTIVOS. En producción (release), se desactivan con #define NDEBUG antes de incluir <cassert>.
Ejemplo: función máximo con assert
#include <iostream>
#include <cassert>
using namespace std;
int maximo(int a, int b) {
return (a > b) ? a : b;
}
int main() {
// Tests
assert(maximo(3, 7) == 7);
assert(maximo(7, 3) == 7);
assert(maximo(5, 5) == 5);
assert(maximo(-5, 5) == 5);
assert(maximo(-5, -3) == -3);
cout << "Todos los tests pasaron" << endl;
return 0;
}
Si algún assert falla, el programa se aborta y te dice cuál falló. Si todos pasan, imprime "Todos los tests pasaron".
2.8.6 — Escribir tests básicos con assert

La estructura de un test
void test_X() {
// 1. Preparación (opcional)
int a = 5, b = 3;
// 2. Ejecución
int resultado = miFuncion(a, b);
// 3. Verificación
assert(resultado == 8);
}
// En main:
int main() {
test_X();
// ... más tests
cout << "Todos los tests pasaron" << endl;
return 0;
}
Ejemplo completo: tests para una calculadora
#include <iostream>
#include <cassert>
using namespace std;
int sumar(int a, int b) { return a + b; }
int restar(int a, int b) { return a - b; }
int multiplicar(int a, int b) { return a * b; }
int dividir(int a, int b) {
assert(b != 0);
return a / b;
}
void test_sumar() {
assert(sumar(3, 5) == 8);
assert(sumar(0, 0) == 0);
assert(sumar(-5, 5) == 0);
assert(sumar(100, 200) == 300);
cout << "test_sumar OK" << endl;
}
void test_restar() {
assert(restar(10, 3) == 7);
assert(restar(0, 0) == 0);
assert(restar(-5, -3) == -2);
cout << "test_restar OK" << endl;
}
void test_dividir() {
assert(dividir(10, 2) == 5);
assert(dividir(0, 5) == 0);
assert(dividir(-10, 2) == -5);
// dividir(10, 0) → assert fallaría, no lo probamos
cout << "test_dividir OK" << endl;
}
int main() {
test_sumar();
test_restar();
test_dividir();
cout << "Todos los tests pasaron" << endl;
return 0;
}
3 tests para una función tuya del LAB anterior. Cada test debe tener al menos 2 casos de prueba.
2.8.7 — Dividir y conquistar: cómo encontrar bugs
La técnica
Si tu programa tiene un bug y no sabes dónde está, dividir el programa en mitades y probar cada mitad con un cout o assert.
// Programa sospechoso
void programa() {
parte1();
parte2(); // ¿El bug está aquí?
parte3();
}
Paso 1: agregar un assert o cout ANTES de parte2.
void programa() {
parte1();
assert( /* condición esperada después de parte1 */ ); // ← NUEVO
parte2();
parte3();
}
Paso 2: si el assert pasa, el bug está en parte2 o después. Si falla, el bug está en parte1.
Paso 3: repetir el proceso con la mitad sospechosa.
El poder de los assert en debugging
Los assert no son solo para tests automatizados. También son red de seguridad en producción: si tu programa tiene una invariante rota, te avisa inmediatamente, no 3 horas después cuando el bug ya hizo daño.
🤖 IA como copiloto — 🤖 AI Mission 2.8
Prompt 1: "Te voy a pegar una función en C++. NO me corrijas el código. Solo dime: (1) qué hace, (2) qué casos de prueba usarías para validarla, (3) si ves algún bug evidente. Máximo 150 palabras."
Prompt 2: "Mi función pasa 3 tests pero falla en el cuarto. Te pego los 4 tests. NO me corrijas la función. Solo dime: (1) qué tiene de diferente el test 4, (2) qué caso borde podría no estar manejando la función. Máximo 100 palabras."
🧪 Laboratorio — 🧪 LAB 2.8 — Testing de programas
🛠️ Herramienta recomendada para los diagramas de flujo de este LAB: Para los ejercicios que pidan un diagrama de flujo, usa [draw.io (diagrams.net)](https://app.diagrams.net/) — herramienta gratis, sin registro, que funciona en el navegador. Tiene plantillas con la simbología ANSI/ISO lista (en `General → Flowchart`). Exporta tus diagramas como PNG e insértalos en tu cuaderno digital o entrégalos junto con el código.
Ejercicio 1 — Traza del factorial (básico, 15 min)
Enunciado. Dada esta función:
int factorial(int n) {
int f = 1;
for (int i = 1; i <= n; i++) f *= i;
return f;
}
Haz la traza con n=0, 1, 3, 5. Verifica que da 1, 1, 6, 120.
Rúbrica (10 pts): 4 trazas correctas (8 pts) + verificación (2 pts).
Ejercicio 2 — Tabla de casos de prueba (básico, 15 min)
Enunciado. Para una función esMayorDeEdad(int edad), diseña 6 casos de prueba cubriendo normal, borde y extremo. Indica el valor de entrada, la salida esperada y el tipo.
Rúbrica (10 pts): 2 normales + 2 bordes + 2 extremos.
Ejercicio 3 — Tests con assert (intermedio, 25 min)
Enunciado. Escribe una función esPrimo(int n) y crea 5 tests con assert que cubran: primo, no primo, 1, 2, número negativo.
Rúbrica (10 pts): función correcta (5 pts) + 5 tests (5 pts).
Ejercicio 4 — Dividir y conquistar (intermedio, 25 min)
Enunciado. Te doy este programa que se comporta raro:
int main() {
int x = 5, y = 3, z = 0;
z = x * y;
z = z + 10;
z = z / 2;
cout << "Resultado: " << z << endl;
return 0;
}
Traza línea por línea y verifica que el resultado sea 12. Si NO es 12, encuentra el bug con la técnica de "dividir y conquistar".
Rúbrica (10 pts): traza correcta (5 pts) + identificación del bug (3 pts) + corrección (2 pts).
Ejercicio 5 — Tests de funciones con arreglos (avanzado, 30 min)
Enunciado. Escribe una función int maximo(int arr[], int n) que encuentre el mayor de un arreglo. Escribe 5 tests con assert que cubran: arreglo normal, todos iguales, un solo elemento, números negativos, arreglo vacío (n=0).
Rúbrica (10 pts): función correcta (5 pts) + 5 tests cubriendo todos los casos (5 pts).
🎯 Cierre del tema
Lo que aprendiste hoy:
- Qué es el testing de software y por qué es rentable.
- Los 3 tipos de errores y quién los detecta.
- Cómo hacer pruebas de escritorio (trazas).
- Cómo diseñar casos de prueba (normal, borde, extremo).
- Cómo usar assert() para verificar invariantes.
- Cómo escribir tests automatizados básicos.
- La técnica "dividir y conquistar" para debugging.
Lo que sigue: en el Tema 2.9 — Funciones: Definición, Parámetros, Retorno (8h), vas a aprender a dividir tu código en funciones reutilizables, cada una con una sola responsabilidad. Esto hace que tus programas sean más fáciles de leer, testear y mantener. Es la base de la modularidad.
XP obtenida al completar este tema: 100 base + 20 por LAB + 20 por Quest = 140 XP.
*"Un código sin tests es un programa que funciona por suerte. Un código con tests es un programa que funciona por diseño."* — De la introducción de este manual.
Fin del Tema 2.8
📝 Quest · Cuestionario — 📝 Quest — Reactivos para autoevaluación
Las siguientes preguntas te sirven para autoevaluarte después de leer el tema. Responde en tu cuaderno o mentalmente, y luego revisa las Respuestas modelo (disponibles en libre acceso, sin iniciar sesión).
Sección A · Opción múltiple
Elige la opción correcta (A, B, C o D). Las respuestas están en la sección Respuestas modelo.
- ¿Qué es una prueba de escritorio?
- Ejecutar en PC
- Simular manualmente la ejecución con valores concretos
- Compilar
- Debug
- ¿Para qué sirve una prueba de escritorio?
- Compilar
- Verificar la lógica antes de codificar
- Ejecutar
- Hacer bonito
- ¿Qué hace assert en C++?
- Confirma compilación
- Verifica condición: si es false, aborta con error
- Lee input
- Es un bucle
- ¿En qué header está assert?
- <iostream>
- <cassert> o <assert.h>
- <vector>
- <string>
- assert(false) hace:
- Nada
- Aborta el programa con mensaje de error
- Compila
- Reinicia
- ¿Por qué son importantes los tests automatizados?
- Para llenar código
- Detectan regresiones y validan el comportamiento
- Para hacerlo más lento
- No son importantes
- En NDEBUG activo, ¿qué pasa con assert?
- Se ejecuta
- Se desactiva (no hace nada)
- Compila
- Falla
- ¿Qué es regresión en software?
- Mejora
- Un bug que reaparece o nueva funcionalidad que rompe algo existente
- Compilar
- Test
- Test unitario verifica:
- Todo el sistema
- Una unidad (función, método) en aislamiento
- La red
- La base de datos
- Test de integración verifica:
- Una función
- La interacción entre múltiples componentes
- El hardware
- La red
Sección B · Preguntas abiertas
Desarrolla tu respuesta en al menos 3 líneas. Compara con la respuesta modelo después de escribir.
- Diseña un test unitario para una función "esPar(int n)" en pseudocódigo.→ Escribe tu respuesta aquí (en tu cuaderno o mentalmente)…
- ¿Qué es la cobertura de código? ¿Por qué 100% no garantiza ausencia de bugs?→ Escribe tu respuesta aquí (en tu cuaderno o mentalmente)…
- Explica la diferencia entre tests unitarios, de integración y end-to-end. Da un ejemplo de cada uno.→ Escribe tu respuesta aquí (en tu cuaderno o mentalmente)…
🟢 Cuando termines, revisa las Respuestas modelo y compáralas con las tuyas. La mejor forma de aprender es discutir cada respuesta contigo mismo o con un compañero.