今回エンジニアブログを担当する、藤澤です。
早いものでエンジニアブログが始まって一ヶ月半が経ちました。多くの方に見ていただきありがとうございます!
見て頂いてる方からこのようなリクエストをいただきました。
「実際のコードによるiOS のテスト自動化(OCUnit+α)をお願いしたし!」
正直これを聞いて初めて OCUnit の存在を知ったわけですが、赤を緑にする作業に取り憑かれた身として Cocos2d-x と OCUnit (※) でどこまで TDD な開発ができるか試してみました。
※ 中身は SenTestingToolkit というライブラリのようなので検索ワードにはこちらがよいかもです。
準備編
Cocos2d-x のプロジェクトで OCUnit を使うための準備ですが、以下の手順でいけました。
1.Cocos2d-x のプロジェクト作成
まずは普通にプロジェクトを作成します。こちらなどを参考に。
2.テスト用のターゲットを追加
作成したプロジェクトにテスト用のターゲットを追加します。プロジェクトの設定から Add Target で Cocoa Touch Unit Testing Bundle を追加します。
3.テストコードからプロダクトコードを参照するための設定
この状態でプロダクト側のヘッダファイルは見えるのでテストコードを書くことはできますが、実装が解決できないのでビルド時に undefined symbols でエラーになると思います。
対処方法は二つあります。
(1) 実装ファイルをテスト側のターゲットに含める
テスト対象の cpp ファイルを選択し、Target Membership でテストのターゲットにチェックを入れます。
(2) バイナリを直接読み込む
テストターゲット側の Build Settings で以下の項目を設定します。
Bundle Loader = $(BUILT_PRODUCTS_DIR)/sample.app/sample
Test Host = $(BUNDLE_LOADER)
sample の部分は適宜置き換えてください。
また、いずれの場合も、C++ Language Dialect, C++ Standard Library の設定をプロダクト側と同じ値 (Compiler Default) に変更しておいたほうがよいです。プロダクトとテストで挙動が変わって思わぬところでハマることがあります。
4.テストコード / プロダクトコードの作成
ここまできたら後はテストコード 、プロダクトコードを書いていくだけです。
Hoge hoge; STAssertEquals(1, hoge.get(), @"hoge.get() expected %d, but was %d", 1, hoge.get());
xUnit 系のフレームワークを使ったことがあれば特に迷うことはないかと思いますが、注意点として、テストケース用のクラスは New File → Cocoa Touch → Objective-C test case class で追加した後、C++ を扱えるように拡張子を .mm に変更してください。また、OCUnit はあくまでも Objective-C を対象としたフレームワークなので STAssertNil などは使えません。
Hoge hoge; STAssertTrue(hoge != NULL, @"hoge must not null");
のようにする必要があります。
OCMock などのライブラリも使えませんでした。
5.スキーマの設定
テストを書いたらメニューの Product → Test から実行できますが、この状態だと Scheme をいちいち切り替えないといけないので面倒です。そこで Scheme を変更しなくてもテストを実行できるように設定します。
プロダクト側のターゲットの Scheme を選択し、Edit Scheme → Test でテスト用のターゲットを追加してください。なお、実際にテストを実行する際は iPhone Simulator を選択しないと動かないようです。(実機だとテストが走らない)
6.Cocos2d-x ライブラリの参照設定
ここまでで通常の C++ のクラス(いわゆる POCO なクラス)のテストはできていると思います。が、Cocos2d-x のクラスをテストコードでも使いたいという場合はさらに一手間必要になります。
テストターゲットの Build Settings に以下の設定をすればいけました。
Header Search Paths に プロダクト側の設定値をコピー
Preprocessor Macros に CC_TARGET_OS_IPHONE を追加
実践編
さて、これで TDD を始める準備はできましたが、実際のコードで Unit Test を書こうと思ったら UI まわりや DB、ネットワークなどのリソースアクセス処理でつまづくことが多いかと思います。そこで MVC や MVVM などのパターンを適用してロジックを分離してやるわけですが、ゲーム開発でもこれがうまく適用できるでしょうか。
今回はこんな感じにしてみました。ゲームの制御は Presenter が行ない、View は画面の描画と画面入力を Presenter に引き渡すだけにします。メインループの処理についても View から Presenter を定期的に叩く形にすることで、「Presenter のメソッドの実行前後で Model の値が変わっているか」というようにテストが書きやすくなります。
Model から View へののデータの引渡しについては Observer パターンでプロパティの変更を通知する形にしました(データバインディングっぽく)。こうすることで PropertyChangedEventArgs でプロパティの変更前後の値を渡せるので、描画をアニメーションさせたりするのにも便利かと思います。
今回作ったサンプルコードはこちらに置いてあります。
ゲーム開発というと UI が密に結合していたりリアルタイム性が高かったりと Unit Test は適用しづらいというイメージがありましたが、今回試してみた限りでは設計さえ決まってしまえば意外とさくさくテストを書くことができました。
今回の記事が TDD を始める参考になれば幸いです。