Introdução: Por que testar em C++ não é um luxo, mas uma necessidade
No mundo C++, a qualidade do código é crucial. A linguagem dá ao desenvolvedor um enorme poder sobre a memória e o desempenho, mas esse poder exige alta responsabilidade. Um erro no gerenciamento de memória, uso incorreto de ponteiros ou manipulação incorreta de multithreading pode levar a bugs difíceis de detectar que só aparecem em produção.
Testar é a única maneira confiável de garantir que seu código C++ se comporte conforme o esperado. Neste artigo, percorreremos o caminho desde pequenos testes unitários até verificações completas de ponta a ponta (E2E). Você aprenderá como estruturar adequadamente uma estratégia de teste, quais ferramentas usar e como escrever testes que realmente tragam benefícios.
Abordaremos três níveis da pirâmide de testes: unit (unitário), integration (integração) e e2e (ponta a ponta). Cada um resolve seu próprio problema, e somente a combinação deles oferece total confiança na confiabilidade do programa.
Testes unitários: A base da qualidade
Testes unitários são o nível mais baixo e mais rápido. Eles verificam funções, classes ou métodos individuais isoladamente de dependências externas (banco de dados, sistema de arquivos, rede). O objetivo principal é garantir que cada unidade lógica de código funcione corretamente.
Escolhendo um framework para C++
No ecossistema C++, existem vários frameworks populares para testes unitários:
- Google Test (gtest) — o framework mais comum do Google. Suporta assertions, fixtures, testes parametrizados e mocks (via Google Mock).
- Catch2 — um framework leve e moderno com excelente sintaxe. Particularmente bom para prototipagem rápida de testes.
- Boost.Test — parte da biblioteca Boost. Poderoso, mas mais pesado.
Para o exemplo, usaremos o Google Test, pois é o padrão de facto em muitos projetos C++.
Exemplo de teste unitário em C++
Suponha que temos uma função simples para calcular o fatorial:
// math_utils.h#ifndef MATH_UTILS_H#define MATH_UTILS_H
long long factorial(int n);
#endif
// math_utils.cpp#include "math_utils.h"
long long factorial(int n) { if (n < 0) throw std::invalid_argument("Negative input"); if (n == 0) return 1; long long result = 1; for (int i = 1; i <= n; ++i) { result *= i; } return result;}Agora vamos escrever um teste unitário usando Google Test:
// test_math_utils.cpp#include <gtest/gtest.h>#include "math_utils.h"
TEST(FactorialTest, HandlesZeroInput) { EXPECT_EQ(factorial(0), 1);}
TEST(FactorialTest, HandlesPositiveInput) { EXPECT_EQ(factorial(1), 1); EXPECT_EQ(factorial(5), 120); EXPECT_EQ(factorial(10), 3628800);}
TEST(FactorialTest, ThrowsOnNegative) { EXPECT_THROW(factorial(-1), std::invalid_argument);}Este teste verifica três cenários: caso limite (0), operação normal e tratamento de erro. Observe as macros EXPECT_EQ e EXPECT_THROW — elas não interrompem o teste em caso de erro, apenas o registram.
Objetos Mock para isolamento
Para testes unitários, é crucial isolar o código testado de dependências externas. Para isso, usamos objetos mock. O Google Mock (parte do Google Test) permite criar stubs facilmente:
// database_connector.hclass DatabaseConnector {public: virtual ~DatabaseConnector() = default; virtual bool connect(const std::string& url) = 0; virtual std::vector<int> fetchData() = 0;};
// user_service.cppclass UserService { DatabaseConnector& db;public: UserService(DatabaseConnector& db) : db(db) {} int ge