C Testes (assert)
Como você sabe que o seu programa funciona? Até agora, rodando e olhando a saída. Isso funciona uma vez; mas, a cada mudança no código, você precisaria rodar e conferir tudo de novo, à mão, e um dia vai esquecer de conferir justo a parte que quebrou. Nesta aula você vai aprender duas ferramentas: o assert, que faz o programa conferir sozinho as coisas que "têm de ser verdade", e os testes automáticos: um programa que chama as suas funções com valores conhecidos e confere as respostas, sempre que você quiser, em um segundo.
- O que faz o
assert(daassert.h) e como ler a mensagemAssertion … failed. - O que é
NDEBUGe por que nunca colocar algo "que precisa acontecer" dentro doassert. - A escrever testes automáticos com uma macro
TESTE(condição)que conta acertos e falhas. - Por que funções puras são fáceis de testar.
- A rodar os testes com o CTest (do CMake) e os nomes dos frameworks de teste para C: Unity, CMocka e Criterion.
As saídas de terminal desta aula são reais, do gcc 15 na máquina fictícia da Ana (pasta ~/c/testes). O assert também funciona no editor do guia, mas a mensagem tem outras palavras (seção 1).
A ideia: o fiscal de qualidade
Numa fábrica de bolos, há duas conferências diferentes:
- Durante o preparo, o confeiteiro tem regras que nunca podem ser quebradas: "o forno tem de estar ligado antes de pôr a massa". Se estiver desligado, algo está muito errado: ele para tudo e chama alguém. Isso é o
assert: uma certeza escrita no código, conferida enquanto o programa roda. - Antes de vender um bolo novo, o fiscal prova amostras com uma lista: "o de chocolate tem gosto de chocolate? o de 1 kg pesa 1 kg?". Isso é o teste: um programa separado, com exemplos cuja resposta você já sabe.
1. assert: "isto tem de ser verdade"
A lojinha da Ana tem uma função que retira produtos do estoque. Ela só faz sentido com uma quantidade positiva e que exista no estoque:
#include <assert.h>
#include <stdio.h>
int retirar(int estoque, int quantidade) {
assert(quantidade > 0);
assert(quantidade <= estoque);
return estoque - quantidade;
}
int main(void) {
int estoque = 10;
estoque = retirar(estoque, 3);
printf("Sobrou: %d\n", estoque);
estoque = retirar(estoque, 9);
printf("Sobrou: %d\n", estoque);
return 0;
}
- #include <assert.h>
- O
assertmora no cabeçalhoassert.h. Assert quer dizer "afirmar": você afirma que algo é verdade. - assert(quantidade > 0);
- Se a condição for verdadeira, não acontece nada e o programa segue. Se for falsa, o programa mostra uma mensagem com a condição, o arquivo e a linha, e é encerrado na hora com
abort(). - assert(quantidade <= estoque);
- A segunda certeza: não dá para retirar mais do que existe.
$ gcc -std=c23 -Wall -Wextra estoque.c -o estoque $ ./estoque Sobrou: 7 estoque: estoque.c:6: retirar: Assertion `quantidade <= estoque' failed. Abortado (imagem do núcleo gravada) ./estoque $ echo $? 134
- Sobrou: 7
- A primeira retirada (3 de 10) passou nos dois
assert. - estoque: estoque.c:6: retirar:
- O nome do programa, o arquivo e a linha do
assertque falhou e a função onde ele está. - Assertion `quantidade <= estoque' failed.
- "A afirmação
quantidade <= estoquefalhou": a condição, copiada do código, entre uma crase e um apóstrofo. Tentaram retirar 9 de um estoque de 7. - Abortado (imagem do núcleo gravada)
- O
assertchamouabort(), que mata o programa (veja Encerrar o Programa). O segundo "Sobrou" nunca apareceu. - 134
- O código de saída de um programa abortado (128 + 6, o número do sinal
SIGABRT; veja Sinais).
No editor do guia, a biblioteca do C é outra (a musl, não a glibc), e a mensagem tem outro formato, com as mesmas informações:
Sobrou: 7 Assertion failed: quantidade <= estoque (estoque.c: retirar: 6) Abortado (imagem do núcleo gravada) [código de saída: 134]
Missão: este programa roda sem erro. Acrescente, antes do return 0;, uma chamada a retirar que faça falhar o primeiro assert (o quantidade > 0). Leia a mensagem e repare no código de saída 134.
#include <assert.h>
#include <stdio.h>
int retirar(int estoque, int quantidade) {
assert(quantidade > 0);
assert(quantidade <= estoque);
return estoque - quantidade;
}
int main(void) {
int estoque = 10;
estoque = retirar(estoque, 3);
printf("Sobrou: %d\n", estoque);
estoque = retirar(estoque, 5);
printf("Sobrou: %d\n", estoque);
return 0;
}
#include <assert.h>
#include <stdio.h>
int retirar(int estoque, int quantidade) {
assert(quantidade > 0);
assert(quantidade <= estoque);
return estoque - quantidade;
}
int main(void) {
int estoque = 10;
estoque = retirar(estoque, 3);
printf("Sobrou: %d\n", estoque);
estoque = retirar(estoque, 5);
printf("Sobrou: %d\n", estoque);
estoque = retirar(estoque, 0);
return 0;
}
Uma mensagem junto: && "texto"
O assert mostra a condição como ela está escrita. Um truque comum para deixar a mensagem mais clara: acrescentar && "explicação". Uma string literal é sempre "verdadeira" (é um ponteiro que não é nulo), então ela não muda o resultado, mas aparece na mensagem:
#include <assert.h>
#include <stdio.h>
int main(void) {
int alunos = 0;
assert(alunos > 0 && "a turma não pode estar vazia");
printf("Média por aluno: %d\n", 30 / alunos);
return 0;
}
$ gcc -std=c23 -Wall -Wextra msg.c -o msg $ ./msg msg: msg.c:6: main: Assertion `alunos > 0 && "a turma não pode estar vazia"' failed. Abortado (imagem do núcleo gravada) ./msg
Sem o assert, o programa chegaria à divisão por zero (veja Comportamento Indefinido). Com ele, para antes, explicando o porquê.
2. Para que serve (e para que não serve) o assert
O assert é para erros do programador: coisas que, se o código estiver certo, nunca acontecem. Exemplos:
- Um parâmetro que tem de vir num certo intervalo:
assert(mes >= 1 && mes <= 12); - Um ponteiro que nunca pode ser nulo:
assert(lista != NULL);(veja Ponteiro Nulo). - Um resultado que tem de fazer sentido:
assert(total >= 0);
Ele não é para erros que podem acontecer de verdade, com o programa certo: o usuário digitar letras em vez de número, um arquivo que não existe, o malloc devolver NULL. Esses casos você trata com if, mostrando uma mensagem e seguindo (ou saindo com um código de erro); veja Validar a Entrada e errno. Um motivo forte é o da próxima seção: o assert pode ser desligado.
3. NDEBUG: desligando os assert
Se o nome NDEBUG (no debug, "sem depuração") estiver definido quando o assert.h é incluído, todo assert vira nada: o compilador nem gera o teste. A opção -D do gcc define um nome (veja Compilação Condicional):
$ gcc -std=c23 -Wall -Wextra -DNDEBUG estoque.c -o estoque $ ./estoque Sobrou: 7 Sobrou: -2 $ echo $? 0
Sem os assert, o erro passou: o estoque ficou negativo e o programa terminou "com sucesso". É por isso que o assert não serve para validar dados do mundo real. O NDEBUG existe para versões finais em que cada nanossegundo conta: o modo Release do CMake, por exemplo, acrescenta -DNDEBUG sozinho (veja CMake). Também dá para definir no próprio código, antes do include:
#define NDEBUG #include <assert.h>
assert algo que precisa acontecer
Com NDEBUG, a linha inteira some, inclusive o que estava dentro dos parênteses. Este programa lê a idade dentro do assert:
#include <assert.h>
#include <stdio.h>
int main(void) {
int idade = 0;
printf("Idade: ");
assert(scanf("%d", &idade) == 1);
printf("Ano que vem: %d\n", idade + 1);
return 0;
}
$ gcc -std=c23 -Wall -Wextra idade.c -o idade $ echo 29 | ./idade Idade: Ano que vem: 30 $ gcc -std=c23 -Wall -Wextra -DNDEBUG idade.c -o idade $ echo 29 | ./idade Idade: Ano que vem: 1
Na versão com -DNDEBUG, o scanf sumiu junto com o assert: a idade nunca foi lida e ficou 0. O certo é ler fora e conferir com if: if (scanf("%d", &idade) != 1) { … }.
Para conferências que podem ser feitas na compilação (por exemplo, "um int tem pelo menos 4 bytes"), existe o static_assert, que não custa nada quando o programa roda e não é desligado pelo NDEBUG (veja static_assert).
4. Testes automáticos
A Ana escreveu duas funções de datas, num arquivo separado do resto do programa:
#ifndef DATAS_H #define DATAS_H bool eh_bissexto(int ano); int dias_no_mes(int mes, int ano); #endif
#include "datas.h"
bool eh_bissexto(int ano) {
return ano % 4 == 0;
}
int dias_no_mes(int mes, int ano) {
switch (mes) {
case 2:
return eh_bissexto(ano) ? 29 : 28;
case 4: case 6: case 9: case 11:
return 30;
default:
return 31;
}
}
(O bool não precisa de include porque, no C23, ele é palavra-chave; veja Booleanos.) Um teste é um outro programa, com um main próprio, que chama essas funções com exemplos cuja resposta a gente sabe de cor. A regra do ano bissexto: divisível por 4, exceto os divisíveis por 100, a não ser que sejam divisíveis por 400. Então 2024 é, 2023 não é, 1900 não é e 2000 é.
Primeira tentativa: uma lista de assert
#include <assert.h>
#include <stdio.h>
#include "datas.h"
int main(void) {
assert(eh_bissexto(2024));
assert(!eh_bissexto(2023));
assert(!eh_bissexto(1900));
assert(eh_bissexto(2000));
printf("Todos os testes passaram.\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra datas.c teste_assert.c -o teste_assert $ ./teste_assert teste_assert: teste_assert.c:8: main: Assertion `!eh_bissexto(1900)' failed. Abortado (imagem do núcleo gravada) ./teste_assert $ echo $? 134
Achou um erro: 1900 não deveria ser bissexto. Mas o primeiro assert que falha para tudo: você não sabe se o 2000 passaria, nem quantos testes falham ao todo. Para testes, é melhor algo que confira todos e conte no fim.
Uma macro TESTE que conta
#include <stdio.h>
#include "datas.h"
static int testes = 0;
static int falhas = 0;
#define TESTE(cond) do { \
testes++; \
if (cond) { \
printf("ok %s\n", #cond); \
} else { \
falhas++; \
printf("FALHOU %s (%s:%d)\n", #cond, __FILE__, __LINE__); \
} \
} while (0)
int main(void) {
TESTE(eh_bissexto(2024));
TESTE(!eh_bissexto(2023));
TESTE(!eh_bissexto(1900));
TESTE(eh_bissexto(2000));
TESTE(dias_no_mes(1, 2025) == 31);
TESTE(dias_no_mes(4, 2025) == 30);
TESTE(dias_no_mes(2, 2024) == 29);
TESTE(dias_no_mes(2, 1900) == 28);
printf("%d de %d testes passaram\n", testes - falhas, testes);
return falhas == 0 ? 0 : 1;
}
- static int testes = 0; static int falhas = 0;
- Dois contadores, visíveis só neste arquivo (veja static e extern).
- #define TESTE(cond) …
- Uma macro com parâmetro: cada
TESTE(x)no código é trocado pelo bloco inteiro, comxno lugar decond. A\no fim de cada linha continua a macro na linha seguinte. - do { … } while (0)
- Um laço que roda uma vez só. É o jeito tradicional de fazer uma macro de várias linhas se comportar como um comando único, que pede
;no fim e funciona dentro de umifsem chaves. - #cond
- O
#antes do parâmetro transforma o texto do argumento numa string:TESTE(eh_bissexto(2024))imprime"eh_bissexto(2024)". É assim que oassertmostra a condição. - __FILE__ e __LINE__
- Nomes que o pré-processador troca pelo nome do arquivo e pelo número da linha onde aparecem (veja Pré-processador). Como a macro é expandida na linha do
TESTE, a linha é a do teste. - return falhas == 0 ? 0 : 1;
- O código de saída diz se tudo passou: 0 = sucesso, 1 = alguma falha. Ferramentas como o
make, o CTest e os servidores de integração contínua olham esse código.
$ gcc -std=c23 -Wall -Wextra datas.c teste_datas.c -o teste_datas $ ./teste_datas ok eh_bissexto(2024) ok !eh_bissexto(2023) FALHOU !eh_bissexto(1900) (teste_datas.c:20) ok eh_bissexto(2000) ok dias_no_mes(1, 2025) == 31 ok dias_no_mes(4, 2025) == 30 ok dias_no_mes(2, 2024) == 29 FALHOU dias_no_mes(2, 1900) == 28 (teste_datas.c:25) 6 de 8 testes passaram $ echo $? 1
Agora o relatório é completo: duas falhas, e as duas apontam para a mesma causa (1900). O eh_bissexto(2000) passou por acaso: a regra "divisível por 4" acerta 2000, mas pelo motivo errado. Depois de consertar eh_bissexto com a regra inteira, a Ana roda os mesmos testes:
$ gcc -std=c23 -Wall -Wextra datas.c teste_datas.c -o teste_datas $ ./teste_datas ok eh_bissexto(2024) ok !eh_bissexto(2023) ok !eh_bissexto(1900) ok eh_bissexto(2000) ok dias_no_mes(1, 2025) == 31 ok dias_no_mes(4, 2025) == 30 ok dias_no_mes(2, 2024) == 29 ok dias_no_mes(2, 1900) == 28 8 de 8 testes passaram $ echo $? 0
Daqui em diante, toda vez que mexer em datas.c, ela roda ./teste_datas e sabe em um segundo se quebrou alguma coisa. Teste que já pegou um erro fica para sempre: ele garante que o erro não volta.
Missão: conserte eh_bissexto (aba datas.c) com a regra completa: divisível por 4 e não por 100, ou divisível por 400 (veja Operadores Lógicos). Não mexa nos testes: a missão termina quando aparecer 8 de 8 testes passaram.
#include <stdio.h>
#include "datas.h"
static int testes = 0;
static int falhas = 0;
#define TESTE(cond) do { \
testes++; \
if (cond) { \
printf("ok %s\n", #cond); \
} else { \
falhas++; \
printf("FALHOU %s (%s:%d)\n", #cond, __FILE__, __LINE__); \
} \
} while (0)
int main(void) {
TESTE(eh_bissexto(2024));
TESTE(!eh_bissexto(2023));
TESTE(!eh_bissexto(1900));
TESTE(eh_bissexto(2000));
TESTE(dias_no_mes(1, 2025) == 31);
TESTE(dias_no_mes(4, 2025) == 30);
TESTE(dias_no_mes(2, 2024) == 29);
TESTE(dias_no_mes(2, 1900) == 28);
printf("%d de %d testes passaram\n", testes - falhas, testes);
return falhas == 0 ? 0 : 1;
}
#include "datas.h"
bool eh_bissexto(int ano) {
return ano % 4 == 0;
}
int dias_no_mes(int mes, int ano) {
switch (mes) {
case 2:
return eh_bissexto(ano) ? 29 : 28;
case 4: case 6: case 9: case 11:
return 30;
default:
return 31;
}
}
#ifndef DATAS_H #define DATAS_H bool eh_bissexto(int ano); int dias_no_mes(int mes, int ano); #endif
#include "datas.h"
bool eh_bissexto(int ano) {
return (ano % 4 == 0 && ano % 100 != 0) || ano % 400 == 0;
}
int dias_no_mes(int mes, int ano) {
switch (mes) {
case 2:
return eh_bissexto(ano) ? 29 : 28;
case 4: case 6: case 9: case 11:
return 30;
default:
return 31;
}
}
5. Funções puras são fáceis de testar
Repare que eh_bissexto e dias_no_mes são fáceis de testar porque são funções puras: recebem tudo pelos parâmetros, devolvem o resultado com return e não fazem mais nada (não leem o teclado, não imprimem, não mexem em variáveis globais). A mesma entrada dá sempre a mesma saída.
Compare com esta função, que mistura conta e conversa com o usuário:
void mostrar_media(void) {
double a, b;
scanf("%lf %lf", &a, &b);
printf("Média: %.1f\n", (a + b) / 2);
}
Para testá-la, seria preciso digitar números e ler a tela. Separe a conta (pura, testável) da entrada e saída:
double media(double a, double b) {
return (a + b) / 2;
}
/* no main: lê com scanf, chama media(a, b), mostra com printf */
Agora TESTE(media(8, 10) == 9) confere a conta sem teclado nenhum. (Cuidado ao comparar double com ==: contas como 0.1 + 0.2 não dão exatamente 0.3. Compare com uma tolerância, fabs(x - esperado) < 1e-9; veja float e double.)
O que testar
- Um caso comum:
contar_vogais("banana")dá 3. - As bordas: a string vazia, o primeiro e o último mês, o zero, o maior valor.
- Os casos especiais da regra: 1900 e 2000 no bissexto.
- Todo erro que você já encontrou: escreva o teste que o pega antes de consertar.
Missão: agora é você quem escreve os testes. A função contar_vogais conta as vogais minúsculas sem acento de uma string. Já há um teste; acrescente pelo menos mais dois TESTE(contar_vogais(…) == …): por exemplo, a string vazia "" e uma palavra sem vogais, como "psst". Todos têm de passar.
#include <stdio.h>
static int testes = 0;
static int falhas = 0;
#define TESTE(cond) do { \
testes++; \
if (cond) { \
printf("ok %s\n", #cond); \
} else { \
falhas++; \
printf("FALHOU %s (%s:%d)\n", #cond, __FILE__, __LINE__); \
} \
} while (0)
int contar_vogais(const char *s) {
int n = 0;
for (int i = 0; s[i] != '\0'; i++) {
char c = s[i];
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
n++;
}
}
return n;
}
int main(void) {
TESTE(contar_vogais("banana") == 3);
printf("%d de %d testes passaram\n", testes - falhas, testes);
return falhas == 0 ? 0 : 1;
}
#include <stdio.h>
static int testes = 0;
static int falhas = 0;
#define TESTE(cond) do { \
testes++; \
if (cond) { \
printf("ok %s\n", #cond); \
} else { \
falhas++; \
printf("FALHOU %s (%s:%d)\n", #cond, __FILE__, __LINE__); \
} \
} while (0)
int contar_vogais(const char *s) {
int n = 0;
for (int i = 0; s[i] != '\0'; i++) {
char c = s[i];
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
n++;
}
}
return n;
}
int main(void) {
TESTE(contar_vogais("banana") == 3);
TESTE(contar_vogais("") == 0);
TESTE(contar_vogais("psst") == 0);
printf("%d de %d testes passaram\n", testes - falhas, testes);
return falhas == 0 ? 0 : 1;
}
6. Rodar os testes com o CTest
Num projeto com CMake, o programa de teste vira mais um alvo, e o CTest (que vem junto com o CMake) roda todos os testes e resume o resultado. Bastam duas linhas no fim do CMakeLists.txt:
cmake_minimum_required(VERSION 3.20) project(datas C) set(CMAKE_C_STANDARD 23) add_executable(teste_datas teste_datas.c datas.c) target_compile_options(teste_datas PRIVATE -Wall -Wextra) enable_testing() add_test(NAME datas COMMAND teste_datas)
- enable_testing()
- "Ligue os testes" neste projeto.
- add_test(NAME datas COMMAND teste_datas)
- Um teste chamado
datas, que roda o programateste_datas. O CTest considera que passou se o código de saída for 0. Por isso oreturn falhas == 0 ? 0 : 1;.
$ cmake -B build > /dev/null
$ cmake --build build > /dev/null
$ ctest --test-dir build --output-on-failure
Test project /home/ana/c/testes/build
Start 1: datas
1/1 Test #1: datas ............................***Failed 0.00 sec
ok eh_bissexto(2024)
ok !eh_bissexto(2023)
FALHOU !eh_bissexto(1900) (/home/ana/c/testes/teste_datas.c:20)
ok eh_bissexto(2000)
ok dias_no_mes(1, 2025) == 31
ok dias_no_mes(4, 2025) == 30
ok dias_no_mes(2, 2024) == 29
FALHOU dias_no_mes(2, 1900) == 28 (/home/ana/c/testes/teste_datas.c:25)
6 de 8 testes passaram
0% tests passed, 1 tests failed out of 1
Total Test time (real) = 0.00 sec
The following tests FAILED:
1 - datas (Failed)
Errors while running CTest
Com a versão errada de datas.c, o CTest marca ***Failed ("falhou") e, por causa do --output-on-failure ("mostre a saída quando falhar"), mostra o que o teste imprimiu. Repare que o __FILE__ agora tem o caminho completo: o CMake passa ao gcc os arquivos com o caminho inteiro. Com a versão consertada:
$ ctest --test-dir build
Test project /home/ana/c/testes/ok/build
Start 1: datas
1/1 Test #1: datas ............................ Passed 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.00 sec
7. Frameworks de teste para C
A macro TESTE é um framework de teste em miniatura. Em projetos maiores, use um pronto: eles separam os testes em grupos, mostram o valor esperado e o obtido (esperava 28, veio 29), rodam cada teste isolado e geram relatórios para servidores de integração contínua.
| Framework | Como é | Bom para |
|---|---|---|
| Unity | Um .c e dois .h que você copia para o projeto; macros como TEST_ASSERT_EQUAL_INT(28, dias_no_mes(2, 1900)). | Começar; sistemas embarcados (microcontroladores), onde é muito usado. |
| CMocka | Uma biblioteca (sudo apt install libcmocka-dev); tem também "dublês" (mocks) para fingir funções que você não quer chamar de verdade no teste. | Testar código que fala com arquivos, rede ou hardware. |
| Criterion | Uma biblioteca moderna; cada teste é um bloco Test(grupo, nome) { … }, sem precisar de main; roda cada teste num processo separado, então um crash não derruba os outros. | Projetos para Linux e macOS. |
| CTest | Não é um framework, é um executor: roda os seus programas de teste (feitos com qualquer um dos acima, ou com a macro TESTE). | Juntar todos os testes de um projeto CMake. |
Qual usar: para aprender e para projetos pequenos, a macro TESTE desta aula basta. Quando o projeto crescer, Unity é o mais simples de adotar (é só copiar arquivos); Criterion é o mais confortável no Linux. Rode os testes também com os sanitizers ligados: um teste que passa mas lê memória proibida ainda é um erro.
Quando dá errado
#include <assert.h>
$ cat semassert.c
#include <stdio.h>
int main(void) {
int estoque = 0;
assert(estoque > 0);
printf("Estoque: %d\n", estoque);
return 0;
}
$ gcc -std=c23 -Wall -Wextra semassert.c -o semassert
semassert.c: In function ‘main’:
semassert.c:5:5: error: implicit declaration of function ‘assert’ [-Wimplicit-function-declaration]
5 | assert(estoque > 0);
| ^~~~~~
semassert.c:2:1: note: ‘assert’ is defined in header ‘<assert.h>’; this is probably fixable by adding ‘#include <assert.h>’
1 | #include <stdio.h>
+++ |+#include <assert.h>
2 |
O que aconteceu: "declaração implícita da função assert": o gcc não sabe o que é assert. A nota até diz a solução: "assert está definido em <assert.h>; provavelmente resolve acrescentar #include <assert.h>". No editor (Clang), a mensagem é error: use of undeclared identifier 'assert'.
Como resolver: #include <assert.h> no começo do arquivo.
-DNDEBUG (ou no modo Release), o programa passou a se comportar diferente
O que aconteceu: havia algo que precisava acontecer dentro de um assert (um scanf, um i++, uma chamada que muda algo), e ele sumiu junto com o assert (seção 3).
Como resolver: faça a ação fora e confira só o resultado: int lidos = scanf(…); assert(lidos == 1);, ou, melhor ainda para entrada do usuário, um if.
O que aconteceu: o teste confere a coisa errada, por exemplo TESTE(dias_no_mes(2, 1900)) (qualquer número diferente de zero é "verdadeiro") em vez de TESTE(dias_no_mes(2, 1900) == 28).
Como resolver: veja cada teste falhar pelo menos uma vez: estrague a função de propósito (ou escreva o teste antes de consertar o erro) e confira que aparece FALHOU.
main: múltiplas definições de "main"
$ gcc -std=c23 -Wall -Wextra main.c teste_datas.c datas.c -o programa /usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccH62o2w.o: na função "main": teste_datas.c:(.text+0x0): múltiplas definições de "main"; /tmp/ccHxhoAT.o:main.c:(.text+0x0): definido primeiro aqui collect2: error: ld returned 1 exit status
O que aconteceu: você compilou o programa principal (main.c) junto com o de teste (teste_datas.c), e cada um tem o seu main. O ligador (ld) reclama: "múltiplas definições de main; definido primeiro aqui", no main.c (em inglês, multiple definition of `main'). Os nomes /tmp/cc….o são arquivos temporários e mudam a cada vez.
Como resolver: deixe as funções testáveis num arquivo sem main (datas.c) e faça dois executáveis: gcc main.c datas.c -o programa e gcc teste_datas.c datas.c -o teste_datas.
Referência rápida
| Item | O que é |
|---|---|
#include <assert.h> | Traz o assert. |
assert(condição); | Se a condição for falsa: mensagem com arquivo, linha, função e condição, e abort() (código 134). |
assert(cond && "texto"); | O texto aparece na mensagem. |
-DNDEBUG ou #define NDEBUG antes do include | Desliga todos os assert. |
static_assert(cond, "texto"); | Confere na compilação (veja static_assert). |
#cond numa macro | Transforma o argumento em string. |
__FILE__, __LINE__, __func__ | Arquivo, linha e nome da função atuais. |
enable_testing() / add_test(NAME n COMMAND prog) | Testes no CMake. |
ctest --test-dir build --output-on-failure | Roda os testes e mostra a saída dos que falharam. |
Exercícios
1. O cabeçalho
Complete o include necessário para usar assert:
2. Desligar
Complete a opção do gcc que desliga todos os assert:
3. Código de saída
Um programa terminou com Assertion `x > 0' failed. e Abortado. Qual o código de saída (echo $?)?
4. Um teste
Complete o teste que confere que fevereiro de 2023 tem 28 dias:
5. Qual destes é um bom uso de assert?
6. Por que a macro TESTE é melhor que uma lista de assert para testes?
7. Qual destas funções é mais fácil de testar?
Resumo
assert(cond)(daassert.h) confere uma certeza do programador: se for falsa, mostra arquivo, linha, função e condição, e aborta (código 134).-DNDEBUGdesliga osassert(o modo Release do CMake faz isso). Por isso: nada que precise acontecer dentro deles, e nada de usá-los para validar a entrada do usuário.- Teste automático = um programa separado que chama as funções com exemplos de resposta conhecida. Uma macro
TESTE(cond)com#cond,__FILE__e__LINE__confere todos, conta as falhas e devolve 0 ou 1. - Funções puras (só parâmetros e
return) são as mais fáceis de testar: separe a conta da entrada e saída. - Teste os casos comuns, as bordas e cada erro já encontrado. Veja cada teste falhar uma vez.
- CTest (
enable_testing,add_test,ctest) roda tudo; Unity, CMocka e Criterion são frameworks prontos.