我希望能够单元测试我的Arduino代码。理想情况下,我可以运行任何测试,而无需将代码上传到Arduino。哪些工具或库可以帮助我做到这一点?

目前正在开发的Arduino模拟器可能很有用,但似乎还没有准备好使用。

Atmel的AVR Studio包含一个芯片模拟器,可能很有用,但我不知道如何将它与Arduino IDE结合使用。


当前回答

试试Autodesk电路模拟器。它允许用许多其他硬件组件测试Arduino代码和电路。

其他回答

我在编写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库的Proteus VSM来调试代码或测试它。

这是在将代码安装到板上之前的最佳实践,但要确保计时,因为模拟不会在板上运行时实时运行。

为此我搭建了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_4 .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库 他在回答中提到了。

见参考。关于单元测试I/O引脚,时钟,串行端口等的更多信息。

这些单元测试是使用ruby gem中包含的脚本编译和运行的。有关如何设置的示例,请参阅README。或者从这些例子中复制一个:

一个测试Queue实现的实际示例 另一个Queue项目上的另一组测试 这是一个复杂的例子,模拟了一个通过SoftwareSerial连接控制交互设备的库,作为Adafruit FONA库的一部分 上面显示的DoSomething示例库,用于测试arduino_ci本身

James W. Grenning写了一本很棒的书,这本书是关于嵌入式C代码的单元测试的。

不要在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。