Introducción: Por qué las pruebas en C++ no son un lujo, sino una necesidad
El desarrollo en C++ es siempre un equilibrio entre rendimiento y fiabilidad. Los errores de gestión de memoria, el comportamiento indefinido (UB) y las dependencias complejas entre módulos hacen de C++ uno de los lenguajes más exigentes en cuanto a calidad de código. Sin un enfoque sistemático de las pruebas, el proyecto se convierte rápidamente en una «caja negra», donde corregir un error genera dos nuevos.
En este artículo, analizaremos tres niveles clave de pruebas en C++: unit tests (pruebas unitarias), integration tests (pruebas de integración) y e2e tests (pruebas de extremo a extremo). Aprenderá qué herramientas utilizar, cómo escribir pruebas efectivas y cómo construir la pirámide de pruebas para un proyecto en C++.
1. Pruebas unitarias: Verificando cada ladrillo
Las pruebas unitarias son la base de la pirámide de pruebas. Verifican de forma aislada una unidad lógica de código: una función, un método de clase o una estructura pequeña. En C++, las pruebas unitarias deben ejecutarse rápidamente (<1 segundo para cientos de pruebas) y no depender de recursos externos (sistema de archivos, red, bases de datos).
Herramientas para pruebas unitarias en C++
- Google Test (gtest) — el framework más popular de Google. Soporta macros de aserción, fixtures y pruebas parametrizadas.
- Catch2 — una alternativa moderna con sintaxis concisa y soporte para estilo BDD.
- Boost.Test — parte de la biblioteca Boost, una herramienta madura con amplias capacidades.
Ejemplo de prueba unitaria con Google Test
Supongamos que tenemos una función para calcular el factorial:
// math_utils.h#pragma onceunsigned long long factorial(unsigned int n);// math_utils.cpp#include "math_utils.h"
unsigned long long factorial(unsigned int n) { if (n == 0) return 1; unsigned long long result = 1; for (unsigned int i = 1; i <= n; ++i) { result *= i; } return result;}Escribimos la prueba unitaria:
// test_math_utils.cpp#include <gtest/gtest.h>#include "math_utils.h"
TEST(FactorialTest, HandlesZero) { EXPECT_EQ(factorial(0), 1);}
TEST(FactorialTest, HandlesPositiveNumbers) { EXPECT_EQ(factorial(1), 1); EXPECT_EQ(factorial(5), 120); EXPECT_EQ(factorial(10), 3628800);}
TEST(FactorialTest, HandlesLargeInput) { // 20! cabe en unsigned long long EXPECT_EQ(factorial(20), 2432902008176640000ULL);}Mejores prácticas de pruebas unitarias en C++:
- Una prueba, una verificación. Use
EXPECT_EQpara verificaciones suaves yASSERT_EQpara verificaciones críticas. - Aísle las dependencias mediante objetos mock (Google Mock).
- Pruebe casos límite: valores cero, desbordamiento, contenedores vacíos.
- Use
TEST_Fpara inicialización repetitiva (fixtures).
2. Pruebas de integración: Cómo funcionan los módulos juntos
Las pruebas de integración verifican la interacción entre varios módulos o componentes. En C++, esto es especialmente importante, ya que los errores suelen surgir en las interfaces: gestión incorrecta de memoria al pasar objetos, incompatibilidad de interfaces o problemas con la concurrencia.
A diferencia de las pruebas unitarias, las pruebas de integración pueden trabajar con bases de datos reales, archivos o sockets de red, pero generalmente en un entorno de prueba (por ejemplo, archivos temporales o contenedores Docker).
Ejemplo de prueba de integración
Supongamos que tenemos un módulo Logger y un módulo FileManager. Queremos verificar que los logs se escriban correctamente en un archivo.
// file_logger.h#pragma once#include <string>
class FileLogger {public: FileLogger(const std::string& filename); vo