TDD vs. Testes de Unidade

Qual a diferença entre TDD e Testes de Unidade?

Qual a diferença entre criar testes de unidade e desenvolver usando TDD (Desenvolvimento Baseado em Testes)?

É isso que veremos neste post. Veja nesta página os posts da série sobre TDD.

Continue lendo “TDD vs. Testes de Unidade”

As Três Leis do TDD

O que é o Desenvolvimento Baseado em Testes ou TDD?

Quais são as Três Leis do TDD?

É isso o que veremos neste post. Veja nesta página os posts da série sobre TDD.

Continue lendo “As Três Leis do TDD”

Minhas Preferências de Formatação

Abaixo estão algumas das minhas preferências e suas razões.

1. Endentação e aspecto geral

  • Endentar com 4 espaços;
  • Limite de 80 caracteres por linha; e
  • Tabulações proibidas.

Para mim 2 espaços é pouco e 8 espaços é muito na endentação. Quatro espaços parecem ser o meio termo adequado.

Linhas longas atrapalham a leitura do código: eu desejo um código tão legível quanto livros e jornais. Além disso, o limite de 80 caracteres permite abrir arquivos lado a lado sem necessitar de rolagem horizontal e ainda facilita revisão do código anterior e atual abertos lado a lado.

Com respeito a proibir tabulações, existe o argumento que tabulações são mais flexíveis, pois o programador pode escolher o tamanho de sua preferência. O argumento é válido, porém somente até se misturarem as tabulações e os espaços: nesse instante, a única pessoa que vê alinhado é o próprio autor do código; e todo o resto do mundo vê uma bagunça desalinhada. Usar apenas um tipo de espaço em branco elimina esse problema.

2. Alinhamento das chaves

Abertura de chaves na linha abaixo.

Isso ajuda a identificar onde acontece a abertura do bloco. Chave abrindo na própria linha permite determinar o início do bloco sem precisar procurar por ela no fim de alguma linha.

unsigned long int funcao_h(
    unsigned long int parametro1,
    unsigned long int parametro2,
    unsigned long int parametro3)
{
    return parametro1 + parametro2 - parametro3;
}

Compare abaixo a mesma função abrindo as chaves na linha. Não é tão óbvio como no caso acima.

unsigned long int funcao_h(
    unsigned long int parametro1,
    unsigned long int parametro2,
    unsigned long int parametro3) {
    return parametro1 + parametro2 - parametro3;
}

Abrindo as chaves na linha abaixo, fica óbvio onde os parâmetros terminam e onde inicia o corpo da função. O mesmo acontece em blocos de controle como if, for, etc.

Mesmo se eliminarmos qualquer informação sobre os caracteres, como é mostrado abaixo, facilmente identificamos a lista de parâmetros e o início do corpo da função.

XXXXXXXX XXXX XXX XXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX
X
    XXXXXX XXXXXXXXXX X XXXXXXXXXX X XXXXXXXXXXX
X

XXXXXXXX XXXX XXX XXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX
    XXXXXXXX XXXX XXX XXXXXXXXXXX X
    XXXXXX XXXXXXXXXX X XXXXXXXXXX X XXXXXXXXXXX
X

A maior parte do tempo que passamos programando é utilizada, na verdade, lendo o código. Esta pequena escolha ajuda a encontrar a parte que importa no momento.

3. Parâmetros de funções

A lista de parâmetros de funções e a lista de argumentos de chamada de uma função devem estar todos em uma linha ou cada um em sua linha.

Dessa forma, a lista de parâmetros (ou argumentos) realmente se parece com uma lista. É fácil de contar os parâmetros e é fácil de encontrá-los.

Nesse ponto, há três possibilidades avaliadas na ordem seguinte:

  • Protótipo todo na mesma linha;
  • Lista de parâmetros toda na segunda linha; e
  • Lista de parâmetros com um parâmetro por linha.

Exemplos abaixo:

/* Protótipo inteiro cabe em uma linha: */

int funcao_f(int parametro1, int parametro2)
{
    return parametro1 + parametro2;
}

/*
 * Protótipo não cabe em uma única linha, porém
 * a lista de parâmetros cabe inteira na segunda linha:
 */

unsigned long int funcao_g(
    unsigned long int parametro1, unsigned long int parametro2)
{
    return parametro1 + parametro2;
}

/*
 * Parâmetros não cabem todos na segunda linha, então
 * coloca-se um parâmetro por linha:
 */

unsigned long int funcao_h(
    unsigned long int parametro1,
    unsigned long int parametro2,
    unsigned long int parametro3)
{
    return parametro1 + parametro2 - parametro3;
}

4. Comentários

  • Usar o mínimo de comentários possível; e
  • Comentários multilinha devem ter um caractere inicial de alinhamento.

Comentários não são analisados pelo compilador e não precisam fazer sentido para o código funcionar. Por isso não são atualizados em conjunto com o código. Consequentemente, com o passar do tempo a semântica (o significado) do código se desvia dos comentários presentes.

Na maioria dos casos é possível fazer um código autoexplicativo através da refatoração e da melhoria de nomes de funções e nomes de variáveis.

/* executa_loop1 */

void executa_loop1(void)
{
    // Lê o valor do sinal do sensor
    int16_t val = ADC0;

    // Calcula tensão do sensor em volts
    // a partir do offset e do ganho
    float valf = (val - 512) * (24.0 / 512.0);

    // Escreve na serial
    Serial_EscreveFloat(valf);
}

/* ---------------------------------------- */

/* executa_loop2 */

#define SENSOR_OFFSET 512
#define SENSOR_GANHO  (24.0 / 512.0)

float TensaoSensor_Volts(void);

void executa_loop2(void)
{
    Serial_EscreveFloat(TensaoSensor_Volts());
}

int16_t SinalSensor_ADC(void)
{
    return ADC0;
}

float TensaoSensor_Volts(void)
{
    return (SinalSensor_ADC() - SENSOR_OFFSET) * SENSOR_GANHO;
}
  • O primeiro comentário é removido com uma chamada da função SinalSensor_ADC().
  • O segundo comentário é removido com uma chamada da função TensaoSensor_Volts() e pelo uso das constantes SENSOR_OFFSET e SENSOR_GANHO.
  • O terceiro comentário é redundante e desnecessário.

Qual versão da função executa_loopX() você prefere ler?

Alguns comentários, por sua vez, são impossíveis de serem transformados em código. Por exemplo, o comentário a seguir mostra um diagrama de um LCD 16×2 que mostra a tensão elétrica e a corrente medida.

/*
 *   0123456789012345
 * 0 Tensão   XX,XX V 0
 * 1 Corrente Y,YYY A 1
 *   0123456789012345
 */

Sobre o caractere inicial dos comentários multilinha, alguns editores e formatadores eliminam espaços em branco iniciais, alinhando o primeiro caractere não branco de cada linha.

Dessa forma, todo o esforço de alinhamento abaixo é perdido em um piscar de olhos:

/*  0123456789012345
  0 Display 16x2     0
  1 Linha 2          1
    0123456789012345 */

O comentário acima acaba por se transformar em alguma dessas atrocidades:

/* 0123456789012345
   0 Display 16x2     0
   1 Linha 2          1
   0123456789012345 */

/* 0123456789012345
0 Display 16x2     0
1 Linha 2          1
0123456789012345 */

Colocando os asteriscos de alinhamento o problema deixa de existir.

/*
 *   0123456789012345
 * 0 Display 16x2     0
 * 1 Linha 2          1
 *   0123456789012345
 */

Considerações finais

Essas são as algumas das minhas preferências, ao menos as que eu acho mais importantes.

Todas essas já mudaram algumas vezes desde que comecei a programar e, com a experiência, são as que me parecem mais naturais e que facilitam a leitura do código.

Além disso, a maioria delas (exceto as sobre comentários), não me dão trabalho algum, pois utilizo um formatador automático. Assim posso focar no código em si e em como melhorá-lo para eliminar comentários.

Formatando o Código

Quando desenvolvido em grupo ou time, a formatação do código de um projeto é um assunto que cria polêmicas com frequência. Cada pessoa tem suas preferências e isso gera conflitos durante as revisões.

Para minimizar esse problema há três pontos que podem ser adotados pelo projeto:

  • Deve haver algum padrão de formatação para o projeto;
  • Dar preferência para formatação automatizada; e
  • Documentar como configurar a formatação automatizada.

Tenha algum padrão de formatação

Para o projeto em si, não importa qual é o padrão estético do código, desde que o código funcione. Os programadores, no entanto, possuem preferências com respeito à estética do código.

Esse primeiro ponto reforça que algum padrão exista. Não importa qual seja o padrão, desde que ele exista e seja seguido. Dessa forma, essas discussões nas revisões deixam de focar em critérios pessoais e passam a usar um critério objetivo: estar ou não de acordo com o padrão.

Com o padrão definido, as discussões sobre preferência pessoal são minimizadas e, caso haja algum ponto adicional que o padrão não abrange, faz-se uma reunião para definir o padrão para tal caso.

Automatize a formatação

O segundo ponto, por sua vez, elimina o trabalho manual de formatar e consequentemente elimina apontamentos de erro nas revisões por conta de erros de formatação.

Também são eliminadas algumas reuniões sobre preferências pessoais não abrangidas pelo padrão, porque as decisões precisam ser mantidas dentro das opções fornecidas pelo formatador automático.

Além disso, são menos regras para se lembrar na hora de escrever código.

Só há uma regra: execute o formatador nos arquivos modificados.

Existe um projeto chamado pre-commit que realiza tarefas logo antes de realizar um commit com o git. O tipo de tarefa mais comum é formatar o código com alguma ferramenta externa, como o clang-format (para C/C++).

Ainda melhor: alguns editores podem ser configurados para formatar o código sempre que o arquivo é salvo.

Documente o processo de automação

É importante documentar como abaixar, instalar e configurar essas ferramentas, de forma que todos possam aderir sem dificuldades.

Crie um link com a informação no LEIAME (README) do próprio projeto ou adicione a informação diretamente nele.

Essa documentação só precisa ser feita uma vez e outros projetos podem simplesmente copiar e adaptar para as suas necessidades.

Links úteis

Considerações finais

Definir um padrão para o código é importante para evitar discussões sem fim sobre a estética do código.

Ferramentas de formatação automática são a melhor opção para eliminar essas discussões e manter o código formatado consistentemente.

Documentar o processo de configuração das ferramentas é importante para os desenvolvedores fazerem uso delas.

Rodando Testes de Unidade com Wine no Linux

Muitas vezes desejamos compilar e executar testes de unidade para a plataforma Windows. No entanto, reiniciar sua máquina e dar boot no Windows só para testar fica meio na contramão.

Uma alternativa é instalar o MinGW e o Wine, para podermos compilar e para executar os testes de unidade plataforma Windows, sem sair do conforto do Linux.

Para instalar o MinGW e o Wine:

# MinGW - Windows 32-bit:
sudo apt install gcc-mingw-w64-i686 g++-mingw-w64-i686

# MinGW - Windows 64-bit:
sudo apt install gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64

# Wine
sudo apt install wine

Para compilar para a plataforma Windows usamos os compiladores MinGW para C ou C++, para 32-bit ou 64-bit.

CompiladorWindows 32-bitWindows 64-bit
C (GCC)i686-w64-mingw32-gccx86_64-w64-mingw32-gcc
C++ (G++)i686-w64-mingw32-g++x86_64-w64-mingw32-g++
Compiladores MinGW para C/C++ em 32-bit ou 64-bit.

Por exemplo, para compilar os arquivos test_unit.c e unit.c e para Windows 32-bit usamos:

i686-w64-mingw32-gcc test_unit.c unit.c -o test.exe

Então, para rodar o executável no próprio Linux, através o Wine, usamos:

wine test.exe

Conclusão

Podemos executar testes de unidade da plataforma Windows (32-bit ou 64-bit), sem sair do Linux, usando o Wine.

Para rodar um executável qualquer com o Wine, no entanto, pode ser necessário fornecer DLLs (bibliotecas). Porém, a princípio, um teste de unidade não deve depender de nada externo, pois todas as dependências devem ser fakes (falsos) ou mocks (imitadores) e, portanto, não deveria cair neste problema.

Se o seu teste PRECISA de alguma DLL, ele provavelmente ele se encaixa melhor no grupo de testes de integração, não de testes de unidade.

%d blogueiros gostam disto: