Arduino コードを単体テストできるようにしたいと考えています。理想的には、コードを Arduino にアップロードしなくてもテストを実行できるようにすることです。これに役立つツールやライブラリは何ですか?
便利な開発中の Arduino エミュレーターがありますが、まだ使用する準備ができていないようです。
Atmel のAVR Studioには便利なチップ シミュレータが含まれていますが、Arduino IDE と組み合わせて使用する方法がわかりません。
Arduino コードを単体テストできるようにしたいと考えています。理想的には、コードを Arduino にアップロードしなくてもテストを実行できるようにすることです。これに役立つツールやライブラリは何ですか?
便利な開発中の Arduino エミュレーターがありますが、まだ使用する準備ができていないようです。
Atmel のAVR Studioには便利なチップ シミュレータが含まれていますが、Arduino IDE と組み合わせて使用する方法がわかりません。
単体テストの意味については多くの議論がありますが、ここでそれについて議論しようとしているわけではありません。この投稿は、最終的なターゲット ハードウェアでのすべての実用的なテスト を避けるように指示しているわけではありません。最も平凡で頻繁なテストからターゲット ハードウェアを排除することによって、開発フィードバック サイクルを最適化することについて強調しようとしています。テスト対象のユニットは、プロジェクト全体よりもはるかに小さいと想定されています。
単体テストの目的は、独自のコードの品質をテストすることです。通常、単体テストでは、制御できない要素の機能をテストするべきではありません。
このように考えてみてください: Arduino ライブラリ、マイクロコントローラー ハードウェア、またはエミュレーターの機能をテストしたとしても、そのようなテスト結果が自分の作業の品質について何かを伝えることは絶対に不可能です。したがって、ターゲット デバイス (またはエミュレーター) で実行されない単体テストを作成する方が、はるかに価値があり効率的です。
ターゲット ハードウェアで頻繁にテストを行うと、サイクルが非常に遅くなります。
ステップ 3 は、シリアル ポート経由で診断メッセージを取得することを期待しているが、プロジェクト自体が Arduino の唯一のハードウェア シリアル ポートを使用する必要がある場合に特に厄介です。SoftwareSerial ライブラリが役立つかもしれないと考えている場合は、同時に他の信号を生成するなど、正確なタイミングを必要とする機能が中断される可能性があることを知っておく必要があります。この問題は私に起こりました。
繰り返しになりますが、エミュレーターを使用してスケッチをテストし、タイム クリティカルなルーチンが実際の Arduino にアップロードするまで完全に実行された場合、学べる唯一の教訓は、エミュレーターに欠陥があるということです。あなた自身の仕事の質については何も明らかにしません。
おそらく、コンピュータを使用して Arduino プロジェクトに取り組んでいるでしょう。そのコンピューターは、マイクロコントローラーよりも桁違いに高速です。コンピューターでビルドして実行するテストを作成します。
Arduino ライブラリとマイクロコントローラの動作は、正しいか、少なくとも一貫して間違っていると想定する必要があります。
テストが期待に反する出力を生成する場合、テストされたコードに欠陥がある可能性があります。テスト出力が期待どおりであるのに、プログラムを Arduino にアップロードしたときにプログラムが正しく動作しない場合は、テストが誤った仮定に基づいており、テストに欠陥がある可能性が高いことがわかります。どちらの場合でも、次のコード変更がどうあるべきかについての真の洞察が得られます。フィードバックの質が「何かが壊れている」から「この特定のコードが壊れている」に改善されました。
最初に行う必要があるのは、テストの目標を特定することです。独自のコードのどの部分をテストするかを考えてから、個別の部分を分離してテストできるようにプログラムを作成してください。
テストするパーツが Arduino 関数を呼び出す場合は、テスト プログラムでモックアップの代替品を提供する必要があります。これは、見た目よりもはるかに少ない作業です。モックアップは、テストに予測可能な入力と出力を提供する以外に、実際に何もする必要はありません。
テストする予定の独自のコードは、.pde スケッチ以外のソース ファイルに存在する必要があります。心配しないでください。スケッチは、スケッチの外部にあるソース コードを使用してもコンパイルされます。実際に取り掛かると、プログラムの通常のエントリ ポイント以外は、スケッチ ファイルで定義する必要があります。
あとは、実際のテストを作成し、お気に入りの C++ コンパイラを使用してコンパイルするだけです! これはおそらく、実際の例で最もよく説明されています。
ここにある私のお気に入りのプロジェクトの 1 つには、PC 上で実行されるいくつかの簡単なテストがあります。この回答の送信では、Arduino ライブラリ関数のいくつかをモックアップした方法と、それらのモックアップをテストするために作成したテストについて説明します。これは、私がモックアップを書いたので、他の人のコードをテストしないという前に言ったことに反するものではありません。自分のモックアップが正しいことを確信したかったのです。
Arduino ライブラリによって提供される一部のサポート機能を複製するコードを含む mock_arduino.cpp のソース:
#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;
偽のシリアル.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用の既存のユニットテストフレームワークがない場合、私は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();
}
ハードウェア アクセスを抽象化し、テストでモック化することで、PIC コードの単体テストにかなりの成功を収めています。
たとえば、PORTA を次のように抽象化します。
#define SetPortA(v) {PORTA = v;}
次に、PIC バージョンにオーバーヘッド コードを追加することなく、SetPortA を簡単にモックできます。
ハードウェアの抽象化がしばらくテストされると、通常、コードがテスト リグから PIC に移動し、最初に機能することがすぐにわかります。
アップデート:
ユニット コードには #include シームを使用し、テスト リグにはユニット コードを C++ ファイルに、ターゲット コードには C ファイルを #include します。
例として、4 つの 7 セグメント ディスプレイを多重化し、1 つのポートでセグメントを駆動し、2 つ目のポートでディスプレイを選択したいとします。ディスプレイ コードは、 および を介してディスプレイとインターフェイスしSetSegmentData(char)
ますSetDisplay(char)
。これらを C++ テスト リグでモックして、期待どおりのデータが得られることを確認できます。私が使用するターゲットの場合#define
、関数呼び出しのオーバーヘッドなしで直接割り当てを取得できます
#define SetSegmentData(x) {PORTA = x;}
simavrは、 avr-gcc を使用した AVRシミュレーターです。
すでにいくつかの ATTiny および ATMega マイクロコントローラーをサポートしており、著者によると、さらに追加するのは簡単です。
例には、Arduino エミュレーターである simduino があります。Arduino ブートローダーの実行をサポートし、 Socat (変更されたNetcat )を介して avrdude でプログラムできます。
私のプロジェクト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
大規模な科学実験でのデータ取得に Arduino ボードを使用しています。その後、実装が異なる複数の Arduino ボードをサポートする必要があります。単体テスト中に Arduino hex イメージを動的にロードする Python ユーティリティを作成しました。以下のリンクにあるコードは、構成ファイルを介して Windows と Mac OS X をサポートします。Arduino IDE によって 16 進イメージが配置されている場所を確認するには、ビルド (再生) ボタンを押す前にシフト キーを押します。アップロードを押しながらシフトキーを押して、avrdude (コマンドラインアップロードユーティリティ) がシステム/Arduino のバージョンのどこにあるかを調べます。または、含まれている構成ファイルを見て、インストール場所 (現在 Arduino 0020) を使用することもできます。
このプログラムは、いくつかの Arduino 単体テストの自動実行を可能にします。テスト プロセスは PC 上で開始されますが、テストは実際の Arduino ハードウェア上で実行されます。通常、単体テストの 1 つのセットは、1 つの 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
単体テストのセットごとに、次の手順が実行されます。
ハードウェア固有のコードを残りから分離または抽象化して、優れたツールがあり、最も使い慣れているプラットフォームで、より大きな「残り」をテストおよびデバッグできるようにします。
基本的に、動作することがわかっているできるだけ多くの構成要素から、最終的なコードをできるだけ多く構築するようにしてください。残りのハードウェア固有の作業は、はるかに簡単かつ高速になります。既存のエミュレーターを使用したり、独自にデバイスをエミュレートしたりすることで、それを完了することができます。そしてもちろん、何らかの形で本物をテストする必要があります。状況に応じて、それはうまく自動化できる場合とそうでない場合があります (つまり、誰または何がボタンを押して他の入力を提供するか? 誰または何がさまざまなインジケーターと出力を観察して解釈するか?)。
James W. Grenning は素晴らしい本を書いています。この本は組み込み C コードのテスト駆動型開発の単体テストに関するものです。
Arduino のネイティブ コアを提供するncoreというプロジェクトがあります。また、Arduino コードのテストを作成できます。
プロジェクトの説明から
ネイティブ コアを使用すると、PC 上で Arduino スケッチをコンパイルして実行できます。通常、変更は必要ありません。標準の Arduino 関数のネイティブ バージョンと、通常はハードウェア自体から来る入力をスケッチに与えるコマンドライン インタープリターを提供します。
テストをビルドする場合は、 http://cxxtest.tigris.orgの cxxtest が必要です 。NCORE は cxxtest 3.10.1 でテストされています。
emulareを使用できます— ダイアグラムにマイクロコントローラーをドラッグ アンド ドロップし、Eclipse でコードを実行できます。Web サイトのドキュメントに、設定方法が記載されています。
基本的に Arduino は C と C++ で書かれており、arduino のライブラリも C と C++ で書かれています。したがって、簡単に言えば、コードを C および C++ として処理し、単体テストを実行してみてください。ここで、「ハンドル」という言葉は、serial.println を sysout に、pinmode を変数に、void ループを while() ループに変更するなど、すべての基本的な構文を変更することを意味します。
これは少し長いプロセスであり、それほど簡単ではないことは承知しています。私の個人的な経験では、一度やり遂げると、これはより信頼できるものになります.
-Nandha_Frost