A Filosofia por Trás das Asserções: ASSERT vs. EXPECT
A diferença fundamental entre as macros ASSERT_* e EXPECT_* no Google Test (GTest) vai além de simplesmente "interromper" ou "continuar" a execução. A escolha correta depende da intenção do teste e da gestão de falhas em cascata.
Considere um teste de inicialização de sistema:
TEST(SystemBootstrapTest, CompleteInitializationFlow) {
auto* db_conn = establish_db_connection();
ASSERT_TRUE(db_conn != nullptr) << "Falha na conexão com o banco de dados, abortando testes subsequentes";
auto app_settings = load_settings("config.yaml");
EXPECT_FALSE(app_settings.is_empty()) << "O arquivo de configuração está vazio";
auto session_mgr = create_session_manager(db_conn);
EXPECT_GT(session_mgr->active_count(), 0) << "Deve haver sessões ativas";
}
A lógica de camadas aqui é crucial:
ASSERT_TRUE(db_conn != nullptr)estabelece uma pré-condição crítica. Sem uma conexão ativa, qualquer operação subsequente falhará ou causará um erro de segmentação. Interromper imediatamente economiza tempo e evita ruído nos logs.- As macros
EXPECT_*funcionam como verificações paralelas. Se a configuração estiver vazia, ainda é valioso saber se o gerenciador de sessões foi instanciado corretamente, coletando o máximo de informações de diagnóstico em uma única execução.
Além disso, o GTest utiliza especialização de templates para garantir segurança de tipos. A macro EXPECT_EQ(a, b) adapta sua estratégia de comparação:
- Para tipos primitivos, realiza comparação direta.
- Para strings no estilo C (
const char*), compara o conteúdo e não os endereços de memória. - Para contêineres da STL (como
std::vector), realiza comparação recursiva e detalha as diferenças em caso de falha.
std::vector<int> expected_metrics = {10, 20, 30};
std::vector<int> actual_metrics = collect_metrics();
EXPECT_EQ(actual_metrics, expected_metrics);
Se a comparação falhar, o GTest integrará a sobrecarga do operador operator<< (se disponível) para exibir exatamente onde a divergência ocorreu, acelerando significativamente a depuração.
Organização de Testes: TEST vs. TEST_F
A escolha entre TEST e TEST_F dita como o estado é gerenciado durante a execução da suíte de testes.
Testes Independentes com TEST
Para funções puras, algoritmos ou utilitários sem efeitos colaterais, TEST é a escolha ideal. Cada teste é isolado e não compartilha estado.
TEST(StringUtilsTest, ConcatenateWithSeparator) {
EXPECT_EQ(join_strings({"x", "y", "z"}, "|"), "x|y|z");
EXPECT_EQ(join_strings({}, ","), "");
}
TEST(MathOperationsTest, FactorialBaseCase) {
EXPECT_EQ(calculate_factorial(0), 1);
}
Compartilhamento de Estado com TEST_F
Quando os testes exigem um contexto complexo ou objetos com estado, TEST_F (Test Fixture) evita a duplicação de código de configuração.
class RingBufferTest : public ::testing::Test {
protected:
void SetUp() override {
ring_buf_ = std::make_unique<RingBuffer>(2048);
ring_buf_->push("data", 4);
}
void TearDown() override {
ring_buf_.reset();
}
std::unique_ptr<RingBuffer> ring_buf_;
};
TEST_F(RingBufferTest, PopRetrievesPushedData) {
char out[10] = {};
size_t bytes = ring_buf_->pop(out, sizeof(out));
EXPECT_EQ(bytes, 4);
EXPECT_STREQ(out, "data");
}
TEST_F(RingBufferTest, SizeReflectsPushOperations) {
EXPECT_EQ(ring_buf_->size(), 4);
ring_buf_->push("_more", 5);
EXPECT_EQ(ring_buf_->size(), 9);
}
O ciclo de vida do TEST_F garante que uma nova instância da classe fixture seja criada para cada teste, executando SetUp() antes e TearDown() depois. Isso previne vazamentos de estado entre casos de teste.
graph TD
A[Iniciar Runner de Testes] --> B{Analisar TEST/TEST_F}
B --> C[Identificar TEST: MathOperationsTest.*]
B --> D[Identificar TEST_F: RingBufferTest.*]
C --> E[Executar Corpo do Teste Diretamente]
D --> F[Instanciar RingBufferTest]
F --> G[Invocar SetUp]
G --> H[Executar Corpo do TEST_F]
H --> I[Invocar TearDown]
I --> J[Destruir Instância]
E & J --> K[Agregar Resultados e Gerar Relatório]
Asserções Avançadas: Precisão e Exceções
Comparação de Ponto Flutuante
Devido às limitações do padrão IEEE 754, a comparação direta de floats com == é propensa a erros. O GTest fornece macros especializadas que utilizam tolerância baseada em ULP (Units in the Last Place).
TEST(PhysicsMathTest, VelocityCalculationPrecision) {
float computed_velocity = 9.8f * 3.0f;
EXPECT_FLOAT_EQ(computed_velocity, 29.4f);
double precise_pi = 3.14159265358979;
EXPECT_NEAR(precise_pi, M_PI, 1e-12);
}
Validação de Exceções
Verificar se um código falha graciosamante é tão importante quanto verificar seu sucesso. As macros de exceção garantem que os caminhos de erro sejam acionados corretamente.
TEST(YamlParserTest, ThrowsOnInvalidSyntax) {
EXPECT_THROW(parse_yaml("key: [unterminated"), YamlSyntaxException);
EXPECT_ANY_THROW(parse_yaml(""));
EXPECT_NO_THROW(parse_yaml("key: value"));
}
Google Mock: Controlando Comportamentos e Dependências
Enquanto o GTest valida o estado e os resultados, o Google Mock (GMock) verifica as interações e o comportamento entre objetos.
Isolamento de Dependências
Considere um serviço de notificação que depende de gateways externos. Testar isso com implementações reais introduz lentidão, custos e não determinismo.
class EmailGateway {
public:
virtual ~EmailGateway() = default;
virtual bool dispatch(const std::string& recipient, const std::string& body) = 0;
};
class MockEmailGateway : public EmailGateway {
public:
MOCK_METHOD(bool, dispatch, (const std::string&, const std::string&), (override));
};
A macro MOCK_METHOD (padrão a partir do C++17) unifica a sintaxe, aceitando o tipo de retorno, nome do método, parâmetros e qualificadores.
Modos de Rigor: Nice, Naggy e Strict
| Template | Comportamento | Caso de Uso |
|---|---|---|
NiceMock<T> |
Ignora chamadas não esperadas silenciosamente | Testes de integração parcial, mocks auxiliares |
NaggyMock<T> |
Emite avisos para chamadas não esperadas (Padrão) | Desenvolvimento ativo e depuração |
StrictMock<T> |
Falha no teste se houver chamadas não esperadas | Testes de regressão, validação estrita de contratos |
TEST(NotificationTest, RetryOnTransientFailure) {
StrictMock<MockEmailGateway> mock_gateway;
NiceMock<MockMetricsCollector> mock_metrics;
ON_CALL(mock_gateway, dispatch(_, _)).WillByDefault(Return(false));
EXPECT_CALL(mock_gateway, dispatch(StrEq("admin@sys.com"), _))
.Times(1)
.WillOnce(Return(true));
NotificationDispatcher svc(&mock_gateway, &mock_metrics);
EXPECT_TRUE(svc.send_alert("admin@sys.com", "Server Down"));
}
ON_CALL vs. EXPECT_CALL
A distinção entre estas duas macros é fundamental para a legibilidade do teste:
ON_CALL: Define um comportamento padrão (stub). Não exige que o método seja chamado.EXPECT_CALL: Estabelece uma expectativa estrita. O teste falhará se a chamada não ocorrer conforme especificado.
ON_CALL(mock_db, fetch_record(_, _))
.WillByDefault(Return(DatabaseRecord{}));
EXPECT_CALL(mock_db, fetch_record(StrEq("users"), Eq(1)))
.Times(1)
.WillOnce(Return(active_users_page));
Matchers e Actions: Refinando Interações
Os matchers permitem validar argumentos complexos de forma declarativa.
EXPECT_CALL(mock_cache, store(Eq("user_profile"), Gt(0), Pointee(StrEq("verified"))))
.Times(AtLeast(1));
As actions definem o que o mock deve fazer quando invocado:
Return(value): Retorna uma cópia do valor.ReturnRef(ref): Retorna uma referência, evitando cópias de objetos grandes.DoAll(a1, a2): Executa múltiplas ações em sequência.Invoke(func): Delega a chamada para uma função ou método real.
EXPECT_CALL(mock_db, execute_query(_, _, _))
.WillOnce(DoAll(
Invoke(&audit_logger, &AuditLogger::log_query),
Return(true)
));
EXPECT_CALL(mock_api, fetch_status())
.WillOnce(Return(503))
.WillOnce(Return(503))
.WillRepeatedly(Return(200));
Práticas de Engenharia e Ciclo de Vida
Injeção de Dependência
A testabilidade exige que as dependências sejam injetadas, preferencialmente através de interfaces abstratas no construtor.
class InvoiceGenerator {
public:
explicit InvoiceGenerator(TaxCalculator* calc) : tax_calc_(calc) {}
double generate(double base_amount);
private:
TaxCalculator* tax_calc_;
};
Gerenciamento de Expectativas
Ao usar fixtures com mocks compartilhados, é crucial limpar as expectativas no TearDown para evitar vazamento de estado entre testes.
void TearDown() override {
testing::Mock::VerifyAndClearExpectations(gateway_.get());
}
Validação de Sequência
Para fluxos onde a ordem das chamadas é crítica, o bloco InSequence impõe uma ordem estrita de execução.
{
InSequence seq;
EXPECT_CALL(mock_auth, authenticate("admin")).WillOnce(Return(true));
EXPECT_CALL(mock_auth, generate_token("admin")).WillOnce(Return("jwt_token"));
}
sequenceDiagram
participant Test
participant SUT
participant Mock
Test->>SUT: Instanciar objeto (Injetar Mock)
Test->>Mock: Configurar EXPECT_CALL(...)
Test->>SUT: Invocar método sob teste
SUT->>Mock: Acionar método virtual
Mock-->>SUT: Retornar valor configurado
Test->>Mock: VerifyAndClearExpectations()
Note right of Test: Validar conformidade das interações
Este diagrama serve como modelo padrão para o design de cada teste com Mock. Cada etapa responde a uma pergunta fundamental:
- A dependência foi injetada corretamente?
- As expectativas foram configuradas?
- O método sob teste foi acionado?
- As interações foram validadas?
Este ciclo de quatro etapas garante a integridade e a confiabilidade de cada caso de teste isolado.