Nie uruchamiaj testów jednostkowych na urządzeniu Arduino lub emulatorze
Sprawa przeciwko testom urządzeń / emulatorów / mikrokontrolerów
Dużo się dyskutuje na temat tego, co oznacza test jednostkowy, i tak naprawdę nie próbuję tutaj o tym argumentować. Ten post nie
mówi ci, aby unikać wszelkich praktycznych testów na twoim ostatecznym sprzęcie docelowym. Próbuję zoptymalizować cykl opinii programistów, eliminując docelowy sprzęt z najbardziej przyziemnych i częstych testów. Zakłada się, że testowane urządzenia są znacznie mniejsze niż cały projekt.
Celem testów jednostkowych jest przetestowanie jakości własnego kodu. Testy jednostkowe na ogół nigdy nie powinny sprawdzać funkcjonalności czynników pozostających poza twoją kontrolą.
Pomyśl o tym w ten sposób: Nawet jeśli były do funkcjonalności testowej biblioteki Arduino, sprzętu mikrokontrolerem lub emulatorze, jest to absolutnie niemożliwe, aby takie wyniki testów powiedzieć nic na temat jakości swojej pracy. Dlatego o wiele bardziej wartościowe i wydajne jest pisanie testów jednostkowych, które nie działają na urządzeniu docelowym (lub emulatorze).
Częste testowanie na docelowym sprzęcie ma boleśnie powolny cykl:
- Popraw swój kod
- Skompiluj i prześlij na urządzenie Arduino
- Obserwuj zachowanie i zgadnij, czy Twój kod działa zgodnie z oczekiwaniami
- Powtarzać
Krok 3 jest szczególnie paskudny, jeśli spodziewasz się otrzymywać komunikaty diagnostyczne przez port szeregowy, ale sam projekt musi korzystać z jedynego sprzętowego portu szeregowego w Arduino. Jeśli myślisz, że biblioteka SoftwareSerial może pomóc, powinieneś wiedzieć, że może to zakłócić funkcjonalność wymagającą dokładnego pomiaru czasu, np. Generowanie innych sygnałów w tym samym czasie. Ten problem mi się przytrafił.
Ponownie, jeśli miałbyś przetestować swój szkic za pomocą emulatora, a Twoje krytyczne pod względem czasu procedury działały idealnie, dopóki nie załadowałeś do rzeczywistego Arduino, jedyną lekcją, której się nauczysz, jest to, że emulator jest wadliwy - i wiedząc o tym nadal nie zdradza nic na temat jakości własnej pracy.
Jeśli głupio jest testować na urządzeniu lub emulatorze, co powinienem zrobić?
Prawdopodobnie używasz komputera do pracy nad projektem Arduino. Ten komputer jest o rząd wielkości szybszy niż mikrokontroler. Napisz testy, aby zbudować i uruchomić na swoim komputerze .
Pamiętaj, że zachowanie biblioteki Arduino i mikrokontrolera powinno być poprawne lub przynajmniej konsekwentnie niepoprawne .
Gdy testy generują dane wyjściowe niezgodnie z oczekiwaniami, prawdopodobnie masz wadę w testowanym kodzie. Jeśli wyniki testu są zgodne z oczekiwaniami, ale program nie działa poprawnie po przesłaniu go do Arduino, to wiesz, że twoje testy były oparte na niepoprawnych założeniach i prawdopodobnie masz wadliwy test. W obu przypadkach uzyskasz prawdziwy wgląd w to, jakie powinny być Twoje kolejne zmiany w kodzie. Poprawiono jakość opinii z „ coś jest zepsute” do „ten konkretny kod jest zepsuty” .
Jak zbudować i uruchomić testy na komputerze
Pierwszą rzeczą, którą musisz zrobić, to określić swoje cele testowe . Zastanów się, które części własnego kodu chcesz przetestować, a następnie upewnij się, że skonstruowałeś swój program w taki sposób, abyś mógł wyizolować dyskretne części do testowania.
Jeśli części, które chcesz przetestować, wywołują dowolne funkcje Arduino, musisz podać zastępcze zamienniki w swoim programie testowym. To znacznie mniej pracy, niż się wydaje. Twoje makiety nie muszą właściwie nic robić, ale zapewniają przewidywalne dane wejściowe i wyjściowe dla twoich testów.
Każdy własny kod, który zamierzasz przetestować, musi istnieć w plikach źródłowych innych niż szkic .pde. Nie martw się, szkic nadal będzie się kompilował, nawet z pewnym kodem źródłowym poza szkicem. Kiedy naprawdę do tego dojdziesz, w pliku szkicu powinno być zdefiniowane niewiele więcej niż normalny punkt wejścia programu.
Pozostaje tylko napisać aktualne testy, a następnie skompilować je przy użyciu ulubionego kompilatora C ++! Prawdopodobnie najlepiej to ilustruje przykład z prawdziwego świata.
Rzeczywisty przykład działania
Jeden z moich znalezionych tutaj projektów dla zwierząt domowych zawiera kilka prostych testów, które można uruchomić na komputerze. Aby odpowiedzieć na to pytanie, omówię sposób, w jaki wyszydziłem niektóre funkcje biblioteki Arduino i testy, które napisałem, aby przetestować te makiety. Nie jest to sprzeczne z tym, co powiedziałem wcześniej o nie testowaniu kodu innych osób, ponieważ to ja napisałem makiety. Chciałem mieć pewność, że moje makiety były prawidłowe.
Źródło mock_arduino.cpp, które zawiera kod powielający niektóre funkcje obsługi zapewniane przez bibliotekę 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);
}
Korzystam z następującej makiety, aby uzyskać czytelny wynik, gdy mój kod zapisuje dane binarne na sprzętowym urządzeniu szeregowym.
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;
i wreszcie aktualny program testowy:
#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();
}
Ten post jest wystarczająco długi, więc zapoznaj się z moim projektem na GitHub, aby zobaczyć więcej przypadków testowych w akcji. Nadal pracuję w toku w gałęziach innych niż master, więc sprawdź te gałęzie również pod kątem dodatkowych testów.
Zdecydowałem się napisać własne lekkie procedury testowe, ale dostępne są również bardziej niezawodne frameworki testów jednostkowych, takie jak CppUnit.