Programar no consiste solo en escribir código y cruzar los dedos para que todo funcione. Esa fase en la que ejecutamos el programa, miramos si “parece ir bien” y pasamos a otra cosa suele ser justo donde empiezan muchos problemas. Las pruebas de software existen para evitar eso: para comprobar de forma ordenada si un programa hace lo que debe hacer, si responde bien ante datos normales, si controla situaciones incorrectas y si mantiene un comportamiento fiable cuando lo modificamos.
Una idea que me gusta repetir, sobre todo cuando explico programación en FP, es esta: un programa no está terminado cuando compila. Compilar solo significa que el código cumple ciertas reglas del lenguaje, pero no garantiza que la lógica sea correcta, que el usuario pueda usar la aplicación sin problemas o que los requisitos estén realmente cubiertos. El documento base del tema lo resume muy bien: un programa empieza a estar preparado cuando se ha probado, se han documentado los resultados y se han corregido las incidencias detectadas.
En mi caso, esta idea no viene solo de la teoría. Mi trayectoria ha pasado por desarrollo software, consultoría tecnológica, Salesforce, proyectos web y docencia en FP Informática, una mezcla que me ha hecho ver las pruebas de software desde varios ángulos: quien construye, quien mantiene, quien escucha al cliente y quien tiene que explicarlo de forma clara en clase.
En esta guía voy a explicarte qué son las pruebas de software, qué tipos existen, cómo se diseñan buenos casos de prueba, cómo se documentan incidencias, qué papel tiene la depuración y por qué las pruebas automáticas ayudan tanto a mantener la calidad del software.
Qué son las pruebas de software y por qué importan
Las pruebas de software son el conjunto de actividades que realizamos para comprobar el comportamiento de un programa y detectar errores antes de que lleguen al usuario. Dicho de forma sencilla: probar software consiste en comparar lo que el programa debería hacer con lo que realmente hace.
La diferencia parece pequeña, pero es enorme. No basta con decir “he ejecutado el programa y no ha fallado”. Las pruebas de software deben hacerse con criterio: seleccionando datos, definiendo resultados esperados, ejecutando casos concretos, anotando resultados obtenidos y documentando cualquier comportamiento incorrecto.
Por ejemplo, imaginemos una función que calcula el precio final de una compra. Si solo la probamos con un precio positivo y un descuento normal, puede parecer correcta. Pero unas buenas pruebas de software deberían comprobar también qué ocurre si el precio es cero, si el descuento es mayor que el precio, si el usuario introduce un valor negativo o si deja el campo vacío. El objetivo no es probar por probar, sino ganar confianza razonable en que el programa se comporta bien en diferentes situaciones.
En desarrollo real, esta mentalidad cambia mucho la forma de trabajar. En mi etapa de desarrollo backend, donde también trabajé con testing y calidad de código, aprendí que las pruebas de software no son un adorno final. Son parte del propio proceso de desarrollo. Cuando pruebas bien, no solo encuentras fallos: también entiendes mejor el problema, mejoras el diseño y reduces sustos futuros.
Por qué un programa que compila no siempre funciona bien
Un programa puede compilar y aun así estar mal. Puede abrirse, mostrar una pantalla correcta y fallar cuando el usuario introduce un dato inesperado. Puede calcular bien una suma sencilla y equivocarse con un valor límite. Puede funcionar hoy y romperse mañana después de una modificación aparentemente pequeña.
Por eso las pruebas de software son imprescindibles. Ayudan a detectar errores antes de que lleguen al usuario, comprobar que se cumplen los requisitos, reducir el coste de mantenimiento, facilitar cambios futuros, aumentar la confianza del equipo y generar documentación técnica sobre el comportamiento esperado del programa.
La pregunta profesional no debería ser “¿mi programa funciona?”. Esa pregunta es demasiado cómoda. La pregunta útil es: “¿he probado suficientes situaciones para confiar razonablemente en que mi programa funciona?”. Esa es la mentalidad correcta cuando hablamos de pruebas de software.
Verificación, validación y calidad del software
Para entender bien las pruebas de software conviene diferenciar tres ideas: verificación, validación y calidad del software. Están relacionadas, pero no significan exactamente lo mismo.
La verificación consiste en comprobar si el producto se está construyendo correctamente según las especificaciones técnicas. Es decir, revisamos si el software cumple lo que estaba diseñado. Si una especificación dice que una función debe calcular el IVA de un precio, la verificación comprueba que esa fórmula está bien implementada.
La validación va un paso más allá. Consiste en comprobar si el producto construido responde realmente a las necesidades del usuario. Aquí la pregunta cambia: no se trata solo de saber si hemos implementado bien una fórmula, sino de saber si esa fórmula resuelve el problema real. Puede que una aplicación calcule correctamente el IVA, pero si el usuario necesitaba también aplicar descuentos, generar tickets o exportar facturas, quizá el producto no sea suficiente.
En consultoría tecnológica esto se ve muchísimo. Puedes construir algo técnicamente correcto y aun así no resolver lo que el cliente necesitaba. Por eso, antes de proponer herramientas o soluciones, hay que escuchar. Mi experiencia en consultoría, procesos, documentación y relación con clientes me ha enseñado que validar bien evita desarrollar cosas que “funcionan”, pero no sirven.
Calidad del software
La calidad del software no depende solo de que un programa funcione una vez. Un software de calidad debe ser fiable, mantenible, comprensible, eficiente, seguro y adecuado a las necesidades del usuario. Las pruebas de software son una herramienta clave para lograr esa calidad, pero no la única. También influyen el diseño, la documentación, la organización del código, el control de versiones y las revisiones.
Una aplicación puede pasar varias pruebas básicas y seguir siendo difícil de mantener. También puede tener una interfaz bonita y fallar en situaciones críticas. Por eso las pruebas de software deben formar parte de una visión más amplia de calidad: probar, documentar, corregir, revisar y volver a probar.
Tipos de errores que pueden aparecer en un programa
Antes de hablar de tipos de pruebas de software, conviene entender qué tipos de errores podemos encontrar. No todos los errores son iguales, y cada uno se detecta de una forma distinta.
Errores de sintaxis
Los errores de sintaxis son fallos en la forma de escribir el código. Son los más fáciles de detectar porque normalmente el compilador o el IDE nos avisa enseguida.
Por ejemplo:
public class Ejemplo {
public static void main(String[] args) {
System.out.println("Hola")
}
}
En este caso falta el punto y coma al final de la instrucción. El programa no compilará. Este tipo de error es molesto, pero suele ser sencillo de localizar.
Errores de tipo
Los errores de tipo aparecen cuando usamos un dato de forma incompatible con su tipo. Por ejemplo:
int edad = "dieciocho";
Aquí intentamos guardar texto en una variable de tipo entero. El código no tiene sentido para el compilador, porque edad espera un número, no una cadena de texto.
Errores lógicos
Los errores lógicos son más peligrosos porque el programa puede compilar y ejecutarse, pero producir un resultado incorrecto. Por ejemplo:
double precio = 100;
double descuento = 20;
double total = precio + descuento;
Si queríamos aplicar un descuento, deberíamos restar, no sumar. El programa no se rompe, pero calcula mal. Este tipo de error es una de las razones por las que las pruebas de software son tan importantes.
Errores de ejecución
Los errores de ejecución aparecen mientras el programa está funcionando. Pueden producirse por datos concretos, archivos inexistentes, recursos no disponibles o divisiones por cero.
int resultado = 10 / 0;
Este código provoca un error porque no se puede dividir entre cero. Una buena estrategia de pruebas de software debe incluir este tipo de situaciones, no solo los casos cómodos.
Errores de requisitos
A veces el código funciona técnicamente, pero no responde a lo que el usuario necesitaba. Este tipo de error suele detectarse mediante validación, revisión de requisitos o pruebas de aceptación.
Este es uno de los errores más caros, porque muchas veces no se ve hasta tarde. El programa “funciona”, pero no resuelve el problema correcto. En proyectos reales, esto ocurre cuando se desarrolla demasiado rápido sin entender bien la necesidad de fondo.
Tipos de pruebas de software más importantes
Existen muchas formas de clasificar las pruebas de software, pero hay varios tipos básicos que conviene dominar: funcionales, estructurales, regresión, unitarias, integración, sistema y aceptación. Cada una responde a una pregunta distinta y aporta una visión diferente del programa.
| Tipo de prueba | Qué comprueba | Ejemplo |
|---|---|---|
| Funcional | El comportamiento externo | El sistema calcula correctamente un precio final |
| Estructural | Los caminos internos del código | Se ejecutan las ramas if y else |
| Regresión | Que un cambio no rompe lo anterior | Tras corregir descuentos, el IVA sigue funcionando |
| Unitaria | Una función o clase aislada | Probar sumar() en una calculadora |
| Integración | Varios módulos juntos | Pedido usa correctamente Producto y Cliente |
| Sistema | La aplicación completa | Flujo completo de compra |
| Aceptación | La necesidad del usuario | El cliente valida que puede gestionar ventas |
Pruebas funcionales o de caja negra
Las pruebas funcionales comprueban qué hace el programa desde fuera. Se basan en requisitos, entradas y salidas esperadas. No necesitamos conocer el código interno. Por eso también se conocen como pruebas de caja negra.
Por ejemplo, si tenemos una función que clasifica edades, podemos probar:
| Entrada | Resultado esperado |
|---|---|
| Edad 20 | Mayor de edad |
| Edad 15 | Menor de edad |
| Edad -3 | Error de dato no válido |
Lo importante aquí no es cómo está programado por dentro, sino si responde correctamente ante cada entrada.
Pruebas estructurales o de caja blanca
Las pruebas estructurales analizan cómo está construido el programa internamente. Se basan en el código, las condiciones, los caminos de ejecución y las estructuras de control. También se conocen como pruebas de caja blanca.
Por ejemplo:
if (edad >= 18) {
return "Mayor de edad";
} else {
return "Menor de edad";
}
Una prueba estructural debería comprobar que se ejecuta tanto la rama del if como la rama del else. Aquí sí miramos el código por dentro para asegurarnos de que las rutas importantes han sido probadas.
Pruebas de regresión
Las pruebas de regresión se realizan después de modificar el programa para comprobar que algo que antes funcionaba no se ha roto. Son especialmente importantes cuando corregimos errores o añadimos funcionalidades nuevas.
En proyectos propios esto se nota muchísimo. Cuando mantienes una web, una herramienta o cualquier aplicación durante tiempo, descubres que cada cambio pequeño puede afectar a otra parte. Por eso las pruebas de software de regresión no son una exageración: son una red de seguridad.
Un ejemplo sencillo:
- La aplicación calcula correctamente descuentos.
- Se modifica el código para añadir cupones.
- Se vuelven a ejecutar las pruebas de descuentos.
- Si alguna falla, puede haberse introducido una regresión.
Pruebas unitarias
Las pruebas unitarias verifican unidades pequeñas de código, como métodos o clases. Su objetivo es comprobar que una parte concreta funciona de manera aislada. Por ejemplo, en una calculadora podemos probar los métodos sumar, restar, multiplicar y dividir.
Una prueba unitaria no pretende demostrar que toda la aplicación funciona. Pretende confirmar que una pieza pequeña hace bien su trabajo. Esto es muy útil porque, cuando algo falla, resulta más fácil localizar el origen del problema.
Pruebas de integración
Las pruebas de integración comprueban que varias partes del sistema funcionan correctamente cuando trabajan juntas. Un módulo puede funcionar bien por separado y fallar al comunicarse con otro.
Por ejemplo, una clase Pedido puede depender de Cliente y Producto. Cada clase puede estar bien por separado, pero al integrarlas puede aparecer un error en el cálculo del total, en la relación entre objetos o en el flujo de datos.
Pruebas de sistema
Las pruebas de sistema evalúan el comportamiento de la aplicación completa. Ya no miramos solo una clase o un grupo de componentes, sino el flujo global.
Un ejemplo sería:
- Registrar un usuario.
- Dar de alta un libro.
- Realizar un préstamo.
- Consultar los préstamos activos.
- Devolver el libro.
- Comprobar que el préstamo queda cerrado.
Este tipo de pruebas de software ayuda a comprobar si la aplicación funciona como conjunto.
Pruebas de aceptación
Las pruebas de aceptación comprueban si el sistema satisface las necesidades del usuario o cliente. Suelen escribirse con lenguaje más cercano al negocio que al código.
Por ejemplo:
| Requisito | Prueba de aceptación |
|---|---|
| El usuario debe poder iniciar sesión | Dado un usuario registrado, cuando introduce credenciales correctas, entonces accede al sistema |
| El sistema debe impedir edades negativas | Dada una edad negativa, cuando se procesa el formulario, entonces se muestra un mensaje de error |
| El comercio debe poder aplicar descuentos | Dado un producto con precio y descuento, cuando se calcula el total, entonces se obtiene el precio rebajado |
Las pruebas de aceptación conectan muy bien con la validación: no basta con construir bien; hay que construir lo correcto.
Cómo diseñar buenos casos de prueba
Uno de los errores más habituales al hacer pruebas de software es probar al azar. Ejecutar el programa, meter dos datos y comprobar que “más o menos funciona” no es una estrategia. Las pruebas deben diseñarse con criterio.
Cuando explico esto en clase, suelo decirlo de forma muy directa: probar no es jugar con el programa. Probar es preparar situaciones, decidir qué esperamos que ocurra y comparar. Esa diferencia convierte una ejecución improvisada en una prueba útil.
Clases de equivalencia
Una clase de equivalencia es un grupo de datos que el programa debería tratar de la misma forma. En lugar de probar todos los valores posibles, elegimos algunos representativos.
Por ejemplo, si una edad válida está entre 0 y 120, podemos definir:
| Clase | Rango | Tipo | Valor representativo |
|---|---|---|---|
| Edad válida | 0 a 120 | Válida | 25 |
| Edad negativa | Menor que 0 | Inválida | -1 |
| Edad demasiado alta | Mayor que 120 | Inválida | 121 |
| Dato no numérico | Texto u otro formato | Inválida | abc |
Esta técnica ayuda a diseñar pruebas de software más eficientes. No podemos probar todos los valores posibles, pero sí podemos elegir valores representativos de cada grupo.
Valores límite
Los errores suelen aparecer cerca de los límites. Por eso conviene probar justo en el límite, justo por debajo y justo por encima.
Si el rango válido de edad es de 0 a 120, no basta con probar 25. Conviene probar:
- -1
- 0
- 1
- 119
- 120
- 121
Si una condición usa > en lugar de >=, el error probablemente aparecerá en un valor límite. Por eso los valores límite son una técnica básica en pruebas de software.
Cobertura de código
La cobertura indica qué parte del código se ha ejecutado durante las pruebas. No garantiza que no existan errores, pero ayuda a detectar zonas que nunca se han probado.
Podemos hablar de:
| Tipo de cobertura | Qué mide |
|---|---|
| Cobertura de instrucciones | Porcentaje de líneas o instrucciones ejecutadas |
| Cobertura de ramas | Porcentaje de caminos condicionales ejecutados |
| Cobertura de métodos | Porcentaje de métodos llamados durante las pruebas |
La cobertura es útil, pero hay que interpretarla con cuidado. Tener mucha cobertura no significa automáticamente que las pruebas de software sean buenas. Puede que hayamos ejecutado muchas líneas, pero con datos poco relevantes.
Caminos de ejecución
Un camino de ejecución es una ruta posible que sigue el programa dependiendo de condiciones, bucles y decisiones.
Por ejemplo:
if (nota >= 5) {
System.out.println("Aprobado");
} else {
System.out.println("Suspenso");
}
Para probar correctamente este fragmento necesitamos al menos un caso que entre en la rama de aprobado y otro que entre en la rama de suspenso. Si solo probamos una nota de 8, no sabemos si la rama de suspenso funciona.
Qué debe incluir un caso de prueba
Un caso de prueba describe una situación concreta que se va a comprobar. Debe estar escrito con claridad para que otra persona pueda repetirlo y obtener el mismo resultado. Esta parte es fundamental: las pruebas de software no deberían depender de la memoria de quien las hizo.
Un buen caso de prueba incluye:
| Campo | Descripción |
|---|---|
| Identificador | Código único del caso, por ejemplo CP-001 |
| Nombre | Título breve que indique qué se comprueba |
| Objetivo | Comportamiento que se quiere verificar |
| Precondiciones | Situación necesaria antes de ejecutar la prueba |
| Datos de entrada | Valores que se introducen en el programa |
| Pasos | Acciones que debe realizar la persona que prueba |
| Resultado esperado | Lo que debería ocurrir si todo funciona bien |
| Resultado obtenido | Lo que ha ocurrido realmente |
| Estado | Superada, fallida, bloqueada o pendiente |
| Observaciones | Comentarios, evidencias o incidencias asociadas |
El documento base insiste en que un caso de prueba debe permitir que otra persona lo repita. Eso es clave. Si una prueba solo la entiende quien la ejecutó, no está bien documentada.
Ejemplo de caso de prueba
Imaginemos una función clasificarEdad.
| Campo | Ejemplo |
|---|---|
| Identificador | CP-001 |
| Nombre | Comprobar mayoría de edad con edad válida |
| Objetivo | Verificar que una edad mayor o igual que 18 se clasifica como mayor de edad |
| Precondiciones | La aplicación está abierta y el método está disponible |
| Datos de entrada | edad = 18 |
| Pasos | Ejecutar el método, introducir edad 18 y observar la salida |
| Resultado esperado | El sistema devuelve “Mayor de edad” |
| Resultado obtenido | El sistema devuelve “Mayor de edad” |
| Estado | Superada |
| Observaciones | Caso en valor límite inferior de mayoría de edad |
Fíjate en un detalle importante: la edad 18 no es un valor cualquiera. Es un valor límite. Por eso es tan interesante para las pruebas de software.
Plan de pruebas: cómo organizar el trabajo
Un plan de pruebas es un documento que organiza cómo se van a realizar las pruebas de software en un proyecto. No tiene por qué ser enorme ni burocrático. De hecho, un plan demasiado pesado puede acabar siendo un documento que nadie consulta. Lo importante es que sea útil.
Un plan de pruebas debería dejar claro:
- Qué se va a probar.
- Qué no se va a probar.
- Qué tipos de pruebas se realizarán.
- Qué entorno se usará.
- Qué casos de prueba se ejecutarán.
- Qué criterios de aceptación se aplicarán.
- Qué evidencias se conservarán.
- Cómo se registrarán las incidencias.
Por ejemplo, en una calculadora básica, el plan podría indicar que se probarán suma, resta, multiplicación y división; que se usarán pruebas unitarias, funcionales y de regresión; que el entorno será Eclipse con JDK instalado; y que el criterio de aceptación será que todas las operaciones devuelvan el resultado esperado y que la división entre cero se controle correctamente.
En FP, esta idea me parece especialmente importante: no buscamos documentación por rellenar. Buscamos documentación que ayude a trabajar mejor. Un buen plan de pruebas debe aclarar el camino, no complicarlo.
Depuración de código con el IDE
La depuración es el proceso de ejecutar un programa de forma controlada para encontrar la causa de un error. En vez de mirar el código y adivinar, usamos el IDE para observar qué ocurre realmente durante la ejecución.
Las herramientas habituales de depuración incluyen:
| Herramienta | Para qué sirve |
|---|---|
| Breakpoint | Detiene la ejecución en una línea concreta |
| Step Over | Ejecuta la línea actual sin entrar en métodos llamados |
| Step Into | Entra dentro del método llamado |
| Step Return | Sale del método actual |
| Resume | Continúa hasta el siguiente breakpoint |
| Vista de variables | Permite observar valores durante la ejecución |
| Pila de llamadas | Muestra desde dónde se ha llegado a ese punto |
El depurador no corrige el programa por nosotros. Lo que hace es enseñarnos información que normalmente está oculta mientras el programa se ejecuta. Esa información es oro cuando estamos haciendo pruebas de software.
Ejemplo de depuración
Supongamos este código:
public class EjemploDebug {
public static void main(String[] args) {
int precio = 100;
int descuento = 20;
int total = calcularTotal(precio, descuento);
System.out.println("Total: " + total);
}
public static int calcularTotal(int precio, int descuento) {
return precio + descuento;
}
}
El programa devuelve 120, pero debería devolver 80. Si usamos el depurador, podemos poner un breakpoint en la llamada a calcularTotal y entrar en el método con Step Into. Así veremos que el problema está en esta línea:
return precio + descuento;
La corrección sería:
return precio - descuento;
Este ejemplo muestra muy bien cómo se combinan depuración y pruebas de software. La prueba detecta que el resultado es incorrecto. La depuración ayuda a encontrar la causa.
Pruebas unitarias y automatización
Las pruebas unitarias son una de las formas más prácticas de empezar a automatizar pruebas de software. Una prueba unitaria comprueba una unidad pequeña del programa, normalmente un método o una clase.
Imaginemos una clase Calculadora:
public class Calculadora {
public int sumar(int a, int b) {
return a + b;
}
public int restar(int a, int b) {
return a - b;
}
public int dividir(int a, int b) {
return a / b;
}
}
Podemos probar que:
sumar(2, 3)devuelve 5.restar(10, 4)devuelve 6.dividir(10, 2)devuelve 5.dividir(10, 0)controla correctamente el error.
Una aserción es una comprobación automática entre el resultado esperado y el resultado obtenido. Si coinciden, la prueba se supera. Si no coinciden, la prueba falla.
Ejemplo de prueba unitaria
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
public class CalculadoraTest {
@Test
public void testSumarDosNumerosPositivos() {
Calculadora calculadora = new Calculadora();
int resultado = calculadora.sumar(2, 3);
assertEquals(5, resultado);
}
@Test
public void testRestarDosNumeros() {
Calculadora calculadora = new Calculadora();
int resultado = calculadora.restar(10, 4);
assertEquals(6, resultado);
}
}
Este tipo de pruebas de software tiene una ventaja enorme: se pueden repetir muchas veces sin esfuerzo manual. Cada vez que cambiamos el código, podemos volver a ejecutar las pruebas y comprobar si algo ha dejado de funcionar.
Arrange, Act, Assert
Una forma clara de organizar pruebas unitarias es seguir la estructura Arrange, Act, Assert:
| Fase | Significado | Ejemplo |
|---|---|---|
| Arrange | Preparar datos y objetos | Crear una calculadora |
| Act | Ejecutar la acción que queremos probar | Llamar a sumar(2, 3) |
| Assert | Comprobar el resultado esperado | Verificar que devuelve 5 |
Esta estructura ayuda a que las pruebas de software sean más legibles. Y cuando una prueba falla, también ayuda a entender más rápido qué estaba preparando, qué acción ejecutaba y qué esperaba comprobar.
Pruebas automáticas y pruebas de regresión
Una prueba manual requiere que una persona ejecute pasos y compruebe resultados. Una prueba automática se ejecuta mediante código o herramientas. Esto permite repetir comprobaciones muchas veces con menos esfuerzo.
Las pruebas automáticas son útiles porque:
- Permiten repetir pruebas rápidamente.
- Reducen errores humanos en comprobaciones repetitivas.
- Facilitan detectar regresiones después de cambios.
- Aumentan la confianza al modificar código.
- Pueden integrarse en procesos profesionales de desarrollo.
Ahora bien, automatizar no significa olvidarse de pensar. Las pruebas automáticas también pueden estar mal diseñadas, necesitan mantenimiento y no sustituyen completamente la revisión humana.
La clave está en automatizar las comprobaciones importantes. En otras palabras: no se trata de hacer pruebas de software automáticas por presumir de tener muchas pruebas, sino de repetir de forma fiable lo que realmente protege el funcionamiento del programa.
En proyectos propios, donde mezclo diseño web, contenido, SEO, tecnología y mejora continua, esta idea aparece continuamente. Cambias una parte, mejoras otra, ajustas contenido, tocas estructura… y necesitas cierta confianza en que no has roto algo que ya funcionaba. Esa mentalidad de mejora continua encaja muy bien con las pruebas de regresión.
Pruebas de integración, sistema y aceptación
Las pruebas unitarias son importantes, pero no bastan. Un método puede funcionar bien de forma aislada y fallar cuando se comunica con otros componentes. Por eso necesitamos pruebas de integración, pruebas de sistema y pruebas de aceptación.
Las pruebas de integración verifican que varias unidades funcionan juntas. Por ejemplo:
Cliente cliente = new Cliente("Ana");
Producto producto = new Producto("Teclado", 25.0);
Pedido pedido = new Pedido(cliente);
pedido.agregarProducto(producto);
// Comprobar que el total del pedido es correcto.
Aquí ya no nos interesa solo si Producto funciona o si Pedido existe. Queremos comprobar si colaboran correctamente.
Las pruebas de sistema van más allá y prueban la aplicación completa. No miran una clase aislada ni dos módulos concretos, sino el flujo global. Por ejemplo, en una aplicación de biblioteca, podríamos probar desde el registro del usuario hasta la devolución de un libro.
Las pruebas de aceptación, por su parte, se centran en si el sistema satisface los requisitos del usuario. Suelen expresarse con lenguaje cercano al negocio: “Dado un usuario registrado, cuando introduce credenciales correctas, entonces accede al sistema”.
Aquí se ve muy bien la diferencia entre técnica y utilidad. En mi trayectoria, una de las ideas que más se repite es que la tecnología debe ser clara, útil y aplicable. Las pruebas de software no son una excepción: no sirven solo para que el código esté bonito, sino para comprobar que lo construido responde a una necesidad real.
Documentación de incidencias
Cuando una prueba falla, no basta con decir “no funciona”. Esa frase no ayuda casi nada. Una incidencia debe documentarse de forma clara para que pueda entenderse, reproducirse, priorizarse y corregirse.
Una incidencia debería incluir:
| Campo | Descripción |
|---|---|
| Identificador | Código único de la incidencia |
| Título | Resumen breve del problema |
| Descripción | Explicación clara del fallo |
| Pasos para reproducirlo | Secuencia necesaria para provocar el error |
| Resultado esperado | Qué debería haber ocurrido |
| Resultado obtenido | Qué ha ocurrido realmente |
| Entorno | Sistema, versión, IDE, navegador o datos relevantes |
| Severidad | Gravedad del fallo |
| Prioridad | Urgencia con la que debe corregirse |
| Evidencias | Capturas, logs o mensajes de error |
| Estado | Abierta, en revisión, corregida, cerrada o rechazada |
La diferencia entre severidad y prioridad también es importante. La severidad responde a “qué gravedad tiene el fallo”. La prioridad responde a “cuándo debe corregirse”. Un error puede ser muy grave, pero darse en una funcionalidad poco usada; o puede ser menos grave, pero bloquear una entrega inmediata.
Ejemplo de incidencia documentada
| Campo | Ejemplo |
|---|---|
| ID | INC-003 |
| Título | La división entre cero cierra la aplicación |
| Descripción | Al intentar dividir un número entre cero, el programa lanza una excepción no controlada |
| Pasos | Abrir calculadora, introducir 10, introducir 0 como divisor y ejecutar división |
| Resultado esperado | El sistema muestra un mensaje indicando que no se puede dividir entre cero |
| Resultado obtenido | El programa muestra un error de ejecución y finaliza |
| Severidad | Alta |
| Prioridad | Alta |
| Evidencia | Captura de consola con ArithmeticException |
| Estado | Abierta |
Documentar bien incidencias convierte las pruebas de software en una herramienta de comunicación. No solo encontramos errores: dejamos claro qué ocurre, cómo repetirlo y qué impacto tiene.
Métricas y calidad del software
Las pruebas de software también pueden relacionarse con métricas de calidad. Algunas métricas básicas son:
| Métrica | Qué mide | Ejemplo |
|---|---|---|
| Casos ejecutados | Número de pruebas realizadas | 20 casos ejecutados |
| Casos superados | Pruebas con resultado esperado | 17 superadas |
| Casos fallidos | Pruebas con resultado incorrecto | 3 fallidas |
| Porcentaje de éxito | Relación entre superadas y ejecutadas | 85% |
| Defectos detectados | Número de incidencias encontradas | 5 incidencias abiertas |
| Cobertura | Parte del código ejecutada por las pruebas | Ramas principales probadas |
El porcentaje de éxito se puede calcular así:
porcentaje_exito = (casos_superados / casos_ejecutados) * 100
Por ejemplo:
porcentaje_exito = (17 / 20) * 100 = 85%
Pero cuidado: una métrica nunca debe interpretarse sola. Tener un 95% de pruebas superadas no significa necesariamente que el software sea perfecto. Puede que falten pruebas importantes, que no se hayan probado valores límite o que los casos estén mal diseñados.
En pruebas de software, la calidad de las pruebas importa tanto como la cantidad. Es mejor tener menos pruebas bien pensadas que muchas pruebas que solo cubren casos fáciles.
Buenas prácticas al hacer pruebas de software
Después de ver tipos, técnicas, casos, depuración e incidencias, conviene resumir algunas buenas prácticas. Las pruebas de software funcionan mejor cuando se aplican con orden y sentido común.
No probar solo el caso feliz
El caso feliz es el escenario en el que todo sale bien: datos correctos, usuario cuidadoso y flujo previsto. Hay que probarlo, claro, pero no puede ser el único.
Un buen tester piensa también en lo que puede hacer un usuario real: equivocarse, dejar campos vacíos, introducir valores extremos o seguir un camino no previsto.
Por ejemplo, si pruebas una calculadora, no pruebes solo 2 + 3. Prueba también negativos, cero, división entre cero, números grandes y combinaciones menos evidentes.
Separar resultado esperado y resultado obtenido
Antes de ejecutar una prueba, debemos tener claro qué esperamos. Si no sabemos cuál es el resultado esperado, no estamos probando bien.
Esto parece obvio, pero en la práctica se olvida mucho. Ejecutamos, vemos un resultado y pensamos “bueno, parece correcto”. Las pruebas de software deben evitar esa improvisación. Primero definimos lo esperado. Luego ejecutamos. Después comparamos.
Documentar los fallos con pasos reproducibles
Un fallo que no se puede reproducir es mucho más difícil de corregir. Por eso hay que anotar los pasos exactos, los datos usados, el entorno y cualquier evidencia útil.
Decir “a veces falla” no ayuda. Decir “falla al dividir 10 entre 0 en la versión actual, mostrando una excepción no controlada” sí ayuda.
Usar el depurador para encontrar la causa
El depurador no sustituye a las pruebas de software, pero las complementa muy bien. La prueba detecta el síntoma. El depurador ayuda a encontrar la causa.
Cuando un resultado no coincide con lo esperado, no conviene cambiar código al azar. Es mejor parar, observar variables, seguir la ejecución paso a paso y entender qué está pasando realmente.
Repetir pruebas después de corregir errores
Cuando corregimos un error, debemos repetir la prueba que fallaba. Y además, si es posible, ejecutar pruebas relacionadas para comprobar que la corrección no ha roto otra funcionalidad.
Esto es especialmente importante en pruebas de regresión. Un cambio pequeño puede tener efectos secundarios. Por eso las pruebas de software deben acompañar al mantenimiento del código.
Automatizar lo repetitivo
Las pruebas repetitivas son buenas candidatas para automatizar. Si cada vez que cambiamos el código tenemos que comprobar manualmente veinte operaciones de una calculadora, acabaremos saltándonos pasos. Si esas comprobaciones están automatizadas, podemos repetirlas con mucha más facilidad.
Eso sí: automatizar no elimina la necesidad de pensar. Una mala prueba automática seguirá siendo mala, aunque se ejecute muy rápido.
Ejemplo completo: calculadora con pruebas de software
Vamos con un ejemplo sencillo, pero muy útil para entender cómo se aplican las pruebas de software.
Supongamos esta clase:
public class Calculadora {
public int sumar(int a, int b) {
return a + b;
}
public int restar(int a, int b) {
return a - b;
}
public int multiplicar(int a, int b) {
return a * b;
}
public int dividir(int a, int b) {
return a / b;
}
}
A primera vista parece correcta. Pero si diseñamos casos de prueba, veremos que hay situaciones que debemos controlar.
| ID | Operación | Entrada | Resultado esperado |
|---|---|---|---|
| CP-001 | Suma | 2 y 3 | 5 |
| CP-002 | Resta | 10 y 4 | 6 |
| CP-003 | Multiplicación | 6 y 7 | 42 |
| CP-004 | División válida | 20 y 5 | 4 |
| CP-005 | División entre cero | 10 y 0 | Mensaje o excepción controlada |
El método dividir funciona en casos normales, pero falla si el divisor es cero. El programa lanza una excepción no controlada. Una mejora sería:
public int dividir(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("No se puede dividir entre cero");
}
return a / b;
}
Y podríamos crear una prueba automatizada orientativa:
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
public class CalculadoraTest {
@Test
public void testSumar() {
Calculadora calculadora = new Calculadora();
assertEquals(5, calculadora.sumar(2, 3));
}
@Test
public void testDivisionEntreCero() {
Calculadora calculadora = new Calculadora();
assertThrows(IllegalArgumentException.class, () -> {
calculadora.dividir(10, 0);
});
}
}
Este ejemplo concentra muchas ideas importantes: pruebas unitarias, casos de prueba, valores límite, control de errores, documentación de incidencias y pruebas de regresión. Por eso es un buen punto de partida para aprender pruebas de software sin perderse en teoría innecesaria.
Errores habituales al hacer pruebas de software
Uno de los errores más habituales es probar solo con los datos que el programador tiene en mente. Esto es normal: cuando escribimos código, tendemos a usarlo como creemos que debería usarse. Pero el usuario real puede equivocarse, dejar campos vacíos, introducir valores imposibles o seguir un camino inesperado.
Otro error frecuente es no documentar. Si una prueba falla y no dejamos constancia, perdemos información útil. Y si alguien tiene que revisar el problema después, necesitará saber qué se hizo, qué datos se usaron y qué ocurrió.
También es común modificar muchas cosas a la vez mientras se intenta corregir un fallo. Esto complica saber qué cambio solucionó el problema o cuál introdujo otro error. Cuando hacemos pruebas de software, conviene trabajar de forma ordenada: detectar, reproducir, analizar, corregir y volver a probar.
Por último, hay que evitar confiar demasiado en una única métrica. La cobertura, el porcentaje de éxito o el número de pruebas ejecutadas son datos útiles, pero no cuentan toda la historia. Lo importante es que las pruebas estén bien diseñadas y cubran situaciones relevantes.
Conclusión
Las pruebas de software son una parte esencial del desarrollo. No son una fase decorativa ni un trámite para entregar un proyecto. Son la forma de comprobar que el programa funciona, que cumple requisitos, que responde ante situaciones incorrectas y que los cambios futuros no rompen lo que ya estaba bien.
Un buen desarrollador no solo escribe código. También sabe probarlo, explicar cómo lo ha probado y justificar por qué confía en que funciona correctamente. Esta idea, que aparece de forma muy clara en el documento base, resume perfectamente el papel de las pruebas de software en cualquier proyecto serio.
En mi forma de trabajar, tanto en desarrollo como en consultoría, docencia y proyectos propios, las pruebas de software encajan con una idea sencilla: la tecnología debe ser útil, clara y aplicable. Y para que sea útil de verdad, no basta con que funcione una vez. Tiene que estar pensada, probada, documentada y preparada para cambiar sin romperse.
Preguntas frecuentes sobre pruebas de software
¿Qué son las pruebas de software?
Las pruebas de software son actividades destinadas a comprobar el comportamiento de un programa y detectar errores. Sirven para comparar el resultado esperado con el resultado obtenido y ayudan a mejorar la calidad del software.
¿Por qué son importantes las pruebas de software?
Son importantes porque reducen el riesgo de errores, ayudan a comprobar requisitos, facilitan el mantenimiento, aumentan la confianza del equipo y permiten detectar problemas antes de que lleguen al usuario.
¿Qué diferencia hay entre verificación y validación?
La verificación comprueba si estamos construyendo bien el producto según las especificaciones. La validación comprueba si estamos construyendo el producto correcto para la necesidad real del usuario.
¿Qué tipos de pruebas de software existen?
Entre las pruebas de software más importantes están las pruebas funcionales, estructurales, de regresión, unitarias, de integración, de sistema y de aceptación.
¿Qué debe incluir un caso de prueba?
Un caso de prueba debe incluir identificador, nombre, objetivo, precondiciones, datos de entrada, pasos, resultado esperado, resultado obtenido, estado y observaciones.
¿Qué son las pruebas unitarias?
Las pruebas unitarias comprueban unidades pequeñas de código, como métodos o clases. Son útiles para verificar que una parte concreta del programa funciona de forma aislada.
¿Qué son las pruebas de regresión?
Las pruebas de regresión comprueban que un cambio reciente no ha roto funcionalidades que antes funcionaban. Son especialmente importantes al corregir errores o añadir nuevas funciones.
¿Qué es una incidencia en pruebas de software?
Una incidencia es el registro documentado de un fallo o comportamiento incorrecto. Debe incluir descripción, pasos para reproducirlo, resultado esperado, resultado obtenido, entorno, severidad, prioridad y evidencias.


