はじめに
組込み開発でTDDをやるために勉強してきました。
今までアプリケーション中心の勉強だったため、少し組込みっぽい領域のものを作ってみます(組込みLinuxを想定)。
見切り発車なので、途中で方針変わるかもしれません。実際やらないとわからないので、温かい目で見守って下さい。
ご意見、ご要望は遠慮なくフィードバック下さい!とても励みになります。
作ろうとしているもの
やはり組込みと言えばLチカでしょう!
ただし、簡単に試せるようにしたいので、キーボードのLEDをチカチカさせます(caps lockとかのやつ)。
高機能なライブラリは使用せず、デバイスファイルの操作でなんやかんややって行きます。
あまり簡単すぎるとツマンナイですし、実践的でないため、次の機能実現を考えていきます。
- キーボード入力で振る舞いを変える(ボタン)
- 設定時間になると振る舞いを変える(タイマ)
組込みなので、別ハードへの移植性は常に考えるようにします。
開発環境
OS | Virtual box ubuntu 16.04 |
コンパイラ | gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609 |
ビルドツール | autotools, pkg-config |
ライブラリ | libevdev-1.0 |
テストフレームワーク | googletest/googlemock@62ba5d9 |
プロダクトコード | C or C++(悩み中) |
下調べ
何をするにも下調べは重要ですね。
とりあえず、自分の環境を調べてみます。
キーボードイベント取得
Linuxでキーボード入力イベントを取得するためには、/dev/input/event〇〇ファイルを監視します。
〇〇の部分は数字が入るのですが、環境や起動時に認識された順でキーボードが何番になるか変わります(udevで固定することが可能ですが、今回はとりあえず置いておきます)。
/proc/bus/input/devicesを覗いて、キーボードを探します。
現状は、event2(input2)がキーボードに対応しています。
cat /proc/bus/input/devices
== 途中省略 ==
I: Bus=0011 Vendor=0001 Product=0001 Version=ab41
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/devices/platform/i8042/serio0/input/input2
U: Uniq=
H: Handlers=sysrq kbd event2 leds
B: PROP=0
B: EV=120013
B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=7
== 以下省略 ==
/dev/inputのようなイベントデバイスのために、Linuxはevdev(Linux Input drivers v1.0)インタフェースを提供しています。
このevdevのラッパーライブラリがlibevdevです。
今回は、このlibevdevを使っていきます。
キーボードLED
次に、キーボードのLEDを確認します。
LinuxではLED classが用意されており、LinuxにLEDが認識されると、sysfsの/sys/class/leds下にexportされます。
capslock, numlock, scrolllockのLEDが認識されていますね。
(私のキーボードにはnumlockとscrolllockキーにはLEDついていないんですが…要調査)
ls /sys/class/leds/
input2::capslock input2::numlock input2::scrolllock
ライブラリ理解のためのテスト
ハード(キーボード)もライブラリ(libevdev)もある、という恵まれた状態からのスタートなので、対象ライブラリを理解するためにテストを書いていきます。
このテストを書くことで、自分のライブラリ理解をテストします。理解したことがテストとして残るのは、とても良いことに思えます。
これがホストマシン上で実行できる場合は、ラッキーです。
1. ターゲットボードにしか対象のハード/ライブラリがない、という状況も多いのではないでしょうか。
2. ターゲット上ですら動かない、物がない、っていうのもよくある話でしょう。
1の場合は素直にクロスビルドしながら進められます。
2の場合、APIがわかっているのであれば、モックを作りましょう。本物が出てきた時に、テストで書いた使い方が正しいかどうかテストできます。
APIもわからないとなると想像でモックを作ります。実装の詳細が分かっていない状態でモックを作ることにも利点はあって、APIが程よく抽象化できる可能性があります。
libevdev理解のために書いたテスト
前置きが長くなりましたが、今回書いたテストを見ていきましょう。
コードは一部省略して掲載します。全貌が見たい方はこちら。
まず、ファイルディスクリプタから、evdevの制御データを取得します。
成功したら、0以上が返ってきます。
最初、このテストは失敗しました。ルート権限が必要だとわかったので、テスト名をわかるように(AsRootを追加)します。
これで、後日同じことでハマることはないでしょう。急に誰かに引き継ぐことになっても、root権限がないと動かせないことがわかるでしょう。
TEST_F(EvdevSampleTest, InputEventFileCanOpenAsRoot) {
struct libevdev *dev = nullptr;
int fd = open("/dev/input/event2", O_RDONLY|O_NONBLOCK);
int rc = libevdev_new_from_fd(fd, &dev);
EXPECT_GE(rc, 0);
}
evdevの制御データが取得できたので、何も入力がない状態では、EAGAINが返ってくることを確認します。
これは一発でパスしました。
TEST_F(EvdevSampleTest, ReturnsEAGAIN) {
input_event ev {};
int actual = libevdev_next_event(evdev_, LIBEVDEV_READ_FLAG_NORMAL, &ev);
EXPECT_EQ(-EAGAIN, actual);
}
次にEnterキーを押すと、LIBEVDEV_READ_STATUS_SUCCESSが返ってきて、期待通りのinput_eventが取得できることをテストします。
テスト実行後、本テスト実行中にテストが止まるので、Enterキーを押してあげると、テストがパスします(もはやユニットテストでないが気にしません)。
最後のEXPECT_EQにたどり着いている時点で、取得したイベントが期待値通りであることが確定していますが、とりあえず気にしません。
TEST_F(EvdevSampleTest, CaptureEnterKeyPress) {
auto expect = create_key_event(KEY_ENTER, KEY_PRESSED);
input_event actual {};
while (true) {
int rc = libevdev_next_event(evdev_, LIBEVDEV_READ_FLAG_NORMAL, &actual);
if ((rc == LIBEVDEV_READ_STATUS_SUCCESS) && actual == expect) break;
}
EXPECT_EQ(expect, actual);
}
テストを作りながら考えたこと
他の入力デバイスでも使えるようにすることを考えると、汎用的なコールバックインタフェースがあると良さそう。
libevdevはコールバックの仕組みを提供してくれないので、コールバックの仕組みは一から作るかな。
コメント
@iwatake2222リンクをコピー このコメントを報告 1
@tomoyuki-nakabayashiリンクをコピー このコメントを報告
2
@iwatake2222(編集済み) リンクをコピー このコメントを報告
2
@tomoyuki-nakabayashiリンクをコピー このコメントを報告 1
@iwatake2222リンクをコピー このコメントを報告 1
おお! 組み込みのテストに関して、まさに知りたかったことを記事にしていただいた感じです。
実例込みの記事で、とても分かりやすかったです。
たいていの場合は、残念ながらこれら2つのケースですよね。
お時間があるときに、これらのついても記事にしていただけると嬉しいです。(勝手なことを言ってみる。。。)
組込み開発する上で避けて通れない道ですね(嫌な話ですが)。
このような状況でも絶望せずに、テストで設計を駆動しよう!という趣旨にしたく、まさにリクエストして頂いたような内容にできるといいなぁ、と思っています。
こういうデバイス想定してやって欲しい、というのがあればおっしゃって下さい(自分で良いアイデアが浮かんでいないので)。必ずやる、とも、いつまでにやる、ともお約束はできませんが(笑)
まさしくこれが理由で、効率的なテストを導入できていない現場が多々あると思います。特に既存システムをベースにした開発だと絶望しかないですね。
デザインパターンの本や記事もそうですが、こういうのの題材って難しいですよね。簡単なテーマだとありがたみを感じられないし、実践的なことだと本質とずれた内容になってしまうし。。。
個人的には、記事のメインはシンプルな題材で解説があって、補足説明で応用的なケースへのガイドがあるというパターンが好きです。
僕もあまりいいアイデアは浮かびませんが、GPIOを使ったシンプルなコードから始めていただけると、とっつきやすいと思います。例えば、下記のようなコード。
これだけでも、「テストはホストPCでやる? ターゲット基板でやる?」。「ホストPCでやる場合のGPIOモックは?」「ロジック部分(この場合は、
and
だけだが)だけホストPCでテストする?」、「ターゲット基板でやる場合の出力の確認は? 目視? レジスタダンプとの比較?」、「そもそも『GPIO入力』処理のテストは? ドライバはテストのスコープ外?」などなど、膨らませられそうです。(当然、読者側にも想像力(ここではロジックはand
だけど、実際にはもっと複雑なロジックになるはず、など)が求められますが。)これだと、Linuxでも、Non-OSのマイコンでも応用できると思います。
↑のコードでさらに割り込みが入った場合 も気になります。例えば、下記のようなコードだと、あえて冗長に書いていますが、リセットボタンの押されるタイミングによって、リセットが効かないというバグがあります。「こういうコードってテストできるの?」というのも気になります。テストに向かないケース集なんかもあると役立つと思います。
また、上の例とは真逆で、「既存の大規模システム(しかもITronなどの組み込み独自のプラットフォーム)で、新規モジュールを開発することになった。どうやってTDDで開発する?」 みたいなことも興味あります。
とりあえず、思いつくままに自分勝手に羅列しちゃいました。まとまりがなくてすいません。。。そして、これらに対して絶対的な解を用意するのは不可能だとも思います(w これが出来たらコンサルとして独立、どころか本書けちゃいますね
本業優先で、お時間のあるときにでもシェアしてください~
アイデア頂き、ありがとうございます。
今やっている内容を、次の想定で進めるのもありかな、と思いました。
最終的には、ボタンとLEDがGPIOに繋がったハードで動かしたいが、ハードがないので、キーボードで代替している。
(これなら、最終的にシンプルなGPIOを使ったコードに落とせそう)
本当に組込みっぽいものの開発ができていたかどうか、の試金石にもなりますし、良い気がします。
一回これで内容考えてみます。
絶望しなくて済むようにお互い頑張りましょうw
そうですね。この記事にあるように、キーボードでLチカというのも全然ありだと思います。
手元で簡単に確認できますしね。