Không chạy thử nghiệm đơn vị trên thiết bị Arduino hoặc Trình giả lập
Trường hợp chống lại các thiết bị vi điều khiển / Trình mô phỏng / Kiểm tra dựa trên Sim
Có rất nhiều cuộc thảo luận về ý nghĩa của bài kiểm tra đơn vị và tôi không thực sự cố gắng tranh luận về vấn đề đó ở đây. Bài đăng này không
nói với bạn để tránh tất cả các thử nghiệm thực tế trên phần cứng mục tiêu cuối cùng của bạn. Tôi đang cố gắng đưa ra quan điểm về việc tối ưu hóa chu kỳ phản hồi phát triển của bạn bằng cách loại bỏ phần cứng mục tiêu khỏi các bài kiểm tra thường xuyên và thường xuyên nhất của bạn. Các đơn vị được thử nghiệm được giả định là nhỏ hơn nhiều so với toàn bộ dự án.
Mục đích của kiểm thử đơn vị là kiểm tra chất lượng mã của riêng bạn. Các bài kiểm tra đơn vị thường không bao giờ kiểm tra chức năng của các yếu tố ngoài tầm kiểm soát của bạn.
Hãy suy nghĩ về nó theo cách này: Ngay cả khi bạn đã kiểm tra chức năng của thư viện Arduino, phần cứng vi điều khiển hoặc trình giả lập, thì kết quả kiểm tra như vậy hoàn toàn không thể cho bạn biết bất cứ điều gì về chất lượng công việc của chính bạn. Do đó, việc viết các bài kiểm tra đơn vị không chạy trên thiết bị đích (hoặc trình giả lập) sẽ có giá trị và hiệu quả hơn nhiều.
Kiểm tra thường xuyên trên phần cứng mục tiêu của bạn có chu kỳ rất chậm:
- Tinh chỉnh mã của bạn
- Biên dịch và tải lên thiết bị Arduino
- Quan sát hành vi và đoán xem mã của bạn có đang làm những gì bạn mong đợi không
- Nói lại
Bước 3 đặc biệt khó chịu nếu bạn muốn nhận thông báo chẩn đoán qua cổng nối tiếp nhưng bản thân dự án của bạn cần sử dụng cổng nối tiếp phần cứng duy nhất của Arduino. Nếu bạn đã nghĩ rằng thư viện SoftwareSerial có thể giúp ích, bạn nên biết rằng làm như vậy có khả năng phá vỡ mọi chức năng yêu cầu thời gian chính xác như tạo ra các tín hiệu khác cùng một lúc. Vấn đề này đã xảy ra với tôi.
Một lần nữa, nếu bạn đã kiểm tra bản phác thảo của mình bằng trình giả lập và các thói quen quan trọng về thời gian của bạn chạy hoàn hảo cho đến khi bạn tải lên Arduino thực tế, thì bài học duy nhất bạn sẽ học là trình giả lập bị lỗi - và biết điều này vẫn còn không tiết lộ gì về chất lượng công việc của bạn
Nếu thật ngớ ngẩn khi thử nghiệm trên thiết bị hoặc trình giả lập, tôi nên làm gì?
Bạn có thể đang sử dụng máy tính để làm việc trong dự án Arduino của bạn. Máy tính đó là đơn đặt hàng có cường độ nhanh hơn vi điều khiển. Viết các bài kiểm tra để xây dựng và chạy trên máy tính của bạn .
Hãy nhớ rằng, hành vi của các thư viện Arduino và vi điều khiển nên được giả định là một trong hai đúng hoặc ít nhất là một cách nhất quán không chính xác .
Khi các thử nghiệm của bạn tạo ra đầu ra trái với mong đợi của bạn, thì bạn có thể có một lỗ hổng trong mã đã được thử nghiệm. Nếu đầu ra thử nghiệm của bạn phù hợp với mong đợi của bạn, nhưng chương trình không hoạt động chính xác khi bạn tải nó lên Arduino, thì bạn biết rằng các thử nghiệm của bạn dựa trên các giả định không chính xác và bạn có thể có một thử nghiệm sai sót. Trong cả hai trường hợp, bạn sẽ được cung cấp những hiểu biết thực sự về những thay đổi mã tiếp theo của bạn. Chất lượng phản hồi của bạn được cải thiện từ " một cái gì đó bị hỏng" thành " mã cụ thể này bị hỏng" .
Cách xây dựng và chạy thử nghiệm trên PC của bạn
Điều đầu tiên bạn cần làm là xác định mục tiêu thử nghiệm của bạn . Hãy suy nghĩ về những phần nào trong mã của riêng bạn mà bạn muốn kiểm tra và sau đó đảm bảo xây dựng chương trình của bạn theo cách mà bạn có thể cách ly các phần riêng biệt để kiểm tra.
Nếu các bộ phận mà bạn muốn kiểm tra gọi bất kỳ chức năng Arduino nào, bạn sẽ cần cung cấp các thay thế giả trong chương trình thử nghiệm của mình. Đây là công việc ít hơn nhiều so với nó có vẻ. Mock-up của bạn không thực sự phải làm bất cứ điều gì ngoài việc cung cấp đầu vào và đầu ra có thể dự đoán cho các thử nghiệm của bạn.
Bất kỳ mã nào của riêng bạn mà bạn định kiểm tra đều cần tồn tại trong các tệp nguồn khác với bản phác thảo .pde. Đừng lo lắng, bản phác thảo của bạn vẫn sẽ biên dịch ngay cả với một số mã nguồn bên ngoài bản phác thảo. Khi bạn thực sự hiểu được nó, ít hơn điểm nhập cảnh bình thường của chương trình của bạn sẽ được xác định trong tệp phác thảo.
Tất cả những gì còn lại là viết các bài kiểm tra thực tế và sau đó biên dịch nó bằng trình biên dịch C ++ yêu thích của bạn! Điều này có lẽ được minh họa tốt nhất với một ví dụ thế giới thực.
Một ví dụ làm việc thực tế
Một trong những dự án thú cưng của tôi được tìm thấy ở đây có một số thử nghiệm đơn giản chạy trên PC. Để gửi câu trả lời này, tôi sẽ xem xét cách tôi mô phỏng một số chức năng của thư viện Arduino và các bài kiểm tra tôi đã viết để kiểm tra các mô hình đó. Điều này không trái với những gì tôi đã nói trước đây về việc không kiểm tra mã của người khác bởi vì tôi là người đã viết các bản nhái. Tôi muốn rất chắc chắn rằng mock-up của tôi là chính xác.
Nguồn của mock_arduino.cpp, chứa mã trùng lặp một số chức năng hỗ trợ do thư viện Arduino cung cấp:
#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);
}
Tôi sử dụng mô phỏng sau để tạo đầu ra có thể đọc được khi mã của tôi ghi dữ liệu nhị phân vào thiết bị nối tiếp phần cứng.
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;
và cuối cùng là chương trình thử nghiệm thực tế:
#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();
}
Bài đăng này đủ dài, vì vậy vui lòng tham khảo dự án của tôi trên GitHub để xem thêm một số trường hợp thử nghiệm đang hoạt động. Tôi giữ các công việc đang tiến hành của mình trong các ngành khác ngoài chủ, vì vậy cũng kiểm tra các nhánh đó để kiểm tra thêm.
Tôi đã chọn để viết các thói quen kiểm tra nhẹ của riêng mình, nhưng các khung kiểm tra đơn vị mạnh mẽ hơn như CppUnit cũng có sẵn.