如何对 Arduino 代码进行单元测试?
-
13-09-2019 - |
题
我希望能够对我的 Arduino 代码进行单元测试。理想情况下,我能够运行任何测试,而无需将代码上传到 Arduino。有哪些工具或库可以帮助我解决这个问题?
有一个 Arduino 模拟器正在开发中 这可能很有用,但似乎还没有准备好使用。
AVR工作室 Atmel 的 包含一个可能有用的芯片模拟器,但我不知道如何将它与 Arduino IDE 结合使用。
解决方案 2
在没有任何预先存在的单元测试框架的用于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();
}
其他提示
不要在 Arduino 设备或模拟器上运行单元测试
反对基于微控制器设备/仿真器/Sim 的测试的案例
关于什么有很多讨论 单元测试 意思是,我并不是真的在这里对此提出争论。这篇文章是 不是告诉你要避免 全部 对您的最终目标硬件进行实际测试。我试图通过从最平凡和频繁的测试中消除目标硬件来优化您的开发反馈周期。假定正在测试的单元比整个项目小得多。
单元测试的目的是测试自己代码的质量。单元测试通常不应该测试您无法控制的因素的功能。
这样想:即使您要测试 Arduino 库、微控制器硬件或模拟器的功能,它也是如此 绝对不可能 这样的测试结果可以告诉您有关您自己工作质量的任何信息。因此,编写不在目标设备(或模拟器)上运行的单元测试更有价值、更高效。
对目标硬件进行频繁测试的周期非常缓慢:
- 调整你的代码
- 编译并上传到Arduino设备
- 观察行为并猜测是否 你的 代码正在执行您期望的操作
- 重复
如果您希望通过串行端口获取诊断消息,但您的项目本身需要使用 Arduino 唯一的硬件串行端口,则第 3 步尤其令人讨厌。如果您认为 SoftwareSerial 库可能有所帮助,您应该知道这样做可能会破坏任何需要精确定时的功能,例如同时生成其他信号。这个问题发生在我身上。
再说一次,如果您要使用模拟器测试您的草图,并且您的时间关键型例程在上传到实际的 Arduino 之前都运行完美,那么您将学到的唯一教训是模拟器有缺陷 - 并且仍然知道这一点揭示 没有什么 关于质量 你自己 工作。
如果在设备或模拟器上测试很愚蠢,那么什么 应该 我愿意?
您可能正在使用计算机来处理 Arduino 项目。该计算机比微控制器快几个数量级。编写测试来构建和 在您的计算机上运行.
请记住,Arduino 库和微控制器的行为应该是 假设是正确的或至少 始终如一地 不正确.
当您的测试产生的输出与您的预期相反时,那么您所测试的代码中可能存在缺陷。如果您的测试输出符合您的预期,但当您将其上传到 Arduino 时程序无法正常运行,那么您就知道您的测试是基于不正确的假设,并且您的测试可能有缺陷。无论哪种情况,您都将获得关于下一个代码更改应该是什么的真正见解。您的反馈质量从“某物 已损坏”至 “这 具体代码 被打破”.
如何在您的 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。
我有相当大的成功单元通过抽象出硬件访问和在我的测试嘲笑它测试我的PIC码。
例如,我抽象PORTA与
#define SetPortA(v) {PORTA = v;}
然后SetPortA可以很容易地被模拟,而不会在PIC版本添加开销代码。
在硬件抽象已经测试了一段予很快就会发现,通常从代码测试装备到PIC推移和工作的第一次。
<强>更新强>
我使用的#include缝为单元的代码,#包括在C的单位代码测试台++文件,和目标代码C文件。
正如我想复4个7段显示器的例子中,一个端口驱动各段和第二选择显示。上述显示用代码的接口经由SetSegmentData(char)
和SetDisplay(char)
显示器。我可以在我的C ++测试装备嘲笑这些检查,我得到我预期的数据。对于目标我用#define
让我没有得到一个函数调用的开销,直接分配
#define SetSegmentData(x) {PORTA = x;}
您可以在Python单元测试与我的项目, PySimAVR 。的 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
我们使用在一个大的科学实验的数据采集的Arduino板。随后,我们必须支持多种的Arduino电路板用不同的实现。我写的Python事业单元测试过程中动态地加载Arduino的十六进制图像。下面的链接中找到的代码支持Windows和Mac OS X通过配置文件。要找出你的十六进制图像通过Arduino的IDE设置,打shift键你打的构建(播放)按钮之前。击Shift键,同时击中上传来找出你AVRDUDE(命令行上传工具)位于的Arduino的系统/版本。或者,你可以看看所包含的配置文件,并使用您的安装位置(目前在Arduino的0020)。
该程序允许自动运行多个 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 单元测试库”编写的: http://code.google.com/p/arduinounit
对每组单元测试执行以下步骤:
- 阅读配置文件以找出要运行哪些测试
- 该脚本编译并上传包含单元测试代码的 Arduino 草图。
- 单元测试在 Arduino 板上运行。
- 测试结果通过串行端口打印并由 Python 脚本进行分析。
- 该脚本开始下一个测试,对配置文件中请求的所有测试重复上述步骤。
- 该脚本打印一个摘要,显示完整测试套件中所有失败/通过的测试的概述。
请特定硬件的代码分离,或从其他抽象出来,所以你可以测试和调试的是更大的,你有很好的工具,并与你在任何平台上“休息”最熟悉的。
基本上,尝试建立尽可能多的最终代码的从尽可能多的已知到工作的积木越好。然后将剩余的特定硬件的工作会更容易和更快。您可以使用现有的仿真器和/或模拟在您自己的设备完成它。然后,当然,你需要以某种方式测试真实的东西。根据具体情况,其可以是或可以不是很好自动化(即谁或什么将按下的按钮和提供其它输入?谁或什么将观察和解释各种指示器和输出?)。
詹姆斯·W.Grenning 写了很棒的书,这本书是关于嵌入式 C 代码的单元测试 嵌入式 C 的测试驱动开发.
我使用 Searduino写Arduino的代码时。 Searduino是一个Arduino模拟器,可以很容易在C / C ++破解使用您喜欢的编辑器开发环境(Makefile中,C代码...)。您可以导入Arduino的草图,并在模拟器中运行它们。
0.8 Searduino的屏幕截图: HTTP:// searduino。 files.wordpress.com/2014/01/jearduino-0-8.png
0.9 Searduino将被释放和视频将被只要持续测试被完成记录....在一天或两天。
在模拟器上测试不被认为是真正的测试,但它肯定已经帮了我很多在寻找愚蠢/逻辑错误(忘记做pinMode(xx, OUTPUT)
,等等)。
BTW:我是人发展Searduino之一
。我建造了 arduino_ci
以此目的。尽管它仅限于测试 Arduino 库(而不是独立的草图),但它允许在本地或 CI 系统(如 Travis CI 或 Appveyor)上运行单元测试。
考虑 Arduino 库目录中有一个非常简单的库,名为 DoSomething
, , 和 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 的 ArduinoUnit 库他在中提到的 他的回答.
看 参考资料.md 有关单元测试 I/O 引脚、时钟、串行端口等的更多信息。
这些单元测试是使用 ruby gem 中包含的脚本编译和运行的。有关如何设置的示例,请参阅 自述文件.md 或者直接复制以下示例之一:
- 一个实际的例子, 测试队列实现
- 另一组测试 另一个队列项目
- 一个复杂的示例,模拟一个通过 SoftwareSerial 连接控制交互式设备的库,作为 Adafruit FONA 库
- 这 DoSomething 示例库 如上所示,用于测试arduino_ci本身
有一个名为 n芯项目,它提供本地核心为Arduino的。并允许你写测试的Arduino的代码。
从项目描述
本机的核心,您可以编译和运行在Arduino的草图 PC,一般没有修改。它提供了原生版本 标准的Arduino功能,和一个命令行interepreter,得到 输入您的草图,通常会来自硬件 本身。
另外关于“我需要什么用它”部分
如果你想建立的测试,你需要从cxxtest http://cxxtest.tigris.org 。 NCORE已经过测试与cxxtest 3.10.1。
如果要MCU(在桌面上)的外部单元测试代码,检查出libcheck: https://libcheck.github.io/check/
我用它来测试我自己的嵌入代码几次。这是非常强有力的框架。
您可以使用 emulare - 您可以将一个图表上放置一个微控制器,并在Eclipse中运行代码。网站上的文档,告诉您如何设置它。
使用变形杆菌VSM与一个Arduino库调试代码或对其进行测试。
这是让你的代码板载前最好的做法,但一定要与时机,因为他们在黑板上运行仿真不会实时运行。
尝试的Autodesk电路模拟器。它允许测试Arduino的代码和电路与许多其他的硬件部件。
在基本Arduino的写有C和C ++,甚至Arduino的文库是用C和C ++。所以,简单来说只是处理代码为C和C ++和尝试做单元测试。在这里,由单词“处理”我的意思是你改变像serial.println所有的基本语法SYSOUT,pinmode到varaibles,无效循环,while()循环打破无论是在keystock或一些迭代后。
我知道这一点是一个漫长的过程,而不是那么直forward.On我个人的经验,一旦你用它做,这原来是更可靠的。
-Nandha_Frost