Paulo Henrique Rodrigues Pinheiro

Um blog sobre programação para programadores!


Testes unitários na linguagem C

Como realizar testes unitários em C?

Dilbert and TDD

People think that writing a unit testing framework has to be complex. In fact, you can write one in just a few lines of code, as this tech note shows. Of course, if you have access to a full-featured testing framework like JUnit, by all means use it. But if you don't, you can still use a simple framework like MinUnit, or whip up your own in a few hours. There's no excuse for not unit testing.

MinUnit by JeraDesign

Inspiração

Para melhorar minhas habilidades em programação, me impus um desafio. Defini alguns exercícios, mas com testes unitários. Não posso falar que pratiquei TDD, pois ao mesmo tempo em que elaborei os exercícios, fui pensando nos testes e pensando no código, e fazendo tudo ao mesmo tempo.

Mas quem quiser experimentar como é trabalhar com um código feito por outra pessoa, com muita coisa já decidida, definida, pode se valer dessa minha empreitada.

Coloquei essa experiência no GitHub. Pretendo definir mais problemas e usar outros frameworks para cada linguagem, e falar um pouco sobre os problemas que encontrei e como os sanei.

Lá, para cada linguagem, há um arquivo problems com o esqueleto das funções a serem desenvolvidas. Dependendo da linguagem, os testes estão no mesmo arquivo ou em arquivo separado. Basta seguir as instruções e hackear um pouco.

Atualmente, os problemas estão codificados em C, Perl e Python.

Linguagem C

Procurei, em cada linguagem, a abordagem mais simplificada, e com esse espírito encontrei o framework MinUnit:


#define mu_assert(message, test) do { if (!(test)) return
message; } while (0)
#define mu_run_test(test) do { char *message = test(); tests_run++; \
                                if (message) return message; } while (0)
extern int tests_run;

Muito simples para instalar e usar. É isso mesmo, essas macros e essa variável global.

Na prática

Imaginemos o seguinte problema: você deve escrever uma função que aceite dois números naturais como parâmetro e execute a subtração deles, na sequência em que foram informados. Se a diferença for gerar um número negativo, deve-se retornar um erro.

Comecemos pelos testes. Usa-se a macro mu_assert, cujo primeiro parâmetro é uma mensagem para o caso de falha, e o segundo parâmetro é uma expressão que se verdadeira indica que o teste funcionou, e caso falsa indica falha.

A nossa função a será chamada N_subtracao. Receberá dois parâmetros e retornará a diferença entre eles. Caso não possa efetuar a conta, retornará -1, que está forá da faixa de valores válidos para números naturais.

Neste fragmento de código, três testes são executados, tentando passar números negativos como se fossem naturais.


mu_assert("Primeiro parâmetro inválido.", -1==N_subtracao( -1,  10));
mu_assert("Segundo parâmetro inválido.",  -1==N_subtracao( 10,  -1));
mu_assert("Os dois parâmetros inválidos", -1==N_subtracao(-10, -20));

Aqui, verifica-se se há alguma garantia de que o resultado não seja negativo:


mu_assert("Resultado negativo.", -1==N_subtracao(10, 20));

E finalmente verifica-se se a função retorna os resultados corretos para valores válidos:


mu_assert("0-0",    0==N_subtracao( 0,  0));
mu_assert("10-0",  10==N_subtracao(10,  0));
mu_assert("10-10",  0==N_subtracao(10, 10));
mu_assert("20-10", 10==N_subtracao(20, 10));

A bateria de testes foi agrupada em funções que são chamadas de outra função que as agrupa:


static char *testes(void) {
    mu_run_test(testa_parametros);
    mu_run_test(testa_resultado_nao_N);
    mu_run_test(testa_resultados_validos);

    return 0;
}

Note que todas as funções envolvidas retornam o tipo (static char *), e retornam o valor 0 caso todos os mu_assert e agregadores mu_run_test passem.

A função

Baseado nessa especificação e nos testes já escritos, aqui está o código para testar:


/* usaremos -1 como indicador de erro */

int N_subtracao(int a, int b) {
    /* Só queremos números naturais como parâmetro. */
    if(a<0 || b<0) {
        return -1;
    }

    /* Só podemos retornar um número natural como resultado. */
    if(a<b) {
        return -1;
    }

    /* sem medo */
    return a-b;
}

Espero que o código fale por si :).

Facilitando

Para não ficar copiando e colando arquivos, você pode baixá-los da seguinte forma:


cd /tmp
git clone https://gist.github.com/1f45a929aba4eb91f376.git minunit

Compilar e executar os testes:


cd minunit
make teste

Se você tiver as ferramentas de desenvolvimento padrão, terá uma saída como essa:


cc -c natural.c -Wall -ansi -pedantic -o natural.o
cc teste.c natural.o -Wall -ansi -pedantic -o teste
./teste
=> testa_parametros
=> testa_resultado_nao_N
=> testa_resultados_validos
TESTES EXECUTADOS COM SUCESSO
Testes executados: 3

O link para o gist é: https://gist.github.com/paulohrpinheiro/1f45a929aba4eb91f376

Leia o código completo, não fique apenas com o que foi colado aqui. Adicione testes e veja o que acontece. Ler código é muito mais divertido e instrutivo do que ler textos!