XCTestによるユニットテスト導入まとめ | GCREST_engineerのブログ

GCREST_engineerのブログ

ブログの説明を入力します。

ユニットテスト、言わずもがな大事ですよね。
サーバ側だけでなくゲームアプリのクライアント側も同様に必要と考えます。
リリース後も継続的に運用されるものであればなおさらです。

設定について

cocos2dxを使用したプロジェクトで、XCTestでユニットテストを行うための設定方法をまとめました。
設定時の環境はXcode5.1.1、MacOSX 10.9.4です。

1. テスト用ターゲットを作成
2. Build Settingsの各種設定
  ・以下の設定値をプロダクト側から全てコピー
    ・Header Search Paths
    ・Preprocessor Macros
    ・C++ Language Dialect
    ・C++ Standard Library
  ・cocos2d_libsのcode generation hiddenをNOに
   (外部のプロジェクトをライブラリとしてインポートしている場合、この設定が必要となります)

新規にテスト用クラスを作成するときは、

・「XCTestCase」を継承する
・「Targets」でテスト用ターゲットにチェックをつける
  (作成したクラスファイルをテスト用ターゲットに加えるという意味)
・作成後、ファイルの拡張子を.mから.mmに変更する

あとはテストを書いて、command+Uでテスト実行です。

書き方の例

ゲームアプリの場合、振る舞いは動きや表示の部分によるものが多くテストしづらいところも多いですが、費用対効果を考慮して部分的に適用していくのがいいと思います。
APIの通信部分やロジック部分はやっておいたほうがいいでしょう。

通信部分のテストはこんな感じです。あくまでサンプルですが。

GameConnect.h


#include "cocos2d.h"
#include "network/HttpRequest.h"
#include "network/HttpClient.h"

class GameConnect
{
public:
GameConnect();
virtual ~GameConnect() {}

virtual void send(const std::string& param);
virtual void onReceive(cocos2d::network::HttpClient* sender
, cocos2d::network::HttpResponse* response);

void setReceiveCallback(const std::function<void()>& callback);
std::string getHoge();

private:
cocos2d::network::HttpRequest* _request;
std::string _hoge;
std::function<void()> _receiveCallback;
};


GameConnect.cpp


#include "GameConnect.h"
#include "spine/json.h"

USING_NS_CC;
using namespace cocos2d::network;

GameConnect::GameConnect()
: _request(nullptr)
, _hoge("foo")
{
}

void GameConnect::setReceiveCallback(const std::function<void()>& callback)
{
_receiveCallback = callback;
}

std::string GameConnect::getHoge()
{
return _hoge;
}

void GameConnect::send(const std::string& param)
{
std::string url(StringUtils::format("http://httpbin.org/get?hoge=%s", param.c_str()));
_request = new HttpRequest();
_request->setUrl(url.c_str());
_request->setRequestType(HttpRequest::Type::GET);
_request->setResponseCallback(CC_CALLBACK_2(GameConnect::onReceive, this));
HttpClient::getInstance()->send(_request);
}

void GameConnect::onReceive(HttpClient* sender, HttpResponse* response)
{
std::vector* data = response->getResponseData();
std::string result(data->begin(), data->end());
Json* json = Json_create(result.c_str());
if (json) {
Json* args = Json_getItem(json, "args");
if (args) {
_hoge = std::string(Json_getString(args, "hoge", ""));
}
Json_dispose(json);
}
CC_SAFE_RELEASE_NULL(_request);
_receiveCallback();
}


GameConnectTests.mm


#import <XCTest/XCTest.h>
#include "cocos2d.h"
#include "GameConnect.h"

USING_NS_CC;
using namespace cocos2d::network;

@interface GameConnectTests : XCTestCase
{
GameConnect* _sut;
}
@end

@implementation GameConnectTests

- (void)setUp
{
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
_sut = new GameConnect();
}

- (void)tearDown
{
// Put teardown code here. This method is called after the invocation of each test method in the class.
delete _sut;
[super tearDown];
}

- (void)testSend
{
bool status = false;

// 一応初期状態を確認
XCTAssertEqual(_sut->getHoge(), "foo");

_sut->setReceiveCallback([&status](){
status = true;
});
_sut->send("fuga");

// 受信してフラグがたつまで待機
while (status == false) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}

// 受信データの検証
XCTAssertEqual(status, true);
XCTAssertEqual(_sut->getHoge(), "fuga");
}

@end


GameConnectがテスト対象のクラスで、GameConnectTestsがテスト用クラスです。
この例ではサンプル用として http://httpbin.org/ というエコーサーバを使っていますが、実際にはここにAPIのURLを記述しパラメータを渡して通信することになります。
Httpで通信をなげて、受信するまで待機、その後受信データを検証します。
このようにして、API通信が行えるか、指定したパラメータに対して意図したレスポンスが得られるかをテストしていきます。

注意点としては通信部分は非同期テストとなるためレスポンスを待機する必要があります。
そのやり方としては上記サンプルのようにフラグを待つ方法だったり、Xcode6であれば非同期テスト用のAPIを使う方法だったり。さらに必要であればNLTHTTPStubServerやGHUnit等も導入を検討した方がいいかもしれません。

ロジック部分に関しては待機する必要はありませんし、拡張子を.mmにすることでC++が使えますので普通にユニットテストを書いていけばいいです。