CocoaのUnitTest
これまでの職歴では大規模開発中心で大抵は発注元の大手ベンダーがチームを作ってテストをすることが多くて単体テストってあまり身近なものじゃなかったんだけれど、今は大規模開発なんて無縁だし、僕が書いたコードをデバグしてくれるのは僕一人という状況だから開発サイクルにうまくテストを組み込むのは必須事項なんです。それで、Cocoaはまだ趣味のレベルなんだけどいろんなテストフレームワークに触れるのも今後大事だと思ったので勉強してみました。
CocoaのテストフレームワークはSenTestingKit.Frameworksが用意されていてこれを使います。具体的にはプロジェクトにUnit Test Bundleを組み込むとビルドフェーズでテストが自動化されます。また、ドライバ作成用にはSenTestCaseというクラスが用意されているのでクラスfooをテストするにはこのSenTestCase(を継承した)クラスからfooのメソッドを呼び出してテストを実行することになります。
自作したpasswdCacheというアプリケーションで使っている暗号化/複合化のメソッドのテストを実際にやってみました。
まず、プロジェクトにテスト用ターゲット(もちろんUnit Test Bundle)を追加しターゲットインスペクタの「一般」タブから「+」ボタンで依存するアプリケーションを追加します。通常はテストしたいクラスを利用するアプリケーションになります。次に「ビルド」タブの検索フィールドにbundleをタイプします。いくつかのbundle関連項目が表示されますがこのうち「バンドルローダー」と「テストホスト」を設定します。
テストコードではテスト対象となるクラスのメソッドや変数を定義しませんがこれらはこのバンドルローダーで指定したアプリケーションから動的に参照されます。これによって、テストモジュールのためにわざわざテスト対象となるクラスをコンパイルする必要はなくなるわけです。
実際の指定は変数を使って$(BUILT_PRODUCTS_DIR)/passwdCache.app/Contents/MacOS/passwdCacheをバンドルローダーに、テストホストにはバンドルローダーそのものを指定するので$(BUNDLE_LOADER)を指定します。これで準備は完了。次はテストドライバを作成します。
「新規ファイル...」から「Objective-C test case class」を選択し適当な名前をつけてテストターゲットへ追加します。SenTestCaseクラスを継承したテストドライバが生成されます。
#import <SenTestingKit/SenTestingKit.h>
@interface PBUtilitiesTest : SenTestCase {
NSArray* testData;
}
@end後は、テストケースを実装するだけ。テストケースの実装は、
- testで始まるメソッドに実装する
- パラメータはとらない
- 戻り値はなし(void)
のルールで書けば後は何でもOK。ただし、テストの準備と後始末メソッドのsetUp/tearDownは例外となります。
- (void)setUp
{
testData = [NSArray arrayWithObjects:@"mypassword",@"yourpassword",nil];
}
- (void)testEncDec
{
id indata;
NSEnumerator* enumerator = [testData objectEnumerator];
while((indata=[enumerator nextObject]) != nil)
{
NSData* temp = [PBUtilities encBlowfish:indata];
NSString* odata = [PBUtilities decBlowfish:temp];
STAssertEqualObjects(indata,odata,@"odata shuld be equal indata");
}
}
- (void)testMustNil
{
STAssertNil([PBUtilities encBlowfish:@""],@"msut be return nil");
}
- (void)testDecWithPassPhrase
{
NSData* temp = [PBUtilities encBlowfish:[testData objectAtIndex:0]];
NSString* odata = [PBUtilities decBlowfishWithPhrase:temp phrase:@"wrong_phrase"];
STAssertNotNil(odata,@"must be empty string,NOT nil");
STAssertEqualObjects(@"",odata,@"must be return empty string");
}ここでは暗号化したデータを複合して正しく元データを復元できるかテストしています。STAssertXXXXはSenTestCaseクラスで使えるマクロで、他にも
- STAssertNil(a1, description, ...)
- a1がnilであることをテストする
- STAssertNotNil(a1, description, ...)
- a1がnilでないことをテストする
- STAssertTrue(expression, description, ...)
- expressionがtrueであることをテストする
- STAssertFalse(expression, description, ...)
- expressionがfalseであることをテストする
- STAssertEqualObjects(a1, a2, description, ...)
- a1オブジェクトとa2オブジェクトが等しいことをテストする
- STAssertEquals(a1, a2, description, ...)
- a1とa2が等しいことをテストする
- STAssertEqualsWithAccuracy(left, right, accuracy, description, ...)
- leftとrightが指定した精度で一致するかテストする
- STAssertThrows(expression, description, ...)
- expressionが例外をスローすることをテストする
- STAssertThrowsSpecific(expression, specificException, description, ...)
- expressionがspecificExceptionをスローすることをテストする
- STAssertThrowsSpecificNamed(expr, specificException, aName, description, ...)
- exprがspecificExceptionのaNmaeをスローすることをテストする
- STAssertNoThrow(expression, description, ...)
- expressionが例外をスローしないことをテストする
- STAssertNoThrowSpecific(expression, specificException, description, ...)
- expressionがspecifixExceptionをスローしないことをテストする
- STAssertNoThrowSpecificNamed(expr, specificException, aName, description, ...)
- exprがspecificExceptionのaNameをスローしないことをテストする
- STAssertTrueNoThrow(expression, description, ...)
- expressionがtrueで例外もスローしないことをテストする
- STAssertFalseNoThrow(expression, description, ...)
- expressionがfalseで例外もスローしないことをテストする
- STFail(description, ...)
- 常にテスト不合格
など多くのマクロが用意されています。もちろん、自前でAssertを書いてもOKでその場合は最後のマクロSTFailとif文などで処理すると良いと思います。
通しでやってみてターゲットのバンドルセッティングのところがCocoa特異の流儀になるのでちょっとわかりずらかったです。最初はせっせとテストターゲットに対象クラスのコンパイルを追加していました。ビルドフェーズの最後の部分で${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTestsを実行して実際のテストをするところはmake testみたいなものと考えればまあそんなに違和感はなかったですね。










最近のコメント
38 weeks 4 days ago
47 weeks 3 days ago
47 weeks 4 days ago
47 weeks 4 days ago
48 weeks 5 days ago
48 weeks 5 days ago
49 weeks 4 days ago
50 weeks 13 hours ago
50 weeks 5 days ago
50 weeks 6 days ago