Não execute testes de unidade no dispositivo ou emulador do Arduino
O caso contra o microcontrolador Device / Emulator / Sim-based test
Há muita discussão sobre o que significa teste de unidade e não estou tentando argumentar sobre isso aqui. Esta publicação não
está lhe dizendo para evitar todos os testes práticos no seu hardware de destino final. Estou tentando enfatizar a otimização do seu ciclo de feedback de desenvolvimento, eliminando o hardware de destino dos testes mais comuns e mundanos. As unidades em teste são consideradas muito menores que o projeto inteiro.
O objetivo do teste de unidade é testar a qualidade do seu próprio código. Os testes de unidade geralmente nunca devem testar a funcionalidade de fatores fora do seu controle.
Pense da seguinte maneira: mesmo se você testasse a funcionalidade da biblioteca do Arduino, do hardware do microcontrolador ou de um emulador, é absolutamente impossível que esses resultados de teste lhe digam algo sobre a qualidade do seu próprio trabalho. Portanto, é muito mais valioso e eficiente escrever testes de unidade que não são executados no dispositivo de destino (ou emulador).
Os testes frequentes no hardware de destino têm um ciclo dolorosamente lento:
- Ajuste seu código
- Compilar e fazer upload para o dispositivo Arduino
- Observe o comportamento e adivinhe se seu código está fazendo o que você espera
- Repetir
A Etapa 3 é particularmente desagradável se você espera receber mensagens de diagnóstico via porta serial, mas seu projeto precisa usar a única porta serial de hardware do Arduino. Se você estava pensando que a biblioteca SoftwareSerial pode ajudar, você deve saber que isso pode atrapalhar qualquer funcionalidade que exija tempo preciso, como gerar outros sinais ao mesmo tempo. Este problema aconteceu comigo.
Novamente, se você testasse seu esboço usando um emulador e suas rotinas com tempo crítico funcionassem perfeitamente até você fazer o upload para o Arduino real, a única lição que você aprenderá é que o emulador é defeituoso - e sabendo disso ainda não revela nada sobre a qualidade do seu próprio trabalho.
Se for tolo testar no dispositivo ou emulador, o que devo fazer?
Você provavelmente está usando um computador para trabalhar no seu projeto do Arduino. Esse computador é uma ordem de grandeza mais rápida que o microcontrolador. Escreva os testes para criar e executar no seu computador .
Lembre-se de que o comportamento da biblioteca e do microcontrolador do Arduino deve ser considerado correto ou pelo menos consistentemente incorreto .
Quando seus testes produzem resultados contrários às suas expectativas, você provavelmente tem uma falha no seu código que foi testada. Se a saída do seu teste corresponder às suas expectativas, mas o programa não se comportar corretamente quando você o enviar para o Arduino, você saberá que seus testes foram baseados em suposições incorretas e é provável que você tenha um teste defeituoso. Em ambos os casos, você terá informações reais sobre quais devem ser suas próximas alterações de código. A qualidade dos seus comentários foi aprimorada de " algo está quebrado" para "esse código específico está quebrado" .
Como criar e executar testes no seu PC
A primeira coisa que você precisa fazer é identificar suas metas de teste . Pense em quais partes do seu próprio código você deseja testar e, em seguida, certifique-se de construir seu programa de forma que você possa isolar partes discretas para teste.
Se as peças que você deseja testar chamarem alguma função do Arduino, será necessário fornecer substituições de maquete em seu programa de teste. Isso é muito menos trabalho do que parece. Suas maquetes não precisam fazer nada além de fornecer entradas e saídas previsíveis para seus testes.
Qualquer código seu que você pretenda testar precisa existir em arquivos de origem que não sejam o esboço .pde. Não se preocupe, seu esboço ainda será compilado, mesmo com algum código-fonte fora dele. Quando você realmente faz isso, pouco mais do que o ponto de entrada normal do seu programa deve ser definido no arquivo de esboço.
Tudo o que resta é escrever os testes reais e compilá-lo usando o seu compilador C ++ favorito! Provavelmente, isso é melhor ilustrado com um exemplo do mundo real.
Um exemplo de trabalho real
Um dos meus projetos de estimação encontrados aqui tem alguns testes simples que são executados no PC. Para enviar esta resposta, mostrarei como simulei algumas das funções da biblioteca do Arduino e os testes que escrevi para testar essas maquetes. Isso não é contrário ao que eu disse antes sobre não testar o código de outras pessoas, porque fui eu quem escreveu os modelos. Eu queria ter certeza de que minhas maquetes estavam corretas.
Origem do mock_arduino.cpp, que contém código que duplica algumas funcionalidades de suporte fornecidas pela biblioteca do Arduino:
#include <sys/timeb.h>
#include "mock_arduino.h"
timeb t_start;
unsigned long millis() {
timeb t_now;
ftime(&t_now);
return (t_now.time - t_start.time) * 1000 + (t_now.millitm - t_start.millitm);
}
void delay( unsigned long ms ) {
unsigned long start = millis();
while(millis() - start < ms){}
}
void initialize_mock_arduino() {
ftime(&t_start);
}
Uso o seguinte modelo para produzir uma saída legível quando meu código grava dados binários no dispositivo serial de hardware.
fake_serial.h
#include <iostream>
class FakeSerial {
public:
void begin(unsigned long);
void end();
size_t write(const unsigned char*, size_t);
};
extern FakeSerial Serial;
fake_serial.cpp
#include <cstring>
#include <iostream>
#include <iomanip>
#include "fake_serial.h"
void FakeSerial::begin(unsigned long speed) {
return;
}
void FakeSerial::end() {
return;
}
size_t FakeSerial::write( const unsigned char buf[], size_t size ) {
using namespace std;
ios_base::fmtflags oldFlags = cout.flags();
streamsize oldPrec = cout.precision();
char oldFill = cout.fill();
cout << "Serial::write: ";
cout << internal << setfill('0');
for( unsigned int i = 0; i < size; i++ ){
cout << setw(2) << hex << (unsigned int)buf[i] << " ";
}
cout << endl;
cout.flags(oldFlags);
cout.precision(oldPrec);
cout.fill(oldFill);
return size;
}
FakeSerial Serial;
e, finalmente, o programa de teste real:
#include "mock_arduino.h"
using namespace std;
void millis_test() {
unsigned long start = millis();
cout << "millis() test start: " << start << endl;
while( millis() - start < 10000 ) {
cout << millis() << endl;
sleep(1);
}
unsigned long end = millis();
cout << "End of test - duration: " << end - start << "ms" << endl;
}
void delay_test() {
unsigned long start = millis();
cout << "delay() test start: " << start << endl;
while( millis() - start < 10000 ) {
cout << millis() << endl;
delay(250);
}
unsigned long end = millis();
cout << "End of test - duration: " << end - start << "ms" << endl;
}
void run_tests() {
millis_test();
delay_test();
}
int main(int argc, char **argv){
initialize_mock_arduino();
run_tests();
}
Como este post é longo o suficiente, consulte o meu projeto no GitHub para ver mais casos de teste em ação. Eu mantenho meus trabalhos em andamento em ramos que não sejam mestre, portanto verifique esses ramos para testes extras também.
Eu escolhi escrever minhas próprias rotinas de teste leves, mas estruturas de teste de unidade mais robustas como o CppUnit também estão disponíveis.