• このエントリーをはてなブックマークに追加

はじめに

仕事では、今までは新規開発だった。なので、真っ白なテキストからテストを書くことができた。しかし、これからは、流用開発。既存コードに機能追加しなければならない。そしてその既存コードはうんざりするほどの量があり、かつテストは1つもない。。。

1つのファイルは5000行くらいあって、インクルードはし放題。インターフェイスもごちゃまぜな感じ。そもそも、ヘッダファイルがソースコードごとにない!テストを通そうにも、うんざりするほどのUndef関数を作成しなければならず、実行ファイルも自力では作れなさそうだ。。。

そんなゲームオーバーで泣きたくなるような状況でも、なんとかテスト環境を構築する方法を模索していた。Undef関数をヘッダファイルから自動生成するツールがどうしても欲しかった。そこで、いろいろと探した所、"CMock"がよさそうだったので、今日は遊んでみた。

CMockとは

CMockとは、C言語用のモッキングフレームワーク。

Throw The Switch! – White Papers – CMock Intro

 

 

以下、概要を日本語訳。

CMock はヘッダファイルからモックインターフェイスを生成する小粋なツールです。CMockによって依存関係のあるモジュールをより簡単にUnitTestすることができます。

int DoesSomething(int a, int b);

…自動的にDoesSomething関数が生成され、実際のDoesSomething関数の代わりにリンクすることができます。このモック化されたものをつかうことによって、期待したデータをモジュールが受け取ったかを検証することができ、モジュールからどんなデータでも望むように返すことができます。あなたが欲しいエラーを返すことだって、もっともっと・・・隣接する実際の最新モジュールの振る舞いはなんだって作り出し、瞬時にしてあなたは以下のような力を得るのです。

"オレは作った出来立てのモジュールのあらゆる細部をコントロールし、検証できるのだ!"

このことをより簡単にするために、CMockでは以下のような関数郡も用意しています。なのでテストごとに、その生成されたDoesSomething関数に、どのように振る舞うのかをきけばよいのです。

void DoesSomething_ExpectAndReturn(int a, int b, int toReturn);
void DoesSomething_ExpectAndThrow(int a, int b, iEXCEPTION_T error);
void DoesSomething_StubWithCallback(CMOCK_DoesSomething_CALLBACK YourCallback);
void DoesSomething_IgnoreAndReturn(int toReturn);

これらの関数を次々に重ねあわせていくことで、あなたがなにを検証したいのかを意識させます。こんなふうに、

test_CallsDoesSomething_ShouldDoJustThat(void)
{
    DoesSomething_ExpectAndReturn(1,2,3);
   
DoesSomething_ExpectAndReturn(4,5,6);
   
DoesSomething_ExpectAndThrow(7,8, STATUS_ERROR_OOPS);

    CallsDoesSomething( );
}

このテストはCallsDoesSomethingを呼びます。これが、われわれがテストしたい関数です。この関数がDoesSomethngを3回呼ぶことを期待しています。初めは、DoesSomething(1,2)で呼ばれることを確かに確認し、魔法のように3を返します。二回目は、DoesSomething(4,5)で呼ばれることを確認し、6を返します。3回めはDoesSomething(7,8)で呼ばれて値の代わりにエラーを投げます。もしCallsDoesSomethingがこの通りでないならば、テストは失敗します。DoesSomethingを呼ばなくても、呼びすぎても、また間違った引数でよんでも、間違った順番で読んでも、テストは失敗します。

CMockはUnityをベースにしています。そしてそれは、全ての内部でのテストに使われています。CMockは全ての作業でRubyを使います(version 1.8.6 – 1.9.2)

CMockのダウンロード

CMockのダウンロードは以下のサイトから。

CMock プロジェクト日本語トップページ – SourceForge.JP

githubでも公開されている。

$ git clone git://github.com/ThrowTheSwitch/CMock.git .

(補足)git cloneだと、vendor配下にあるunityとc_expectionがダウンロードできなかった。それぞれを追加で持ってくる。

$ git clone git://github.com/ThrowTheSwitch/Unity.git vendor/unity
$ git clone git://github.com/ThrowTheSwitch/CException.gitvendor/c_exception/ vendor/c_expeption

CMockをとりあえず動かしてみる

事前にRuby と rakeをインストールしておくことが必要。

examples配下で実行

とりあえず、ちゃんとダウンロードできたかどうかを確認するために、examples配下に移動して以下を実行。(mocksとbuildディレクトリをつくっておかないとエラーした(´・_・`))

$ cd examples/
$ mkdir mocks
$ mkdir build
$ rake

それっぽいモック生成メッセージとテストが走ってやや感動するはず。最後までテストが通らなかったけれども、とりあえずよしとしよう。

cmock配下で実行

つづいて、cmockインストールディレクトリでrakeを実行。

$ rake
rake aborted!
cannot load such file — rspec/core/rake_task

rspecがないよといわれるのでrspecをインストールする。

$ gem install rspec

再度rakeを実行すると以下のエラー。

/usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:in `require’: cannot load such file — hardmock (LoadError)

hardmockをインストールする。

$ gem install hardmock

再度rakeを実行て、それっぽいテストが動作した。ヤタ───v(-∀-)v───♪

CMockの使い方

これも、原文をそのまま日本語訳してしまう。

CMockはRubyスクリプトとクラスです。なので、コマンドラインから直接リヨ出来ます。また、自分のスクリプトやrakeファイルにインクルードできます。

コマンドラインからモックする

CMockを解凍したら、’lib’ディレクトリ内にCMock.rbがある。これが動作させるものです。モックするためには、ヘッダファイルのリストが必要です。それとともに、より詳細な構成をしていするためには、オブションとしてyamlファイルが必要です。(configration optionの章を参照)

たとえば、これは構成をMyConfig.ymlで指定して、3つのモックを生成する。

ruby cmock.rb -oMyConfig.yml super.h duper.h awesome.h

これは、デフォルト構成で2つのモックを生成する。

ruby cmock.rb ../mocking/stuff/is/fun.h ../try/it/yourself.h

スクリプトやRakeからモックする

CMockはスクリプトやRakeファイルから直接利用できる。cmock.rbをインクルードして、CMockの実装を生成する。実装を生成したら、3つのうちのいずれかの方法で初期化する。

なにも指定しなければ、デフォルトの設定で動作させることになる。
cmock = CMock.new

YAMLファイルを指定することで、好きな構成オプションを指定できる。
cmock = CMock.new(‘../MyConfig.yml’)

例外として、オプション指定可能。
cmock = Cmock.new(:plugins => [:cexception, :ignore], :mock_path => ‘my/mocks/’)

既存コードにcmockを組み込んで実行する

ここからが本題。一つのファイル(モジュール)をテストしたい時、他のファイルが存在しなくてもテストを実行するために、Undef関数をcmockで自動生成したい。

cmockと同じgithub上にあった、以下のサンプルを利用する。

git clone git://github.com/ThrowTheSwitch/CMockExample.git .

src/LedControl.cをテストしたいとする。普通にコンパイルしようとすると、当然怒られる。

$ gcc src/LedControl.c
/tmp/ccsR1nMW.o:LedControl.c:(.text+0x14): undefined reference to `_GPIO_SetPin’
/tmp/ccsR1nMW.o:LedControl.c:(.text+0x28): undefined reference to `_GPIO_SetPin’
/tmp/ccsR1nMW.o:LedControl.c:(.text+0x68): undefined reference to `_GPIO_ClearPin’
/tmp/ccsR1nMW.o:LedControl.c:(.text+0x7c): undefined reference to `_GPIO_ClearPin’
/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../libcygwin.a(libcmain.o): In function `main’:
/usr/src/debug/cygwin-1.7.17-1/winsup/cygwin/lib/libcmain.c:39: undefined reference to
`_WinMain@16′
collect2: ld はステータス 1 で終了しました

モックを生成する。モックを生成するためのディレクトリを作成して、コマンド実行。

$ mkdir mocks
$ ruby ../lib/cmock.rb src/Gpio.h src/main.h src/System.h
Creating mock for Gpio…
Creating mock for main…
Creating mock for System…
$ ls mocks/
MockGpio.c  MockGpio.h  Mockmain.c  Mockmain.h  MockSystem.c  MockSystem.h

おお、感動的だ。゚(●’ω’o)゚。

ココからが独自カスタマイズ。必要な部品をunityプロジェクト、cmockプロジェクトからカレントディレクトリにかき集めてくる。

  • unity/src/unity.c  unity.h unity_internals.h
  • unity/auto/*
  • cmock/lib/*
  • cmock/config/*
  • cmock/src/cmock.c cmock.h

rakeをよく知らんので、Makefileもちょろっと書いてmakeしてみる。Makefileって自動生成できないのかな。。

TARGET_BASE = LedControl
TARGET_EXTENSION=.exe
TARGET = $(TARGET_BASE)$(TARGET_EXTENSION)

# Unity Files
SRC_FILES   = src/unity.c

# CMock Files
SRC_FILES  += src/cmock.c

# Source Files
SRC_FILES += src/LedControl.c
SRC_FILES += test/test_LedControl.c
SRC_FILES += build/test_LedControl_Runner.c

SRC_FILES += mocks/mock_Gpio.c
SRC_FILES += mocks/mock_main.c
SRC_FILES += mocks/mock_System.c

INC_DIRS  =-Isrc
INC_DIRS +=-Imocks

SYMBOLS=-DTEST -DUNITY_SUPPORT_64

CLEANUP = rm -f build/*.o ; rm -f $(TARGET); mkdir -p build

all: clean default

default:
        ruby auto/generate_test_runner.rb test/test_LedControl.c build/test_LedControl_Runner.c
        ruby lib/cmock.rb src/Gpio.h
        ruby lib/cmock.rb src/main.h
        ruby lib/cmock.rb src/System.h
        $(C_COMPILER) $(INC_DIRS) $(SYMBOLS) $(SRC_FILES) -o $(TARGET)
        ./$(TARGET)

clean:
        $(CLEANUP)

このままだと、mocks/Mock****というモックオブジェクトが生成される。test_***と結合しようとするとエラーしたので、lib/CMockConfigでmock_prefixを変更する。

:mock_prefix => ‘mock_’,

再度実行してみる。

$ make

rm -f build/*.o ; rm -f LedControl.exe  ; mkdir -p build

ruby auto/generate_test_runner.rb test/test_LedControl.c build/test_LedControl_Runner.c

ruby lib/cmock.rb src/Gpio.h

Creating mock for Gpio…

ruby lib/cmock.rb src/main.h

Creating mock for main…

ruby lib/cmock.rb src/System.h

Creating mock for System…

gcc  -Isrc  -Imocks  -DTEST -DUNITY_SUPPORT_64 src/unity.c  src/cmock.c  src/LedControl.c  test/test_LedControl.c  build/test_LedControl_Runner.c mocks/mock_Gpio.c mocks/mock_main.c mocks/mock_System.c -o LedControl.exe

./LedControl.exe

test/test_LedControl.c:13:test_LedControl_TurnLedOn_should_turn_on_GPIO_pin_1_when_turning_on_the_red_LED:PASS

test/test_LedControl.c:22:test_LedControl_TurnLedOn_should_turn_on_GPIO_pin_2_when_turning_on_the_blue_LED:PASS

test/test_LedControl.c:31:test_LedControl_TurnLedOff_should_turn_off_GPIO_pin_1_when_turning_off_the_red_LED:PASS

test/test_LedControl.c:40:test_LedControl_TurnLedOff_should_turn_off_GPIO_pin_2_when_turning_off_the_blue_LED:PASS

———————–

4 Tests 0 Failures 0 Ignored

OK

。゚(●’ω’o)゚。おお、感動的だ。゚(●’ω’o)゚。

果たして、このツールは泥沼スパゲッティコードを断ち切る勇者となるか、使えない愚者になるか。まだまだ評価が必要そうだ。

これさえあれば、どんなに依存関係があって、Undef関数だらけなコンパイルエラーを攻略できそうだ。たとえそれが、10000行くらいのソースコードであっても・・・orz??

ああ、無情。でもガンバルじゃん。

おまけ  書籍の紹介

Embedded Testing with Unity and CMock

UnityとCMockの使い方について、電子書籍とペーパーブックが出ている。6ドルくらい。

Embedded Testing with Unity and CMock by Mark VanderVoord (eBook) – Lulu


内容はとても異色だ。まず、ページが正方形なのが、なんかおかしい。物語形式で話が進んでいく。途中に可愛い?!マンガチックなイラストがたくさんでてくる。文学表現が初めの方はおおくて、知らない単語が多かったりした。前半がUnityの話、後半がCMockの話。

UnityとCMockの使い方が分かる本『Embedded Testing with Unity and CMock』を読んだ | Futurismo

test driven development for embedded c

UnityおよびCMockについては、以下の書籍でも話題にでている。

(電子書籍はここから)

http://pragprog.com/book/jgade/test-driven-development-for-embedded-c

Unityはけっこうベージが割かれているけれども、CMockについては2ページくらい。どちらかというと、Mockを自力で作成するためのアイデアに役に立つ。

そろそろ『test driven development for embedded c』について書いてみる | Futurismo