Pruebas en C++: Tests Unitarios, de Integración y E2E con ejemplos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: Por qué las pruebas son críticas para proyectos C++



Probar el código en C++ no es solo una formalidad, sino una práctica fundamental que garantiza la estabilidad, seguridad y mantenibilidad del software. A diferencia de los lenguajes interpretados, donde un error puede manifestarse instantáneamente, en C++ los bugs a menudo permanecen ocultos hasta el momento de la compilación o la ejecución en condiciones específicas. Una fuga de memoria pasada por alto, un comportamiento indefinido o un manejo incorrecto de punteros pueden provocar fallos fatales.



La industria del desarrollo moderna destaca tres niveles principales de pruebas: Unit (pruebas unitarias), Integration (pruebas de integración) y E2E (pruebas de extremo a extremo). Cada uno resuelve su propia tarea y juntos forman la "pirámide de pruebas", permitiendo detectar hasta el 90% de los errores antes del lanzamiento a producción.



En este artículo, analizaremos cada tipo de prueba con ejemplos prácticos en C++, utilizando los frameworks populares Google Test y Catch2, y también discutiremos las mejores prácticas para su aplicación.



1. Pruebas Unitarias: Verificación de módulos atómicos



Las pruebas unitarias son el nivel más bajo y el más rápido. Verifican funciones, clases o métodos individuales de forma aislada del resto del sistema. El objetivo es asegurarse de que cada "bloque" de código funcione correctamente.



Ejemplo de prueba unitaria con Google Test



Consideremos una función matemática simple:



// math_utils.h#pragma once

namespace MathUtils { int add(int a, int b); int divide(int a, int b);}


// math_utils.cpp#include "math_utils.h"

namespace MathUtils { int add(int a, int b) { return a + b; }

int divide(int a, int b) { if (b == 0) { throw std::invalid_argument("Division by zero"); } return a / b; }}


Ahora escribamos pruebas unitarias con Google Test:



// test_math_utils.cpp#include <gtest/gtest.h>#include "math_utils.h"

TEST(MathUtilsTest, AddPositiveNumbers) { EXPECT_EQ(MathUtils::add(2, 3), 5); EXPECT_EQ(MathUtils::add(0, 0), 0);}

TEST(MathUtilsTest, AddNegativeNumbers) { EXPECT_EQ(MathUtils::add(-1, -1), -2); EXPECT_EQ(MathUtils::add(-5, 3), -2);}

TEST(MathUtilsTest, DivideNormal) { EXPECT_EQ(MathUtils::divide(10, 2), 5); EXPECT_EQ(MathUtils::divide(7, 3), 2); // División de enteros}

TEST(MathUtilsTest, DivideByZeroThrows) { EXPECT_THROW(MathUtils::divide(5, 0), std::invalid_argument);}


Uso de objetos Mock



Para aislar un módulo, a menudo es necesario simular dependencias. Google Mock (parte de Google Test) permite crear stubs:



// database_interface.hclass DatabaseInterface {public:    virtual ~DatabaseInterface() = default;    virtual bool save(const std::string& data) = 0;};

// user_service.cppclass UserService { DatabaseInterface& db;public: explicit UserService(DatabaseInterface& db) : db(db) {} bool registerUser(const std::string& name) { return db.save("user:" + name); }};

// test_user_service.cpp#include <gmock/gmock.h>

class MockDatabase : public DatabaseInterface {public: MOCK_METHOD(bool, save, (const std::string&), (override));};

TEST(UserServiceTest, RegisterUserCallsDb) { MockDatabase mockDb; EXPECT_CALL(mockDb, save("user:Alice")).WillOnce(testing::Return(true));

UserService service(mockDb); EXPECT_TRUE(service.registerUser("Alice"));}


Ventajas: Velocidad de ejecución (milisegundos), localización precisa de errores, posibilidad de ejecutar sin dependencias externas.
Desventajas: No verifican la interacción entre componentes reales.



2. Pruebas de Integración: Verificación de la combinación de componentes



Las pruebas de integración verifican cómo varios mó

Blogs

Book Recommendations