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

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

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


当前回答

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

其他回答

有一个叫ncore的项目,它为Arduino提供了原生内核。并允许您为Arduino代码编写测试。

来自项目描述

本机核心允许您编译和运行Arduino草图 PC,一般无需修改。的原生版本 标准的Arduino功能,以及一个命令行解释器 草图的输入通常来自硬件 本身。

同样在“我需要使用它什么”部分

如果要构建测试,则需要从 http://cxxtest.tigris.org。NCORE已经用cxxtest 3.10.1进行了测试。

该程序允许自动运行几个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

对每组单元测试执行以下步骤:

Read the config file to find out which tests to run The script compiles and uploads an Arduino sketch that contains the unit testing code. The unit tests are run on the Arduino board. The results of the test are printed over the serial port and analyzed by the Python script. The script starts the next test, repeating the above steps for all test that are requested in the configuration file. The script prints a summary showing an overview of all the failed/passed tests in the complete testsuite.

通过抽象出硬件访问并在测试中模拟它,我在单元测试PIC代码方面取得了相当大的成功。

例如,我用抽象PORTA

#define SetPortA(v) {PORTA = v;}

然后SetPortA可以很容易地模拟,而不需要在PIC版本中添加开销代码。

一旦硬件抽象被测试了一段时间,我很快发现代码通常会从测试平台到PIC,并且第一次就能工作。

更新:

对于单元代码,我使用#include seam,对于测试平台,在c++文件中使用#include单元代码,对于目标代码使用C文件。

作为一个例子,我想复用四个7段显示器,一个端口驱动段和第二个选择显示。显示代码通过SetSegmentData(char)和SetDisplay(char)与显示进行接口。我可以在我的c++测试平台中模拟这些,并检查我是否得到了我期望的数据。对于目标,我使用#define,这样就可以直接赋值,而不需要调用函数

#define SetSegmentData(x) {PORTA = x;}

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

看起来乳香就能完美地完成这项工作。

Emulino是Greg Hewgill为Arduino平台开发的模拟器。(源)

GitHub库