我希望能够单元测试我的Arduino代码。理想情况下,我可以运行任何测试,而无需将代码上传到Arduino。哪些工具或库可以帮助我做到这一点?
目前正在开发的Arduino模拟器可能很有用,但似乎还没有准备好使用。
Atmel的AVR Studio包含一个芯片模拟器,可能很有用,但我不知道如何将它与Arduino IDE结合使用。
我希望能够单元测试我的Arduino代码。理想情况下,我可以运行任何测试,而无需将代码上传到Arduino。哪些工具或库可以帮助我做到这一点?
目前正在开发的Arduino模拟器可能很有用,但似乎还没有准备好使用。
Atmel的AVR Studio包含一个芯片模拟器,可能很有用,但我不知道如何将它与Arduino IDE结合使用。
当前回答
基本Arduino是用C和c++编写的,甚至Arduino的库也是用C和c++编写的。因此,简单地说,只需将代码处理为C和c++,并尝试进行单元测试。这里,通过“句柄”这个词,我的意思是你改变所有的基本语法,如串行。Println到sysout, pinmode到变量,void循环到while()循环,该循环在keystock或某些迭代后中断。
我知道这是一个漫长的过程,不那么直接。根据我个人的经验,一旦你开始使用它,它就会变得更可靠。
-Nandha_Frost
其他回答
我在编写Arduino代码时使用Searduino。Searduino是Arduino模拟器和开发环境(Makefiles, C代码…),可以使用您最喜欢的编辑器在C/ c++中轻松破解。您可以导入Arduino草图并在模拟器中运行它们。
Searduino 0.8截图:http://searduino.files.wordpress.com/2014/01/jearduino-0-8.png
Searduino 0.9将发布,并在最后的测试完成后立即录制视频....一两天后。
在模拟器上测试并不是真正的测试,但它确实帮助我找到了许多愚蠢的/逻辑错误(忘记执行pinMode(xx, OUTPUT)等)。
顺便说一句:我是Searduino开发人员之一。
在没有Arduino单元测试框架的情况下,我创建了ArduinoUnit。下面是一个简单的Arduino草图,展示了它的使用:
#include <ArduinoUnit.h>
// Create test suite
TestSuite suite;
void setup() {
Serial.begin(9600);
}
// Create a test called 'addition' in the test suite
test(addition) {
assertEquals(3, 1 + 2);
}
void loop() {
// Run test suite, printing results to the serial port
suite.run();
}
如果您有兴趣运行INO草图并检查串行输出,我在我的Arduino NMEA校验和项目中有一个工作实现。
下面的脚本获取该文件,并使用Arduino CLI将其编译为HEX文件,然后加载到SimAVR中,SimAVR对其进行计算并打印串行输出。由于所有Arduino程序都永远运行,没有真正杀死自己的选项(exit(0)不起作用),我让草图运行几秒钟,然后将捕获的输出与预期的输出进行区分。
下载并提取Arduino CLI(在本例中,版本为0.5.0 -撰写本文时的最新版本):
curl -L https://github.com/arduino/arduino-cli/releases/download/0.5.0/arduino-cli_0.5.0_Linux_64bit.tar.gz -o arduino-cli.tar.gz
tar -xvzf arduino-cli.tar.gz
现在你可以更新索引并安装相应的核心:
./arduino-cli core update-index
./arduino-cli core install arduino:avr
假设你的草图名为nmea-checksum。ino,获取ELF和HEX,运行:
./arduino-cli compile -b arduino:avr:uno nmea-checksum.ino
接下来,SimAVR运行HEX(或ELF) -我从源代码构建,因为最新版本不适合我:
sudo apt-get update
sudo apt-get install -y build-essential libelf-dev avr-libc gcc-avr freeglut3-dev libncurses5-dev pkg-config
git clone https://github.com/buserror/simavr.git
cd simavr
make
成功的编译将为您提供simavr/run_avr,您可以使用它来运行草图。就像我说的,暂停它,否则它永远不会终止:
cd simavr
timeout 10 ./run_avr -m atmega168 -f 16000000 ../../nmea-checksum.ino.arduino.avr.uno.elf &> nmea-checksum.ino.clog || true
生成的文件将有ANSI颜色代码控制字符包装串行输出,以摆脱这些:
cat nmea-checksum.ino.clog | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" > nmea-checksum.ino.log
cat nmea-checksum.ino.log
现在你所需要做的就是将这个文件与一个已知的好文件进行比较:
diff nmea-checksum.ino.log ../../nmea-checksum.ino.test
如果没有差异,diff将以代码0退出,否则脚本将失败。
不要在Arduino设备或模拟器上运行单元测试
案例针对微控制器设备/模拟器/基于sim的测试
关于单元测试的含义有很多讨论,但我不是 我想在这里讨论一下。这篇文章不是 告诉你不要对你的终极目标进行任何实际测试 硬件。我想说的是关于优化你的 通过消除目标硬件来实现开发反馈周期 你最平常最频繁的测试被测单元是假定的 要比整个项目小得多。
单元测试的目的是测试你自己代码的质量。单元测试通常不应该测试超出您控制范围的因素的功能。
可以这样想:即使你要测试Arduino库、微控制器硬件或模拟器的功能,这些测试结果也绝对不可能告诉你自己工作的质量。因此,编写不在目标设备(或模拟器)上运行的单元测试更有价值,也更有效。
在目标硬件上频繁测试的周期非常缓慢:
调整代码 编译并上传到Arduino设备 观察行为并猜测您的代码是否正在执行您所期望的操作 重复
如果你希望通过串口获得诊断信息,但你的项目本身需要使用Arduino唯一的硬件串口,那么步骤3就特别麻烦了。如果您认为SoftwareSerial库可能会有所帮助,那么您应该知道这样做可能会破坏任何需要精确计时的功能,例如同时生成其他信号。我遇到过这样的问题。
同样,如果你要用模拟器测试你的草图,你的时间紧迫的例程在你上传到实际的Arduino之前运行得很好,那么你唯一要学到的教训就是模拟器有缺陷——即使知道这一点,也不能说明你自己的工作质量。
如果在设备或模拟器上进行测试很愚蠢,我该怎么做?
你可能正在用电脑做你的Arduino项目。那台计算机比微控制器快几个数量级。编写要在计算机上构建和运行的测试。
请记住,应该假设Arduino库和微控制器的行为是正确的或至少始终不正确的。
When your tests produce output contrary to your expectations, then you likely have a flaw in your code that was tested. If your test output matches your expectations, but the program does not behave correctly when you upload it to the Arduino, then you know that your tests were based on incorrect assumptions and you likely have a flawed test. In either case, you will have been given real insights on what your next code changes should be. The quality of your feedback is improved from "something is broken" to "this specific code is broken".
如何在PC上构建和运行测试
您需要做的第一件事是确定您的测试目标。考虑一下您想要测试自己代码的哪些部分,然后确保以这样一种方式构建您的程序,即您可以隔离用于测试的离散部分。
如果要测试的部件调用任何Arduino功能,则需要在测试程序中提供模型替换。这比看上去要简单得多。您的模型实际上不需要做任何事情,只需要为您的测试提供可预测的输入和输出。
您打算测试的任何自己的代码都需要存在于.pde草图之外的源文件中。不要担心,即使使用草图之外的一些源代码,您的草图仍然可以编译。当您真正开始着手时,在草图文件中应该只定义程序的正常入口点。
剩下的就是编写实际的测试,然后使用您最喜欢的c++编译器编译它!这也许可以用一个真实的例子来最好地说明。
一个实际的工作示例
我在这里发现的一个喜欢的项目有一些在PC上运行的简单测试。对于这个答案提交,我将回顾一下我如何模拟Arduino库的一些函数,以及我为测试这些模型而编写的测试。这与我之前所说的不要测试其他人的代码并不矛盾,因为我是编写模型的人。我想非常确定我的模型是正确的。
mock_arduino.cpp的源代码,其中包含复制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);
}
当我的代码将二进制数据写入硬件串行设备时,我使用下面的模拟生成可读的输出。
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;
最后是实际的测试程序:
#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();
}
这篇文章已经足够长了,所以请参考我在GitHub上的项目来查看更多的测试用例。我将正在进行的工作保存在master以外的分支中,因此也要检查这些分支以获得额外的测试。
我选择编写自己的轻量级测试例程,但也有更健壮的单元测试框架,如CppUnit。
基本Arduino是用C和c++编写的,甚至Arduino的库也是用C和c++编写的。因此,简单地说,只需将代码处理为C和c++,并尝试进行单元测试。这里,通过“句柄”这个词,我的意思是你改变所有的基本语法,如串行。Println到sysout, pinmode到变量,void循环到while()循环,该循环在keystock或某些迭代后中断。
我知道这是一个漫长的过程,不那么直接。根据我个人的经验,一旦你开始使用它,它就会变得更可靠。
-Nandha_Frost