N'exécutez pas de tests unitaires sur le périphérique ou l'émulateur Arduino
Les arguments contre les tests basés sur un périphérique / émulateur / simulateur de microcontrôleur
Il y a beaucoup de discussions sur ce que signifie le test unitaire et je n'essaie pas vraiment d'argumenter à ce sujet ici. Cet article ne
vous dit pas d'éviter tous les tests pratiques sur votre matériel cible ultime. J'essaie de faire un point sur l'optimisation de votre cycle de rétroaction de développement en éliminant votre matériel cible de vos tests les plus banals et les plus fréquents. Les unités testées sont supposées être beaucoup plus petites que l'ensemble du projet.
Le but des tests unitaires est de tester la qualité de votre propre code. Les tests unitaires ne doivent généralement jamais tester la fonctionnalité de facteurs indépendants de votre volonté.
Pensez-y de cette façon: même si vous deviez tester les fonctionnalités de la bibliothèque Arduino, du matériel du microcontrôleur ou d'un émulateur, il est absolument impossible que de tels résultats de test vous disent quoi que ce soit sur la qualité de votre propre travail. Par conséquent, il est beaucoup plus précieux et efficace d'écrire des tests unitaires qui ne s'exécutent pas sur le périphérique cible (ou l'émulateur).
Les tests fréquents sur votre matériel cible ont un cycle extrêmement lent:
- Ajustez votre code
- Compiler et télécharger sur un appareil Arduino
- Observez le comportement et devinez si votre code fait ce que vous attendez
- Répéter
L'étape 3 est particulièrement désagréable si vous vous attendez à recevoir des messages de diagnostic via le port série mais que votre projet lui-même doit utiliser le seul port série matériel de votre Arduino. Si vous pensiez que la bibliothèque SoftwareSerial pourrait vous aider, sachez que cela risquerait de perturber toute fonctionnalité nécessitant une synchronisation précise, comme la génération d'autres signaux en même temps. Ce problème m'est arrivé.
Encore une fois, si vous deviez tester votre esquisse à l'aide d'un émulateur et que vos routines critiques en temps fonctionnaient parfaitement jusqu'à ce que vous le téléchargiez sur l'Arduino réel, la seule leçon que vous allez apprendre est que l'émulateur est défectueux - et le sachant toujours. ne révèle rien sur la qualité de votre propre travail.
S'il est ridicule de tester sur l'appareil ou l'émulateur, que dois- je faire?
Vous utilisez probablement un ordinateur pour travailler sur votre projet Arduino. Cet ordinateur est des ordres de grandeur plus rapide que le microcontrôleur. Écrivez les tests à construire et à exécuter sur votre ordinateur .
N'oubliez pas que le comportement de la bibliothèque Arduino et du microcontrôleur doit être considéré comme correct ou du moins systématiquement incorrect .
Lorsque vos tests produisent une sortie contraire à vos attentes, vous avez probablement une faille dans votre code qui a été testé. Si la sortie de votre test correspond à vos attentes, mais que le programme ne se comporte pas correctement lorsque vous le téléchargez sur l'Arduino, vous savez que vos tests étaient basés sur des hypothèses incorrectes et que vous avez probablement un test défectueux. Dans les deux cas, vous aurez reçu de véritables informations sur ce que devraient être vos prochains changements de code. La qualité de vos commentaires est améliorée de " quelque chose est cassé" à "ce code spécifique est cassé" .
Comment créer et exécuter des tests sur votre PC
La première chose à faire est d' identifier vos objectifs de test . Pensez aux parties de votre propre code que vous souhaitez tester, puis assurez-vous de construire votre programme de manière à pouvoir isoler les parties discrètes pour les tests.
Si les pièces que vous souhaitez tester appellent des fonctions Arduino, vous devrez fournir des remplacements de maquette dans votre programme de test. C'est beaucoup moins de travail qu'il n'y paraît. Vos maquettes n'ont rien d'autre à faire que de fournir des entrées et des sorties prévisibles pour vos tests.
Tout code que vous avez l'intention de tester doit exister dans des fichiers source autres que l'esquisse .pde. Ne vous inquiétez pas, votre esquisse sera toujours compilée même avec du code source en dehors de l'esquisse. Lorsque vous y arrivez vraiment, un peu plus que le point d'entrée normal de votre programme devrait être défini dans le fichier d'esquisse.
Il ne reste plus qu'à écrire les tests réels, puis à les compiler à l'aide de votre compilateur C ++ préféré! Ceci est probablement mieux illustré par un exemple du monde réel.
Un exemple de travail réel
L'un de mes projets favoris trouvés ici a quelques tests simples qui s'exécutent sur le PC. Pour cette soumission de réponse, je vais juste passer en revue la façon dont j'ai simulé certaines des fonctions de la bibliothèque Arduino et les tests que j'ai écrits pour tester ces maquettes. Ce n'est pas contraire à ce que j'ai dit précédemment sur le fait de ne pas tester le code des autres parce que c'est moi qui ai écrit les maquettes. Je voulais être très certain que mes maquettes étaient correctes.
Source de mock_arduino.cpp, qui contient du code qui duplique certaines fonctionnalités de support fournies par la bibliothèque 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);
}
J'utilise la maquette suivante pour produire une sortie lisible lorsque mon code écrit des données binaires sur le périphérique série matériel.
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;
et enfin, le programme de test proprement dit:
#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();
}
Cet article est assez long, veuillez donc vous référer à mon projet sur GitHub pour voir d'autres cas de test en action. Je garde mes travaux en cours dans des branches autres que master, donc vérifiez également ces branches pour des tests supplémentaires.
J'ai choisi d'écrire mes propres routines de test légères, mais des frameworks de tests unitaires plus robustes comme CppUnit sont également disponibles.