1. Introdução
Todo mundo conhece os benefícios do teste de unidade. Antes de tudo, escrever testes ao mesmo tempo que o código permite detectar erros mais cedo e não perder tempo posteriormente em trabalhosa depuração complexa. No caso de desenvolvimento incorporado, o teste de unidade possui recursos relacionados, primeiramente, ao fato de o código ser executado em algum lugar profundo nas entranhas do dispositivo e é muito difícil interagir com ele; em segundo lugar, o código está fortemente ligado ao hardware de destino .
Se existem fragmentos no projeto que não dependem do hardware e, ao mesmo tempo, implementam uma lógica bastante complexa, para eles o uso de testes de unidade trará o maior benefício. Por exemplo, pode ser a implementação de um protocolo de transferência de dados, vários cálculos ou uma máquina de estado de controle.
Existem três maneiras de executar testes de unidade para plataformas incorporadas:
- Inicie diretamente na plataforma de destino. Nesse caso, você pode trabalhar com o equipamento do dispositivo e o código funcionará exatamente da mesma forma que nas condições de combate. No entanto, para testar, você precisará de acesso físico ao dispositivo. Além disso, o ciclo de testes será bastante longo devido à necessidade de baixar constantemente o código para o dispositivo.
- Rodando em um emulador. Esse método é bom principalmente porque permite trabalhar mesmo quando a plataforma de destino não está disponível (por exemplo, porque ainda não foi concluída). As desvantagens são a precisão limitada na reprodução do comportamento do ferro (e o mundo circundante), bem como a dificuldade de criar esse emulador.
- Executando na máquina host (localmente). Ele não funcionará com o equipamento (você pode usar stubs de teste), mas os testes serão iniciados e funcionarão rapidamente e você não precisará de acesso ao dispositivo de destino. Um bom exemplo para usar esse método é testar a implementação de um algoritmo computacional no microcontrolador, que por si só não depende do hardware, mas usa os dados do sensor do dispositivo. Testar um algoritmo com uma fonte de dados real será muito inconveniente, é muito melhor registrar essas medições uma vez e executar testes já nos dados armazenados. Este script executará testes localmente e será discutido posteriormente.
Esta publicação fornece uma maneira de configurar testes de unidade no ambiente STM32CubeIDE, baseado no Eclipse e destinado ao desenvolvimento de controladores da família STM32. A linguagem de desenvolvimento é C, mas os testes em si são escritos em C ++. Os testes serão executados em uma máquina host Windows usando Cygwin. Como estrutura de teste, o Google Test é usado. Os resultados serão exibidos em uma janela de plug-in especial para teste de unidade e podem ser iniciados com um botão do projeto para STM32:

O método descrito é adequado para outros ambientes de desenvolvimento baseados no Eclipse, a menos que os bons fabricantes os tenham cortado demais por conveniência dos desenvolvedores. Este método também funcionará com o CubeIDE no Linux, sem a necessidade de se preocupar com o Cygwin.
Você vai precisar
- Cygwin 3.0.7 x86 (como os testes são para um microcontrolador de 32 bits, também usaremos um ambiente de 32 bits em uma plataforma de 64 bits)
- STM32CubeIDE 1.0.2 para Windows.
- Estrutura de teste do Google 1.8.1
Instale o Cygwin e o STM32CubeIDE
Cygwin
Instale o Cygwin, versão x86. No instalador, selecione pacotes adicionais: gcc-core, g ++, binutils, automake, autoconf, cmake, libtool, gdb, make. Você pode instalar as últimas versões estáveis dos pacotes.

Você também precisa registrar variáveis de ambiente:
CAMINHO: ...; C: \ <caminho_do_Cygwin> \ Cygwin \ bin; C: \ <caminho_do_Cygwin> \ Cygwin \ lib
caminho de classe: C: \ <path_to_Cygwin> \ Cygwin \ lib
STM32CubeIDE
O ambiente é instalado como de costume. É aconselhável instalar o CubeIDE após o Cygwin, pois nesse caso o Cube pegará a cadeia de ferramentas Cygwin existente.
Primeiro, crie um projeto C ++ para a plataforma Cy86 x86. Precisamos disso para, em primeiro lugar, verificar a funcionalidade do conjunto de ferramentas e, em segundo lugar, usá-lo como um "doador" da configuração da montagem para o projeto principal.
Escolha Arquivo> Novo> Projeto C / C ++. Selecione C ++ Managed Build. Criamos um projeto do tipo hello world para a cadeia de ferramentas Cygwin GCC:

Em seguida, você precisará escolher quais configurações de montagem criar. Apenas Debug é suficiente.
Agora você pode verificar se o projeto está em andamento, escolhendo Projeto> Construir Tudo. Também é aconselhável verificar a depuração no Cygwin executando Executar> Depurar como> Aplicativo C / C ++ local. O aplicativo emitirá "Hello world" para o console dentro do CubeIDE.
Para que o depurador mostre linhas executáveis nos arquivos de código-fonte, você precisa configurar a exibição dos caminhos. Na janela Janela> Preferências, na guia C / C ++> Depuração, selecione Caminho de pesquisa de origem e adicione uma nova exibição: Adicionar> Mapeamento de caminho. Na janela, você precisa nomear algo como uma nova exibição e adicionar linhas aos discos que estão no sistema:
- \ cygdrive \ c - C: \
- \ cygdrive \ g - G: \


Para uma bela execução de teste, também precisamos de um plug-in para Eclipse com suporte para testes de unidade para C ++. Ele é instalado diretamente no STM32CubeIDE: menu Ajuda> Instalar Novo Software, selecione o Repositório Eclipse e instale o plug-in C / C ++ Unit Testing Support.

Crie a biblioteca de testes do Google
O código-fonte da biblioteca pode ser obtido em: https://github.com/google/googletest/tree/release-1.8.1
Descompacte as fontes, acesse o diretório googletest-release-1.8.1 usando o terminal Cygwin e execute:
cmake . make
Após a montagem bem-sucedida, o arquivo estático da biblioteca estará em ./googlemock/lib/libgtest.a, e os arquivos de cabeçalho estarão no diretório ./googletest/include/gtest/. Eles precisarão ser copiados para o nosso projeto (ou escrever o caminho para esses arquivos nas configurações do projeto).
Criando um projeto para STM32
Projeto para a placa de depuração STM32L476G-DISCO. O exemplo não será muito sofisticado - há dois LEDs na placa, deixe-os mostrar um contador binário de 00 a 11. Implementaremos um módulo separado para o contador, descrito em um par de arquivos .he .c, e escreveremos um teste para ele.
O projeto pode ser criado como de costume, usando o configurador Cube, o principal é garantir que os pinos PB2 e PE8 estejam configurados como saídas digitais. Ao criar um projeto, seria melhor especificar o tipo - C ++, isso será necessário para compilar os testes (o código principal ainda será compilado pelo compilador C). A conversão de um projeto de C será possível posteriormente, clicando no nome do projeto RMB e selecionando "Converter em C ++".
Para compilação no MK e para testes, precisamos de duas configurações de montagem diferentes. Nessas configurações, diferentes conjuntos de arquivos serão coletados - os principais receberão os módulos para trabalhar com o hardware e os módulos testados, e o teste obterá os mesmos módulos e arquivos de teste testados. Portanto, criaremos diretórios diferentes na raiz do projeto - Aplicativo com o código do aplicativo para MK (você pode simplesmente renomear o diretório Src que o Cube criou), Comum para módulos que não dependem do ferro (que iremos testar) e Testes para testes. Os diretórios podem ser excluídos da montagem clicando em RMB em seu nome, menu Configuração de Recursos> Excluir da Construção.
Adicione nosso módulo de contador ao diretório comum:
Código Led_counter(led_counter.h):
#ifndef LED_COUNTER_H_ #define LED_COUNTER_H_ #include <stdint.h> void Led_Counter_Init(); uint8_t Led_Counter_Get_Next(); #endif /* LED_COUNTER_H_ */
led_counter.cpp:
#include "led_counter.h" static uint8_t led_cnt_state = 0; void Led_Counter_Init() { led_cnt_state = 0; } uint8_t Led_Counter_Get_Next() { if(++led_cnt_state > 3) led_cnt_state = 0; return led_cnt_state; }
Os diretórios Common e Tests devem ser adicionados ao caminho de pesquisa para incluir arquivos: propriedades do projeto (Propriedades)> C / C ++ Geral> Caminhos e Símbolos> Inclui.
Adicione ao trabalho com os principais LEDs
Fragmento main.cmain.c:
… #include "led_counter.h" … int main(void) { … Led_Counter_Init(); uint8_t led_state = 0; while (1) { led_state = Led_Counter_Get_Next(); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, led_state & (1<<0)); HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, led_state & (1<<1)); HAL_Delay(500); } … }
O projeto deve compilar e executar, e os LEDs devem piscar.
Testes de escrita
Agora aquilo para o qual tudo foi iniciado.
Crie uma nova configuração de construção através das propriedades do projeto - Propriedades> Construção do C / C ++> Configurações> Gerenciar configurações. O CubeIDE não permitirá que você crie uma configuração para compilar no Cygwin; portanto, copie-a do projeto que criamos anteriormente:

Agora você precisa mudar para essa configuração e configurar os caminhos para os arquivos de origem e de cabeçalho. Nas propriedades do projeto na guia Caminhos e símbolos que prescrevemos (ao adicionar uma entrada, é melhor colocar um daw no campo "adicionar a todos os idiomas"):
- Inclui - testes / Inc, comum / Inc
- Bibliotecas - gtest
- Caminhos da Biblioteca - Testes / Lib
- Local de Origem - / <nome_prj> / Comum e / <nome_prj>> / Tests (substitua <nome_prj> pelo nome do projeto)
Em seguida, copie a biblioteca gtest - o arquivo .a para o diretório Tests / Lib no projeto e os arquivos de cabeçalho na pasta gtest - para a pasta Tests / Inc. Na pasta Testes, crie um novo arquivo main.cpp no qual os testes serão executados. Seu conteúdo é padrão:
main.cpp:
#include "gtest/gtest.h" int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
Além disso, para testar a configuração, criaremos um teste que verificará se o tamanho do ponteiro é de 32 bits em nosso ambiente (queremos garantir que seja o mesmo que no microcontrolador, para isso definimos o Cygwin de 32 bits).
Crie o seguinte arquivo de teste test_platform.cpp:
#include "gtest/gtest.h" TEST(PlatformTest, TestPointerSize) {
Agora, se o projeto for executado como o aplicativo C ++ usual, a saída de depuração conterá uma mensagem do Google Test informando que todos os testes foram aprovados.
A estrutura do projeto deve se parecer com isso:

Agora vamos escrever testes para o nosso módulo de contador de LED. Os arquivos de teste podem estar localizados na pasta Testes:
test_led_counter.cpp #include "gtest/gtest.h" extern "C" { #include "led_counter.h" }
Para que os resultados do teste sejam exibidos em uma janela bonita, você precisa criar uma nova configuração de inicialização no menu Executar> Configurações de Depuração. O plug-in instalado permite criar configurações do tipo C / C ++ Unit. Crie-o, chame Executar testes, selecione a configuração de montagem "Testar" usada e desmarque a caixa de seleção "parar na inicialização em" na guia Depurador. Depois disso, a configuração pode ser iniciada.
Para que uma janela com os resultados apareça, selecione-a em Janela> Mostrar Visualização> Outra> C / C ++> Unidade C / C ++.

Feito! Agora o projeto pode ser compilado e executado sob o alvo MK, como de costume. Quando você precisar executar testes locais, quando executar a configuração Executar Testes, o projeto será reconstruído automaticamente para x86, o ambiente executará os testes e mostrará o resultado.
Literatura
- J. Grenning. Desenvolvimento Orientado a Testes para Embedded C. - trabalho fundamental em testes unitários de sistemas embarcados e na aplicação da metodologia TDD.
- https://uncannier.com/unit-testing-of-embedded-firmware-part-1-software-confucius/ - Código do microcontrolador x86 de teste de unidade no Texas Instruments Code Composer Studio, estrutura CppUTest
- http://blog.atollic.com/why-running-your-embedded-arm-cortex-code-on-a-host-pc-is-a-good-thing - um artigo sobre por que pode ser útil executar código para um microcontrolador em uma plataforma de desktop