はじめての Unity というか Ceedling
こまった・・・
CppUTest を使っていましたがあっという間に行き詰まりました。static な関数やグローバル変数を使うとテストコードから参照できなくなるためです(そもそも static にすると他のソースから隠蔽されるので自明な話)。普通、テストコードと本体のソースコードは別にしてリンクすると思いますが、ソースコード自体を同一ファイルにしなければなりません。できなくもないですが、ヘッダファイルとかいろいろと面倒なことになる未来しか見えません。
ということで C ネイティブの Unity を使ってみることにしました。ところが最初は、インストールの説明がまったくないためかなり遠回りしました。Unity 単体では使いにくいし、その他のツールも使うとなるとめちゃくちゃ面倒で、ガチャガチャしてるうちに Ceedling に出会いました。Ceedling をインストールすると Unity/CMock/CException も一緒にインストールされて自動でテストを行ってくれます。
Ceedling のインストール
Ceedling はコマンド1つでインストールできます。
$ sudo gem install ceedling
ただ、OSのパッケージ管理に対応していないので、Ruby を使っている人は他のプロジェクトと混ざらないよう rbenv を使うことをおすすめします。なお、Ruby 3 ではうまく動かないようなので Ruby 2.x をおすすめします。
プロジェクト作成
ceedling コマンドですべてを操作しますが、まず最初にやらなければいけないのはプロジェクト(ディレクトリツリー)の作成です。
$ ceedling new project_x
Welcome to Ceedling!
create project_x/project.yml
Project 'project_x' created!
- Execute 'ceedling help' from project_x to view available test & build tasks
$ tree project_x/
project_x/
├── project.yml
├── src
└── test
└── support
名前からしてだいたいおわかりかと思いますが、このツリーにある src で開発を行い、test にテストコードを書くということになります。なお、メッセージにあるとおり ceedling help
でヘルプを見ることができます。(見てもよくわからないですが)
テスト作成
まず最初に実装する関数の仕様を定義します。今回は掛け算をやってみます。単純に、2つの引数を取り、かけ合わせた結果を返す関数とします。関数名は multiply()。
とにもかくにも Test First! なぜテストを先に書くのかご存じない方は前回の記事をご覧ください。
プロジェクト内の test ディレクトリにソースを書きますが、ファイル名を test_ で始まるようにしてください。また、テスト関数も同じく test_ から始めます。Ceedling はこれらの名前を見て自動的にテストを実行してくれるのです。
#include "unity.h"
// 前処理と後処理を書く関数ですが、ここでは必要ないので中身を書いていません。
// 省略はできませんのであしからず。
void setUp(void){};
void tearDown(void){};
// テスト関数。test_ で始めるのを忘れずに。
void test_do_multiply(void)
{
int result;
// 掛け算実行!
result = multiply(2,3);
// ここがテスト。2x3 の結果が 6 であることを期待しています
TEST_ASSERT_EQUAL_INT(6, result);
}
テスト実行は ceedling コマンドをオプション無しで実行します。ただし、プロジェクトのトップディレクトリで実行しなければなりません。
$ ceedling
Test 'test_multiply.c'
----------------------
Generating runner for test_multiply.c...
Compiling test_multiply_runner.c...
Compiling test_multiply.c...
test/test_multiply.c: In function ‘test_do_multiply’:
test/test_multiply.c:8:11: warning: implicit declaration of function ‘multiply’ [-Wimplicit-function-declaration]
8 | result = multiply(2,3);
| ^~~~~~~~
以下略
関数の実体がありませんのでエラーになります。さあ、multiply を実装してこのエラーを解決してやろうではありませんか!
本体実装
さて、Ceedling はテストコードからどうやってテスト対象のソースを見つけるのでしょうか。当然リンクしなければ実行できませんから、リンクするファイルを見つけなければなりません。ヒントが先程のテスト失敗エラーに含まれています。(長いので上では省略してますが)
NOTICE: If the linker reports missing symbols, the following may be to blame:
1. Test lacks #include statements corresponding to needed source files.
2. Project search paths do not contain source files corresponding to #include statements in the test.
3. Test does not #include needed mocks.
恥ずかしながら私は詳細を理解してませんが、ヘッダファイルをもとに推測しているようですね。なのでテストコードにヘッダファイルを入れます。サーチパスがどうなってるかはまだ確認できてませんが、まずは以下の一行を先頭に追記します。
#include "multiply.h"
ヘッダファイルの本体を作ります。
#ifndef MULTIPLY_H
#define MULTIPLY_H
int multiply(int a, int b);
#endif
そして関数を実装します。ここではわざとテストを失敗するように書いてみます。
#include "multiply.h"
int multiply(int a, int b){
return 0;
}
テストを実行してみます。
$ ceedling
Test 'test_multiply.c'
----------------------
Compiling multiply.c...
Linking test_multiply.out...
Running test_multiply.out...
-------------------
FAILED TEST SUMMARY
-------------------
[test_multiply.c]
Test: test_do_multiply
At line (12): "Expected 6 Was 0"
--------------------
OVERALL TEST SUMMARY
--------------------
TESTED: 1
PASSED: 0
FAILED: 1
IGNORED: 0
---------------------
BUILD FAILURE SUMMARY
---------------------
Unit test failures.
ちょっと表示が冗長な気もしますが、どこのテストに失敗したかを見るには FAILED TEST SUMMARY を見れば良いようです。2x3 で 6 が返ってくることを想定 (Expected 6) していたのに 0 が返ってきてしまってます(Was 0)。
完成!
ではバグを FIX して再テスト。FIX の diff はさすがに誰でもわかるでしょうから省略します。
$ ceedling
Test 'test_multiply.c'
----------------------
Compiling multiply.c...
Linking test_multiply.out...
Running test_multiply.out...
--------------------
OVERALL TEST SUMMARY
--------------------
TESTED: 1
PASSED: 1
FAILED: 0
IGNORED: 0
Awesome!
static 関数をどうするか
冒頭に問題提起した static 関数にどう対応するかですが、ご覧の通り Ceedling は src, test に分けてファイルを作成し、それぞれリンクするので、このままでは static 関数のテストはできません。私の場合は、ceedling を実行する前にソースを生成してしまうことにしました。以下に簡単なスクリプトを書きますが、前提としてテストコードは上の例と違い test/multiply.c に書いています。
#!/bin/bash
TARGETS="multiply"
# Generate test code
for t in $TARGETS; do
cat src/$t.c test/$t.c > test/test_$t.c
done
# Run test
ceedling
# clean up
for t in $TARGETS; do
rm -f test/test_$t.c
done
test_ がついていないファイルは Ceedling が無視することを利用しています。ファイル名を自由に作れないあやしい方法ですが、目的は果たせるのでしばらくはこれで運用してみようと思います。
ところで、ceedling は Ruby で作られているので、Spring なかと組み合わせると相性がいいかもしれません。こんど試してみようかと思案中。
Discussion