Tag Archives: Testing

CoreDataと違い、直接SQLiteを扱うためのライブラリであるFMDBを扱ったテストのセットアップ例を書いてみました。

テストフレームワークにKiwiを使っていますが、どのテストフレームワークもsetup/teardownは持ってるので同じことができます。

SQLManagerSpec.m がテストファイルです。

#define DATA_BASE_FILE_PATH @"/tmp/tmp.sqlite"

@interface SQLManagerSpec : KWSpec

@end

@implementation SQLManagerSpec

+ (void)buildExampleGroups {
    describe(@"SQLManager", ^{
        __block FMDatabase *fmDatabase;
        beforeEach(^{
            fmDatabase = [self database];
            [fmDatabase open];
        });
        afterEach(^{
            [fmDatabase close];
            [self cleanup];
        });
    });
}

+ (FMDatabase *)database {
    FMDatabase *fmDatabase = [FMDatabase databaseWithPath:DATA_BASE_FILE_PATH];
    return fmDatabase;
}

+ (void)cleanup {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:DATA_BASE_FILE_PATH]) {
        return;
    }
    NSError *error = nil;
    [fileManager removeItemAtPath:DATA_BASE_FILE_PATH error:&error];
    if (error) {
        NSLog(@"%@", [error localizedDescription]);
    }
}
@end

テストの中身は省略してますが、beforeEach/afterEachが毎回テストの前後で実行される処理ですね。

beforeEachでデータベースファイルを開いて、afterEachでデータベースを閉じてデータベースを削除しています。
CoreDataのようにメモリストアを使う方法がわかればそちらでもいい気がします。(FMDB初めて使ったのでよくわからなかった)

 DATA_BASE_FILE_PATHを絶対パスにしているのは、ファイル名だけだと多分ロジックテストの制約に引っかかったからだと思います。

CoreDataだと以下の記事を参考にすれば、同じようなことができます

MagicalRecordを使った場合は以下のような感じです。

データベース周りは比較的テストを書きやすい部分なので積極的にテストを書いていくといいと思います。

テストをコマンドラインから実行する場合はxctoolがお勧めです。
azu/FMDB_test_setup · GitHub にもConfiguration (.xctool-args)が置いてあるので、xctool testとコマンドを叩けばCLIでテストが実行出来ます。
(まだCocoaPodsを完全にはサポートしてないので、ちょっと挙動おかしいですが Twitter / CocoaPods: ℹ xctool doesn’t support … )

 

OCUnit系のTest Bundleを使ったテストをコマンドラインから実行する方法について簡潔に書いてみます。
(Xcode 4.6~) 

サンプルとして以下のプロジェクトをコマンドラインからテストしてみます。

$ make test でテストが走るようにMakefileを書きます。

Makefileの中身はすごく単純で、以下のようになっています。

test:
	osascript -e 'tell app "iPhone Simulator" to quit'
	xcodebuild -workspace notify-changeLog.xcworkspace -scheme Tests -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES clean build

1行目で、iPhoneシミュレーターをKillしています。

osascript -e 'tell app "iPhone Simulator" to quit'

2行目でプロジェクトをビルドしています。

xcodebuild -workspace notify-changeLog.xcworkspace -scheme Tests -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES clean build

notify-changeLog ではCocoaPodsを使ってOCHamcrestなどのTest Matcherをインストールしているため、-workspace notify-changeLog.xcworkspaceでワークスペースファイルを指定しています。
通常のプロジェクトの場合は、-project NAMEを指定するか、無指定でも勝手に選んでくれるようです。

オプションの詳細は

$ xcodebuild -h

を見るといいです。

-scheme Tests

でTestを実行するSchemeの名前を渡しています。

これは、Cmd+Uでテストを実行できるようにしたSchemeであればいいので、名前は適当に合わせれば問題ないです。

Notify changeLog xcworkspace  notify changeLog xcodeproj 2013 04 08 18 10 01<

 
-sdk iphonesimulator 

は、なぜか付けないと後述するTEST_AFTER_BUILDが機能しなかったのでつけておく必要がありそうです。

次に環境変数の

ONLY_ACTIVE_ARCH=NO
TEST_AFTER_BUILD=YES

についてです。

Debugでビルドする場合は、ONLY_ACTIVE_ARCH=NOにしないとビルドエラーになってしまいます。
Test BundleのTargetからONLY_ACTIVE_ARCHを設定する場合でも同じ効果になります。

TEST_AFTER_BUILD=YES がこの話のメインとなるもので、ビルドするときTest Bundleのテストも実行するというオプションになっています。
これを設定しておくことで、ビルドするときにテストも実行することができます。

最後に、clean buildでビルドしています(TEST_AFTER_BUILD=YESならテストも走る)

clean build

これで、make test でコマンドラインからテストをとりあえず実行できると思います。

workspaceの指定とかは自動化できると思うので、その辺は

などを参考にすればいいと思います。

また、Xcode4.5ではTEST_AFTER_BUILD=YESが機能しなくてコマンドラインからテストが出来ないというバグみたいなのがあったので注意。

参考

iOSアプリのアプリ内課金をテストするときに見ておくべき事をメモした内容です。

アプリ内課金を試す場合は、申請する前にiTunes Connectでアイテムを登録しておく必要があります。

まず最初に、テスト用のiTunesアカウントを作成しておきます。

  1. iTunes ConnectのManage Usersへ
  2. Select User TypeでTest Userを選択
  3. Add New Userから新規ユーザーを作成する。

ここで作成したテストアカウントを使ってテストを行います。
テストは実機でしか行えないので、先に普段使っているiTunesアカウントは、App Storeからログアウトしておいた方でいいです。
(アプリ内からiTunesアカウントのログアウトはできないため)
またJailbreakした端末ではテストができないようになっているので、普通の実機を使ってテストを行う必要があります。
(In-App Purchase で、プロダクトID が invalid になる場合の対処方法 – Over&Out その後)

以下、チェックする所を箇条書きにしたもの

  • 課金を行うページにiTunes アカウントを使用する旨のメッセージが表示されているか?
    メールアドレスとパスワードを使って課金アイテムを購入するが、
    その時に使うアカウントがiTunes Storeのアカウントで有ることを示すメッセージがあったほうがいい。
  • 電波がないときに購入ボタンを押した場合エラーになるか?
    通信エラーであるというメッセージがでるか?
    機内モードにして試すのが楽な方法
  • 機能制限で”App内での購入”が許可されてない場合に
    機能制限に関するエラーメッセージがでるか?
    “設定 > 一般 > 機能制限で[App内での購入]をONにしてください” のようなエラーメッセージがでると丁寧
  • 購入が成功したかが分かるか?
    購入成功のメッセージとかがあると分かりやすい
  • 購入ボタンを連打できたりしないか?
    単純に実装してると購入リクエストが複数飛んでややこしくなるので、
    モーダルなローディングメッセージやボタンを複数押せないような作りになっていたほうがいい。
  • ローディング画面などがある場合
    課金処理後にそのローディングメッセージがちゃんと消えるかどうか?
    購入(失敗/成功)、通信エラー、リストア(成功/失敗)等色々あるので、ちゃんとローディングメッセージが消えるかを確認する
  • Consumable(消費型)プロダクトの課金アイテムである場合、何度も購入できるか?
    消費型はアップルが購入情報を保存してくれない。
  • Non-Consumable(非消費型)プロダクトの課金アイテムである場合、リストア処理ができるか?
    2012年9月3日現在だと、リストア処理はちゃんとUIとして用意しないとリジェクトされる傾向になってる
    iOSアプリ内課金、リストア機能なしはなぜリジェクトされるのか: 誰でもできる電子書籍iPhoneアプリ開発講座
  • 購入ボタンを押したときに何も反応がない
    上の方のと同じような事だけど、フィードバックのないボタン等が存在しないかを確認
  • 購入中に通信が切れた場合
    テストするのは難しいけど、一応留意しておく。

月額課金の場合(Auto-Renewable Subscriptions)

基本的には消費型とやることは同じです。

月額課金の場合、Sandbox内では実際の時間と異なる時間になってることに注意します。
例えば、1ヶ月ごとの更新となる課金の場合、Sandboxでは5分で期限切れとなるため、リストアのテストをする場合
課金してからすぐ試さないと期限切れとなります。(レシートを検証すると21006を返す)

本番環境とサンドボックス環境では以下のような期間の対応になってます。

本番環境 サンドボックス環境
1週間 3分
1ヶ月 5分
2ヶ月 10分
3ヶ月 15分
6ヶ月 30分
1年 1時間

アプリ内課金のテストは自動的にSandbox(StoreKitアラート集 – StoreKitDemoで出るようにEnviroment: Sandboxとなる)で行われますが、
この Sandbox で挙動が結構怪しくなったり(具体的にはリストアで返ってくるものがおかしいなど)した場合は、
アプリを一度削除してみる事や、テストユーザーのアカウントを作りなおして見るなどすると改善することがあります。

他参考

実装については以下等が参考になる

前回のcedarに引き続き、BDDスタイルのiOSテスティングフレームワークのKiwiについての紹介。

Kiwiについては以下のサイトがとてもよくまとめてくれているので、そこを見るだけでもいい気がしますが、
最近になってKiwiの導入方法等も変わったので、新しくなったKiwiの導入方法についてです。
どちらにしても下記のサイトは参考になるのでKiwiを使うには読んでおくべきでしょう。

公式にもインストール方法のページGuide: Up and Running with Kiwi – GitHubがあるので、そちらも読むといいです。
とても細かくかいてあります。(絶対パスで指定したりする部分があるのが微妙ですが)

今回作成したサンプルプロジェクトは以下に置いてあります

まずはOCUnit環境の構築から始めます。(既にその環境ができてるなら飛ばしても大丈夫です)
ちょっと詳しくは知りませんが、OCUnit環境の上にKiwiの環境を重ねて作るというようなイメージで。

プロジェクトの作成

NewImage

既にできてるプロジェクトに追加する場合は、途中からOCUnitを追加するのと同じでtargetにTest bundleを加えてやればいい。とりあえずここまでやればOCUnitのテストはできてる状態。

NewImage

手動でターゲットにTest Bundleから追加した時はSchemeにそのターゲットのSchemeが追加されるが、
メインとなってるSchemeを選んでいるときにもTestを実行できるようにするには、メインSchemeの編集で、
Testの部分にTestターゲットを関連付けすればメインSchemeからTestを実行できるようになる。(プロジェクト作成でInclude Unit Testした場合はこれが自動で行われている)

NewImage

ここからがKiwiの導入

まずはKiwiを入れたいプロジェクトがあるディレクトリまでコンソールで移動しておく

➜  KiwiSample git:(master) ls
KiwiSample           KiwiSample.xcodeproj KiwiSampleTests
➜  KiwiSample git:(master) git submodule add https://github.com/allending/Kiwi.git Kiwi
Cloning into Kiwi...
remote: Counting objects: 1305, done.
remote: Compressing objects: 100% (453/453), done.
remote: Total 1305 (delta 821), reused 1288 (delta 807)
Receiving objects: 100% (1305/1305), 12.59 MiB | 1.19 MiB/s, done.
Resolving deltas: 100% (821/821), done.
➜  KiwiSample git:(master) ✗ ls                                                                                                                              [~/Dropbox/workspace/iOS/project/KiwiSample]
Kiwi                 KiwiSample           KiwiSample.xcodeproj KiwiSampleTests
➜  KiwiSample git:(master) ✗ git submodule update --init # Kiwiをアップデートしておく                                                                                         [~/Dropbox/workspace/iOS/project/KiwiSample]
Submodule 'Kiwi' (https://github.com/allending/Kiwi.git) registered for path 'Kiwi'
➜  KiwiSample git:(master) ✗ open Kiwi                                                                                                                       [~/Dropbox/workspace/iOS/project/KiwiSample]

このようにして、Kiwiをプロジェクトのgitサブモジュールとして追加しておきます。(まだプロジェクト自体には入っていない)こうしておけば、以降はgit submodule updateとかでKiwiがアップデートできるようになると思います。

そして、最後にopen KiwiでKiwiディレクトリを開くと、Kiwi.xcodeprojがあるので、それをプロジェクトに追加します。

NewImage

 

Kiwiの導入 – 設定

まずはのBuild Phasesタブ設定から

そして、プロジェクトに作ったテストターゲット(画面ではKiwiSampleTest)のBuild Phases -> Target Dependiciesから先ほど追加したKiwiを依存関係に加えておきます。

NewImage

NewImage

次に、同じくテストターゲットのBuild Phase->Link Binary With LibraiesからWorkspaceにあるlibKiwi.aを追加します。

NewImage

次はBuild Settingタブ

テストターゲットのBuild Setting -> “User Header Search Paths”に、追加したKiwi.xcodeproj内のソースを参照するように、Kiwi/Kiwi とパスを追加します。

NewImage

最後にテストターゲットのBuild Setting -> “Other Linker Flags“におなじみの-all_loadを追加します。

NewImage

これで、Kiwiの準備は完了で後はテストケースを書いていくだけです。
KiwiSampleTests.m という次のようなファイルをテストターゲットに追加していけばテストケースを書いていけます。

#import "Kiwi.h"

SPEC_BEGIN(MathSpec)

describe(@"Math", ^{
    it(@"is pretty cool", ^{
        NSUInteger a = 21;
        NSUInteger b = 21;
        [[theValue(a + b) should] equal:theValue(42)];
    });
});

SPEC_END

Kiwiのいい所はデフォルトでOCUnitと同じようにTestからテストを実行できるようなっている所。

NewImage

KiwiはBlocksを使ったBDDライクな書き方ができようになっていて、この辺はCedarと同じです。(語弊ありますが)
また、Kiwi自体にMockとStubの機能が含まれていたりするようです。

Kiwiについてのドキュメントですが、以前はkiwi-lib.infoがあってそこにまとまっていましたが、今はKiwi WikiというGithub上に集めているみたいなので(まだ未完成なのでドキュメントが行方不明です…)、そちらを参照するのがいいでしょう。

一応まだサイト上には過去のドキュメントが残ってるのでそれを見るのがいいかも。

導入方法の参考

おまけ

自分はAppCodeを普段使っているので、TestをAppCodeで走らせたいと思います。

Edit Configuration から OCUnitとしてターゲット:KiwiSampleTestsを指定します。

NewImage

そして、Configを作ったOCunit(画面だとKiwiTest)に切り替えて実行するとテストが実行できます。
(#import “Kiwi.h”でWarningが出てしまいますが、ちゃんと動作します)
OCUnitをAppCodeで実行するのと同じでグリーン、レッドバーで結果が表示されます。

NewImage

補完とか整形が若干おかしいのが残念ですが、構文自体は結構好きです。

 

cedarJasmineRobolectric 等他の言語でもテスティングフレームワークを色々と作成しているPivotal Labs.によるObjective-C向けのBDDテスティングフレームワークです。

今回は適当な導入方法について

まずはCedar-iOS static frameworkをビルドするために、Githubからcedarをcloneします。

➜  objectiveC  git clone --recursive https://github.com/pivotal/cedar.git

submoduleも含めて持ってきたいので–recursiveオプションでまとめてcloneします。

ダウンロードしたプロジェクトをXCodeで開く

➜  objectiveC  open cedar/Cedar.xcodeproj

NewImage

NewImage

ビルドすると、ProductsディレクトリにiPhoneUniversalのディレクトリがあるので、そこからcedar-iOS.frameworkを取り出しておく。
(イマイチ簡単にProductsディレクトリを開く方法がわからなかったので、cedar.frameworkをShow In Finderするなどして辿った)

プロジェクトに組み込む

GHUnitのようにアプリケーションとして動作させるので、プロジェクト作成時にInclude Unit Testはチェックしなくていい。
適当なプロジェクト名でEmpty Applicationなどを選んでプロジェクトを作成する。

次にテスト用のTargetを追加する。
add Targetから”UISpecs”というような名前をつけたTargetを追加する。
(この時Unit Test Bundleではなくアプリケーションから選ぶ、今回はEmpty Application)

NewImage

Target UISpecsの方に対して、先程ビルドしたcedar-iOS.frameworkを追加する。

NewImage

Target UISpecsのBuild SettingのOther Linker Flagsに対して-ObjC と -all_load を追加する

NewImage

最後にmain.mの書き換えを行う。
CedarApplicationDelegateを見に行くように書き換える(元々あるAppDelegateは消しても問題ない)

#import <UIKit/UIKit.h>
#import <Cedar-iOS/Cedar-iOS.h>

int main(int argc, char *argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    int retVal = UIApplicationMain(argc, argv, nil, @"CedarApplicationDelegate");
    [pool release];
    return retVal;
}

この状態になったら多分Runから起動できるようになる。
GHUnitと同じようにアプリケーションとして起動して、テスト毎にUITableViewで表示するような形になっている。

次はテストケースを書いてみる。
TestSample.mみたいな適当なクラスを追加する。

#import <Cedar-iOS/SpecHelper.h>

SPEC_BEGIN(FooSpecs)

describe(@"Something that shares behavior", ^{
    itShouldBehaveLike(@"a similarly-behaving thing");
});

describe(@"Something else that shares behavior", ^{
    itShouldBehaveLike(@"a similarly-behaving thing");
});

SPEC_END

実行してみると次のようにTableViewにグリーン、レッドが表示される

NewImage

 

これで、アプリケーションテストのために導入する方法は終わりなのだが、
cedarには C++ templatesを利用したMatchersというメソッドの書き方のセットが用意されていて、
それを利用するためにはObjective-C++でテストケースを書けるようにする必要がある。

先ほどの状態のままテストケースの書き方を少し変更するだけで Matchers の機能が利用できるようになる。
まずは、拡張子を.mmにしたTestSample.mmという感じにして次のようなファイルを作成する。

#import <Cedar-iOS/SpecHelper.h>

using namespace Cedar::Matchers;

SPEC_BEGIN(FooSpecs)

describe@"a similarly-behaving thing", ^{
    it(@"should do something common", ^{
        NSString *aString = @"something";
        expect(aString).to(equal(@"something"));
    });
});

describe(@"1 + 2", ^{
    it(@"should be 3", ^{
        1 + 2 should equal(3);
    });
});

SPEC_END

このようにusing namespace Cedar::Matchers;を入れることでMatchersを使ったテストケースを書けるようになる。
1 + 2 should equal(3); のように書けたりするので便利な気がする。

メソッドについては pivotal/cedar – GitHubを見る。

まとめると次のような変更を行えばいい。

  • 拡張子を.mmへ変更
  • using namespace Cedar::Matchers; をSPEC_BEGINの前に入れる

今回書いたサンプルはazu/Cedar-sample – GitHubに置いてあります

ものすごく簡略化した導入方法を紹介したが、Cedarはかなり色々な事ができるように作られてるようなので、今回の内容はごくごく一部な気がします。
OCUnit Logic Tests というように アプリケーションとしてではなくてOCUnitみたいにメニューのTestからテストできるようにもできたり、
標準でJUnit XMLレポートを出力できるなど 色々と奥が深そうです。