我希望能够对我的Arduino代码进行单元测试。理想情况下,我可以运行任何测试而无需将代码上传到Arduino。哪些工具或库可以帮助我?
有一个Arduino emulator in development可能有用,但它似乎还没有准备好使用。
来自Atmel的AVR Studio包含一个可能有用的芯片模拟器,但我看不出如何将它与Arduino IDE结合使用。
答案 0 :(得分:113)
关于单元测试的含义有很多讨论,我不是 真的想在这里争论一下。这篇文章是不是 告诉你要避免对你的最终目标进行所有实际测试 硬件。我试图说明如何优化你的 通过消除目标硬件来开发反馈周期 你最平凡和频繁的测试。假设被测单位 比整个项目小得多。
单元测试的目的是测试您自己代码的质量。单元测试通常不应测试您控制之外的因素的功能。
以这种方式思考:即使您要测试Arduino库,微控制器硬件或仿真器的功能,绝对不可能这样的测试结果告诉您有关你自己的工作质量。因此,编写不在目标设备(或仿真器)上运行的单元测试更有价值和高效。
对目标硬件进行频繁测试的周期非常缓慢:
如果您希望通过串口获取诊断消息,但步骤3特别令人讨厌,但您的项目本身需要使用您的Arduino唯一的硬件串口。如果您认为SoftwareSerial库可能有所帮助,您应该知道这样做可能会破坏任何需要精确计时的功能,例如同时生成其他信号。这个问题发生在我身上。
同样,如果您要使用模拟器测试草图,并且您的时间关键例程运行良好,直到您上传到实际的Arduino,那么您将要学习的唯一教训是模拟器存在缺陷 - - 并且知道这仍然会发现没有关于你自己的工作的质量。
您可能正在使用计算机来处理您的Arduino项目。该计算机的速度比微控制器快。编写测试以构建并在您的计算机上运行。
请记住,Arduino库和微控制器的行为应该假设是正确的,或者至少始终不正确。
如果您的测试产生的输出与您的预期相反,那么您的代码中可能存在测试缺陷。如果您的测试输出符合您的期望,但是当您将程序上传到Arduino时程序运行不正常,那么您就知道您的测试基于错误的假设并且您可能存在缺陷测试。在任何一种情况下,您都将获得有关下一个代码更改应该是什么的真实见解。您的反馈质量会从" 某些被破坏后得到改善" "此特定代码已损坏" 。
您需要做的第一件事是确定您的测试目标。考虑一下您想要测试的您自己的代码的哪些部分,然后确保以隔离离散部分进行测试的方式构建您的程序。
如果要测试的部件调用任何Arduino函数,则需要在测试程序中提供模拟替换。这比看起来要少得多。您的模型除了为您的测试提供可预测的输入和输出外,不必实际做任何事情。
您打算测试的任何自己的代码都需要存在于.pde草图以外的源文件中。不用担心,即使在草图之外的某些源代码,您的草图仍然会编译。当你真正了解它时,只需要在草图文件中定义程序的正常入口点。
剩下的就是编写实际的测试,然后使用您最喜欢的C ++编译器进行编译!这可能是一个真实世界的例子。
我发现的一个宠物项目here有一些在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();
}
这篇文章足够长,所以请参考my project on GitHub以查看更多测试用例。我将我的作品保留在除主人以外的分支中,因此也要检查这些分支以进行额外的测试。
我选择编写自己的轻量级测试例程,但也可以使用更强大的单元测试框架,如CppUnit。
答案 1 :(得分:60)
在没有任何预先存在的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();
}
答案 2 :(得分:21)
通过抽象出硬件访问并在我的测试中模拟它,我在单元测试PIC代码方面取得了相当大的成功。
例如,我用
抽象PORTA#define SetPortA(v) {PORTA = v;}
然后可以轻松地模拟SetPortA,而无需在PIC版本中添加开销代码。
一旦硬件抽象测试了一段时间,我很快发现通常代码从测试平台到PIC并且第一次工作。
<强>更新强>
我使用#include接缝作为单元代码,#包括测试装备的C ++文件中的单元代码,以及目标代码的C文件。
作为一个例子,我想复用四个7段显示器,一个端口驱动段,另一个端口选择显示器。显示代码通过SetSegmentData(char)
和SetDisplay(char)
与显示界面连接。我可以在我的C ++测试装备中模拟这些并检查我是否得到了我期望的数据。对于目标,我使用#define
,以便在没有函数调用的开销的情况下获得直接赋值
#define SetSegmentData(x) {PORTA = x;}
答案 3 :(得分:15)
答案 4 :(得分:11)
simavr是使用avr-gcc的AVR 模拟器。
它已经支持一些ATTiny和ATMega微控制器,并且 - 根据作者的说法 - 很容易添加更多。
在示例中有simduino,一个Arduino模拟器。它支持运行Arduino引导加载程序,可以通过Socat(修改后的Netcat)进行avrdude编程。
答案 5 :(得分:8)
您可以使用我的项目PySimAVR在Python中进行单元测试。 Arscons 用于构建, simavr 用于模拟。
示例:
from pysimavr.sim import ArduinoSim
def test_atmega88():
mcu = 'atmega88'
snippet = 'Serial.print("hello");'
output = ArduinoSim(snippet=snippet, mcu=mcu, timespan=0.01).get_serial()
assert output == 'hello'
开始测试:
$ nosetests pysimavr/examples/test_example.py
pysimavr.examples.test_example.test_atmega88 ... ok
答案 6 :(得分:6)
我们在大型科学实验中使用Arduino板进行数据采集。随后,我们必须支持几个具有不同实现的Arduino板。我编写了Python实用程序,以在单元测试期间动态加载Arduino十六进制图像。以下链接中的代码通过配置文件支持Windows和Mac OS X.要找出Arduino IDE放置十六进制图像的位置,请在点击构建(播放)按钮之前按Shift键。在点击上传时按下shift键,找出您的avrdude(命令行上传工具)在您的Arduino系统/版本上的位置。或者,您可以查看包含的配置文件并使用您的安装位置(目前在Arduino 0020上)。
答案 7 :(得分:6)
答案 8 :(得分:6)
该程序允许自动运行多个Arduino单元测试。测试过程在PC上启动,但测试在实际的Arduino硬件上运行。一组单元测试通常用于测试一个Arduino库。 (这个
Arduino论坛:http://arduino.cc/forum/index.php?topic=140027.0
GitHub项目页面:http://jeroendoggen.github.com/Arduino-TestSuite
Python包索引中的页面:http://pypi.python.org/pypi/arduino_testsuite
单元测试使用“Arduino Unit Testing Library”编写:http://code.google.com/p/arduinounit
对每组单元测试执行以下步骤:
答案 9 :(得分:5)
将特定于硬件的代码与其他代码分开或抽象出来,这样您就可以在任何平台上测试和调试更大的“休息”,这些平台上有很好的工具,并且您最熟悉它。
基本上,尝试尽可能多地从已知的工作构建块构建最终代码。然后,剩余的硬件特定工作将变得更加容易和快捷。您可以通过自己使用现有的模拟器和/或模拟设备来完成它。然后,当然,你需要以某种方式测试真实的东西。根据具体情况,可能会或可能不会很好地自动化(即谁按或按什么按钮并提供其他输入?谁或什么会观察和解释各种指标和输出?)。
答案 10 :(得分:5)
我在编写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)
等)。
答案 11 :(得分:4)
James W. Grenning撰写精彩的书籍,这是关于嵌入式C代码 Test Driven Development for Embedded C 的单元测试。
答案 12 :(得分:4)
我为此建立了arduino_ci
。虽然它仅限于测试Arduino库(而不是独立的草图),但它可以在本地或CI系统(如Travis CI或Appveyor)上运行单元测试。
考虑一个名为DoSomething
的Arduino Library目录中一个非常简单的库do-something.cpp
:
#include <Arduino.h>
#include "do-something.h"
int doSomething(void) {
return 4;
};
您将按如下方式对其进行单元测试(使用名为test/is_four.cpp
的测试文件或其他类似测试文件):
#include <ArduinoUnitTests.h>
#include "../do-something.h"
unittest(library_does_something)
{
assertEqual(4, doSomething());
}
unittest_main() // this is a macro for main(). just go with it.
这就是全部。如果assertEqual
语法和测试结构看起来很熟悉,那是因为我采用了一些Matthew Murdoch's ArduinoUnit library
他在his answer中提到过。
有关单元测试I / O引脚,时钟,串行端口等的更多信息,请参见Reference.md。
使用ruby gem中包含的脚本编译和运行这些单元测试。有关如何进行设置的示例,请参阅README.md或仅从以下示例之一进行复制:
答案 13 :(得分:3)
有一个名为ncore的项目,它为Arduino提供本机核心。并允许您编写Arduino代码的测试。
来自项目描述
本机核心允许您编译和运行Arduino草图 PC,一般不做修改。它提供了原生版本 标准的Arduino函数,以及命令行的解释器 草图的输入通常来自硬件 本身。
同样在"what do I need to use it" section
上如果你想构建测试,你需要cxxtest http://cxxtest.tigris.org。 NCORE已经过cxxtest 3.10.1测试。
答案 14 :(得分:2)
如果您想在MCU外部(在桌面上)对代码进行单元测试,请查看libcheck: https://libcheck.github.io/check/
我用它来测试我自己的嵌入代码几次。这是非常强大的框架。
答案 15 :(得分:1)
尝试 Autodesk电路模拟器。它允许使用许多其他硬件组件测试Arduino代码和电路。
答案 16 :(得分:1)
将Proteus VSM与Arduino库一起使用来调试代码或进行测试。
这是将代码放到板上之前的最佳做法,但请确保时间安排,因为模拟不会在板上运行时实时运行。
答案 17 :(得分:1)
您可以使用emulare - 您可以在图表上拖放微控制器并在Eclipse中运行您的代码。网站上的文档告诉您如何设置它。
答案 18 :(得分:0)
基本的Arduino是用C和C ++编写的,甚至arduino的库也是用C和C ++编写的。因此,简单来说,只需将代码作为C和C ++进行处理,然后尝试进行单元测试即可。在这里,“ handle”一词的意思是您将所有基本语法(例如serial.println)更改为sysout,将pinmode更改为变量,将void循环更改为while()循环,这可能会在密钥库中或在某些迭代后中断。
我知道这是一个漫长的过程并且不是那么简单。根据我的个人经验,一旦您开始使用它,它将变得更加可靠。
-Nandha_Frost
答案 19 :(得分:0)
如果您有兴趣运行INO草图并检出串行输出,请在我的Arduino NMEA checksum项目中对此进行有效的实现。
以下脚本获取文件,并使用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和十六进制,请运行:
./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
,可用于运行草图。就像我说的,timeout
否则它将永远不会终止:
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退出,否则脚本将失败。