単体テストを開始しましょう(Objective-C)

この記事では、Xcode 6を使用したObjective-Cでのテストの主題に焦点を当てます。この記事では、標準のテストライブラリとサードパーティのOCMockライブラリについて説明します。 経験豊富な開発者は、このパスに最近乗り出した人にとって、あまり有用な情報をここで見つけられないかもしれません-この記事は、Objective-Cで単体テストを書く上で必要な基本知識を明らかにします。



テストの基本については、こちらにお問い合わせください

単体テストの基本については、 こちらをご覧ください



そして、Objective-Cのフレームワーク内で単体テストの研究を開始します。



ステップ1.基本



新しいiOSプロジェクトを作成しましょう。



スクリーンショット








ご覧のとおり、Xcodeはテスト用のディレクトリDemoUnitTestingTestsとファイルDemoUnitTestingTests.mを作成しました。 ここに見えるもの:



テスト用の標準ライブラリ:



#import <XCTest/XCTest.h>
      
      





DemoUnitTestingTestsクラスがXCTestCaseクラスを継承しているという事実(詳細は説明しません)。



各テストを実行する前に呼び出されるメソッド:



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





各テストの結果の後に呼び出されるメソッド:



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





そして直接2つのテスト。 1つ目はデモテスト、2つ目はパフォーマンステストのデモンストレーションです。



 - (void)testExample { // This is an example of a functional test case. XCTAssert(YES, @"Pass"); } - (void)testPerformanceExample { // This is an example of a performance test case. [self measureBlock:^{ // Put the code you want to measure the time of here. }]; }
      
      





ご覧のとおり、すべてのテストはtestという単語で始まり、テスト用のリストに自動的に含まれます。



ステップ2.情報



いくつかのルールとコメント:

すべてのテストは独立して実行されます

クラス分離を最大化する

テストの名前はその目的を反映する必要があります。

テストの結果はメインコードに影響しないはずです



テストを書くとコードがバグから完全に救われるとは思わないでください 。 おそらくあなたのテストは正しく書かれていません。



ステップ3.テストの調査を開始します



XCTestをテストするために標準ライブラリから次のテスト関数を選択し、それらを使用してテストを記述します。



いつも間違い
 - (void)testAlwaysFailed { /* 1 -   */ XCTFail(@"always failed"); }
      
      







基本型の平等
 - (void)testIsEqualPrimitive { /* 1 - 1 2 - 2  3 -    -       */ int primitive1 = 5; int primitive2 = 5; XCTAssertEqual(primitive1, primitive2, @"(%d) equal to (%d)", primitive1, primitive2); }
      
      







基本型の不等式
 - (void)testIsNotEqualPrimitive { /* 1 - 1 2 - 2  3 -    -       */ int primitive1 = 5; int primitive2 = 6; XCTAssertNotEqual(primitive1, primitive2, @"(%d) not equal to (%d)", primitive1, primitive2); }
      
      







基本型の誤差との平等
 - (void)testIsEqualWithAccuracyPrimitive { /* 1 - 1 2 - 2 3 -     4 -    -       */ float primitive1 = 5.012f; float primitive2 = 5.014f; float accuracy = 0.005; XCTAssertEqualWithAccuracy(primitive1, primitive2, accuracy, @"(%f) equal to (%f) with accuracy %f", primitive1, primitive2, accuracy); }
      
      







基本型エラーを含む不等式
 - (void)testIsNotEqualWithAccuracyPrimitive { /* 1 - 1 2 - 2 3 -     4 -    -       */ float primitive1 = 5.012f; float primitive2 = 5.014f; float accuracy = 0.001; XCTAssertNotEqualWithAccuracy(primitive1, primitive2, accuracy, @"(%f) not equal to (%f) with accuracy %f", primitive1, primitive2, accuracy); }
      
      







BOOL YESを確認
 - (void)testIsTrue { /* 1 - boolean    2 -    -      */ BOOL isTrue = YES; XCTAssertTrue(isTrue); }
      
      







BOOL NOを確認
 - (void)testIsFalse { /* 1 - boolean    2 -    -      */ BOOL isTrue = NO; XCTAssertFalse(isTrue); }
      
      







無チェック
 - (void)testIsNil { /* 1 -   2 -    -      */ id foo = nil; XCTAssertNil(foo, @"pointer:%p", foo); }
      
      







NOT nilを確認してください
 - (void)testIsNotNil { /* 1 -   2 -    -      */ id foo = @""; XCTAssertNotNil(foo); }
      
      







より多くのプリミティブの比較(>)
 - (void)testGreaterPrivitive { /* 1 - 1 2 - 2  3 -    -      */ int privitive1 = 4; int privitive2 = 3; XCTAssertGreaterThan(privitive1, privitive2); }
      
      







(> =)以上のプリミティブの比較
 - (void)testGreaterOrEqualPrivitive { /* 1 - 1 2 - 2  3 -    -      */ int privitive1 = 4; int privitive2 = 4; XCTAssertGreaterThanOrEqual(privitive1, privitive2); }
      
      







less(<)によるプリミティブの比較
 - (void)testLessPrivitive { /* 1 - 1 2 - 2  3 -    -      */ int privitive1 = 3; int privitive2 = 4; XCTAssertLessThan(privitive1, privitive2); }
      
      







プリミティブを(<=)以下で比較する
 - (void)testLessOrEqualPrivitive { /* 1 - 1 2 - 2  3 -    -      */ int privitive1 = 4; int privitive2 = 4; XCTAssertLessThanOrEqual(privitive1, privitive2); }
      
      







例外のスローを確認する
 - (void)testThrowException { /* 1 - //  2 -    -      */ void (^block)() = ^{ @throw [NSException exceptionWithName:NSGenericException reason:@"test throw" userInfo:nil]; }; XCTAssertThrows(block()); }
      
      







例外をスローしないことを確認してください
 - (void)testNoThrowException { /* 1 - //  2 -    -      */ void (^block)() = ^{ }; XCTAssertNoThrow(block()); }
      
      







例外クラスによる例外のスローを確認します
NSExceptionから派生したMyExceptionクラス

 @interface MyException : NSException @end @implementation MyException @end
      
      





 - (void)testThrowExceptionClass { /* 1 - // 2 -    3 -    -      */ void (^block)() = ^{ @throw [MyException exceptionWithName:NSGenericException reason:@"test throw" userInfo:nil]; }; XCTAssertThrowsSpecific(block(), MyException); }
      
      







例外クラスから例外DIFFERENTをスローするためのチェック
NSExceptionから派生したMyExceptionクラス

 @interface MyException : NSException @end @implementation MyException @end
      
      





 - (void)testNoThrowExceptionClass { /* 1 - // 2 -    3 -    -      */ void (^block)() = ^{ @throw [NSException exceptionWithName:NSGenericException reason:@"test throw" userInfo:nil]; }; XCTAssertNoThrowSpecific(block(), MyException); }
      
      







という例外クラスによる例外のスローをチェックします
NSExceptionから派生したMyExceptionクラス

 @interface MyException : NSException @end @implementation MyException @end
      
      





 - (void)testThrowWithNamedExceptionClass { /* 1 - // 2 -   3 -    4 -    -      */ NSString *nameException = @"name expection"; void (^block)() = ^{ @throw [MyException exceptionWithName:nameException reason:@"test throw" userInfo:nil]; }; XCTAssertThrowsSpecificNamed(block(), MyException, nameException); }
      
      







次の名前を持つ例外クラスから例外EXCELLENTをスローすることを確認します
NSExceptionから派生したMyExceptionクラス

 @interface MyException : NSException @end @implementation MyException @end
      
      





 - (void)testNoThrowWithNamedExceptionClass { /* 1 - // 2 -   3 -    4 -    -      */ NSString *nameException = @"name expection"; void (^block)() = ^{ @throw [MyException exceptionWithName:[nameException stringByAppendingString:@"123"] reason:@"test throw" userInfo:nil]; }; XCTAssertNoThrowSpecificNamed(block(), MyException, nameException); }
      
      







オブジェクトの平等
 - (void)testEqualObject { /* 1 -   isEqual 2 -   isEqual  3 -    -      */ id obj1 = @[]; id obj2 = @[]; XCTAssertEqualObjects(obj1, obj2, @"obj1(%@) not equal to obj2(%@))", obj1, obj2); }
      
      







オブジェクトの不等式
 - (void)testNoEqualObject { /* 1 -   isEqual 2 -   isEqual  3 -    -      */ id obj1 = @"name"; id obj2 = @{}; XCTAssertNotEqualObjects(obj1, obj2, @"obj1(%@) not equal to obj2(%@))", obj1, obj2); }
      
      







時間遅延チェック
 - (void)testAsync { /* 1.     (     ) 2.    3.   4.  fulfill     XCTestExpectation    fulfill     -     */ XCTestExpectation *expectation = [self expectationWithDescription:@"block not call"]; NSTimeInterval timeout = 1.0f; [expectation performSelector:@selector(fulfill) withObject:nil afterDelay:0.3f]; [self waitForExpectationsWithTimeout:timeout handler:nil]; }
      
      







ステップ4.依存関係のあるコードのテスト



記事の最も興味深い部分にアプローチしました。 最初の2つのリンクを注意深く読んだ場合、ユニットテストでは依存関係からクラスを分離する必要があることを既に知っています。 すべてを手で書くことができますが、完成したものを取ります。 OCMockライブラリを取得します。



CocoaPodsを使用してインストールします

ポッド「OCMock」、「〜> 3.1.2」


依存関係がある場合にテストを記述する際に最も一般的な問題を考慮し、OCMockライブラリを使用してそれらに対処する方法を確認します。 デモンストレーション用にクラスClassAとClassBを作成しましょう。



別のクラスへの依存
 - (void)testInit { /*  mock   ClassB     init  ClassA ,  classA.classB     ,   mockClassB */ id mockClassB = OCMClassMock([ClassB class]); ClassA *classA = [[ClassA alloc] initWithClassB:mockClassB]; XCTAssertEqual(classA.classB, mockClassB); }
      
      





別のオブジェクトからのデータ
 - (void)testStub { /*  mock   ClassB   stub ,   .      ClassB    */ NSString *expectedInfo = @"info"; id mockClassB = OCMClassMock([ClassB class]); OCMStub([mockClassB info]).andReturn(expectedInfo); NSString *info = [mockClassB info]; XCTAssertEqualObjects(info, expectedInfo); }
      
      





入力に応じた別のオブジェクトからのデータ
 - (void)testStubWithArg { /*  mock   ClassB   stub ,        */ id mockClassB = OCMClassMock([ClassB class]); NSInteger expectedFactorial3 = 6; NSInteger expectedFactorial5 = 120; OCMExpect([mockClassB factorial:3]).andReturn(expectedFactorial3); OCMExpect([mockClassB factorial:5]).andReturn(expectedFactorial5); NSInteger factorial3 = [mockClassB factorial:3]; NSInteger factorial5 = [mockClassB factorial:5]; XCTAssertEqual(factorial3, expectedFactorial3); XCTAssertEqual(factorial5, expectedFactorial5); }
      
      





別のオブジェクトからの通知
 - (void)testNotification { /*  mock   ClassB   stub ,    mock observer        mock         mock observer */ id mockClassB = OCMClassMock([ClassB class]); NSString *notificationName = @"notification name"; NSNotification *notification = [NSNotification notificationWithName:notificationName object:mockClassB]; OCMStub([mockClassB postNotification]).andPost(notification); id mockObserver = OCMObserverMock(); [[NSNotificationCenter defaultCenter] addMockObserver:mockObserver name:notificationName object:mockClassB]; OCMExpect([mockObserver notificationWithName:notificationName object:mockClassB]); [mockClassB postNotification]; OCMVerifyAll(mockObserver); }
      
      







別のオブジェクトのメソッドを呼び出す
上記の例とロジックに違いはありません。 期待を書き、それらが呼び出されたかどうかを確認します

 - (void)testVerifyExpect { /*  mock   ClassB      info  postNotification        mock  */ id mockClassB = OCMClassMock([ClassB class]); OCMExpect([mockClassB info]); OCMExpect([mockClassB postNotification]); [mockClassB info]; [mockClassB postNotification]; OCMVerifyAll(mockClassB); }
      
      







オブジェクトの現在の状態に応じて
 - (void)testPartialMockObject { /*    ClassA,      count.  count  readonly */ id classA = [[ClassA alloc] initWithCount:3]; XCTAssertEqual([classA count], 3); id partialMock = OCMPartialMock(classA); OCMStub([partialMock count]).andReturn(41); XCTAssertEqual([classA count], 41); }
      
      







遅延を伴うブロックオブジェクトの呼び出し
 - (void)testStubWithBlock { /*  mock   ClassB   stub ,       mock   ClassA      ,          */ void (^block)() = [OCMArg checkWithBlock:^BOOL(id value) { return YES; }]; id mockClassB = OCMClassMock([ClassB class]); OCMExpect([mockClassB setBlock:block]); id mockClassA = OCMClassMock([ClassA class]); OCMStub([mockClassA useBlockInClassB]).andDo(^(NSInvocation *invocation) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [mockClassB setBlock:^{ }]; }); }); [mockClassA useBlockInClassB]; OCMVerifyAllWithDelay(mockClassB, 2); }
      
      







ほとんどのタスクでは、これで十分です。 ここで詳細を読むことを提案します



ステップ5.論理テスト実行の構成



テストを実行すると(たとえば、cmd + uの組み合わせで)、メインコードが最初に起動され、そのテストの後にのみ起動されます。 これは悪いです。 多くの場合、メインコードの実行時にプログラムがクラッシュし、テストが開始されない場合があります。 修正してください。



別のAppDelegate (AppDelegateForTest)を作成しますが、テストを実行します(作成時に、メインターゲットに追加することを選択します)。



main.cを変更する
 int main(int argc, char *argv[]) { @autoreleasepool { Class appDelegateClass = (NSClassFromString(@"XCTestCase") ? [TestingSAAppDelegate class] : [SAAppDelegate class]); return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass)); } }
      
      







できた これで、テストはメインコードとは独立して実行されます。



この記事は完成できると思います。 OCMockライブラリから最も必要なものと同様に、標準ライブラリからのテスト用の関数が考慮されました。 プロジェクトのソースコードはこちらからダウンロードできます



テストで頑張ってください!



All Articles