Introdução: Por que os testes são criticamente importantes para projetos C++
Testar código C++ não é apenas uma formalidade, mas uma prática fundamental que garante a estabilidade, segurança e capacidade de manutenção do software. Diferente de linguagens interpretadas, onde um erro pode se manifestar instantaneamente, em C++ os bugs frequentemente permanecem ocultos até o momento da compilação ou execução em condições específicas. Um vazamento de memória perdido, comportamento indefinido ou manipulação incorreta de ponteiros podem levar a falhas fatais.
A indústria moderna de desenvolvimento destaca três níveis principais de teste: Unit (unitário), Integration (integração) e E2E (ponta a ponta). Cada um resolve seu próprio problema e juntos formam a "pirâmide de testes", permitindo capturar até 90% dos erros antes da entrada em produção.
Neste artigo, analisaremos cada tipo de teste com exemplos práticos em C++, usando os frameworks populares Google Test e Catch2, além de discutir as melhores práticas para sua aplicação.
1. Teste Unitário: Verificação de módulos atômicos
Testes unitários são o nível mais baixo e mais rápido. Eles verificam funções, classes ou métodos individuais isolados do resto do sistema. O objetivo é garantir que cada "bloco de construção" do código funcione corretamente.
Exemplo de Teste Unitário com Google Test
Considere uma função matemática simples:
// 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; }}Agora vamos escrever testes unitários usando 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); // Divisão inteira}
TEST(MathUtilsTest, DivideByZeroThrows) { EXPECT_THROW(MathUtils::divide(5, 0), std::invalid_argument);}Uso de Mock Objects
Para isolar um módulo, muitas vezes é necessário substituir dependências. Google Mock (parte do Google Test) permite criar 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"));}Vantagens: Velocidade de execução (milissegundos), localização precisa de erros, possibilidade de executar sem dependências externas.
Desvantagens: Não verificam a interação entre componentes reais.
2. Teste de Integração: Verificação da combinação de componentes
Testes de integração verificam como vários mo