2015年06月17日

テストフレームワークXCTest

iOS担当のエモトです.私事ではありますが,ラブライブ関係者様,劇場版の興行収益ランキング1位おめでとうございます.私も微力ながら貢献させていただきました.

さて先日のWWDCにて, Xcodeのアップデートが発表されました.ネイティヴアプリに変わったwatchOS2やオープンソースになるSwift2.0などと比べると話題性はあまり大きくないですが,他の新機能にはなかなか興味深いものがあり,Xcode7の正式リリースが楽しみです.

私が気になるものの1つは,User Interface Testing / Recording です.XcodeはテストフレームワークXCTestを用いたテストが可能ですが,UIテストは非対応でした.UIテスト用のサードライブラリはこれまでもありましたが,近年iOS端末の解像感が多様化してきたので使用を控えていました.標準でサポートされるのであれば,是非使っていきたいですね.

そのUI Testing / Recordingの紹介といきたいですが,Xcode7はいまbeta(2015/06/17 現在)ということもあり控えるとして,今回はテストフレームワークXCTestの簡単な使い方です(UI Testing / Recordingは一般公開されてからですね).

XCTestを使ってみる

例として,二つの実数を加算するメソッドをもつクラス(Calculator)を作成して,このメソッドをテストしてみます.実際は単純な足し算ですが,あとで非同期でのテストを試すため,わざと遅延処理をいれた完了ブロックを追加しています.

Calculator.h
#import <Foundation/Foundation.h>

extern NSString *const kNotificationCalculatorFinished;
extern NSString *const kNotificationCalculatorFailed;
typedef void (^CalculatorBlock) (float answer);

@interface Calculator : NSObject

@property (nonatomic) float answer;
@property (nonatomic, retain) NSString *answerString;

-(float)add:(float)v1
         to:(float)v2
 completion:(CalculatorBlock)completion;

@end
Calculator.m
#import "Calculator.h"

NSString *const kNotificationCalculatorFinished = @"kNotificationCalculatorFinished";
NSString *const kNotificationCalculatorFailed = @"kNotificationCalculatorFailed";
NSTimeInterval const kDelayInterval = 0.5;

@implementation Calculator

-(float)add:(float)v1 to:(float)v2 completion:(CalculatorBlock)completion
{
    self.answer = v1+v2;
    self.answerString = [NSString stringWithFormat:@"%@", @(self.answer)];
    
    if (completion) {
        dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW,
                                             kDelayInterval * (NSTimeInterval)NSEC_PER_SEC);
        dispatch_after(when, dispatch_get_main_queue(), ^{
            completion(self.answer);
        });
    }
    
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc postNotificationName:kNotificationCalculatorFinished
                      object:nil];
    
    return self.answer;
}
@end

テストをしてみた

プロジェクト作成時に特に設定を変更していなければ「“プロジェクト名”Tests」というフォルダーがプロジェクトに作成されていると思います(ない場合はターゲット追加からテストを追加してください).その中でmファイルにテストコードを書いていきます.例として,加算メソッドのテストを書いてみます.
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "Calculator.h"

@interface SampleTestAppTests : XCTestCase
{
    Calculator *calculator_;
}
@end

@implementation SampleTestAppTests

- (void)setUp {
    [super setUp];
    calculator_ = [[Calculator alloc] init];
}

- (void)tearDown {
    [super tearDown];
}

- (void)testAdd
{
    float v1 = 1;
    float v2 = 2;
    float v3 = [calculator_ add:v1 to:v2 completion:nil];
    XCTAssertEqual(v3, 3);
}
@end
ここで決まりとして,テストコードは頭文字に必ずtestをつけます.この頭文字がないとテストコードと認識してくれません.テストコードを書き終えたら,左側のツリー状からコードを選択するか,ソース部の行番号の左あたりをクリックしてテストを実行します.なにも問題がなければテストは成功です.

xctest_run2.png

非同期でもテストがしたい

テストは非同期にも対応しています.幾つかの方法があり,以下のようにしてテストコードを書くことができます.
- (void)testAddAsync
{
    XCTestExpectation *expectation = [self expectationWithDescription:@"add async"];
    
    float v1 = 1;
    float v2 = 2;
    [calculator_ add:v1 to:v2 completion:^(float answer) {
        XCTAssertEqual(answer, 3);
        [expectation fulfill];
    }];
    
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
        NSLog(@"%s, %@", __func__, error.localizedDescription);
    }];
}

- (void)testAddNotification
{
    XCTestExpectation *exp = [self expectationForNotification:kNotificationCalculatorFinished
                                                       object:nil
                                                      handler:^BOOL(NSNotification * __nonnull notification)
                              {
                                  NSLog(@"%s, notification %@", __func__, notification);
                                  [exp fulfill];
                                  return true;
                              }];
    
    float v1 = 1;
    float v2 = 2;
    [calculator_ add:v1 to:v2 completion:nil];
    
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
        NSLog(@"%s, %@", __func__, error.localizedDescription);
    }];
}

- (void)testAddKVO1
{
    [self keyValueObservingExpectationForObject:calculator_
                                        keyPath:@"answerString"
                                  expectedValue:@"3"];
    
    float v1 = 1;
    float v2 = 2;
    [calculator_ add:v1 to:v2 completion:nil];
    
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
        NSLog(@"%s, %@", __func__, error.localizedDescription);
    }];
}

- (void)testAddKVO2
{
    [self keyValueObservingExpectationForObject:calculator_
                                        keyPath:@"answerString"
                                        handler:^BOOL(id  __nonnull observedObject, NSDictionary * __nonnull change)
     {
         NSLog(@"%s, observedObject %@, change %@", __func__, observedObject, change);
         
         NSString *correct = @"3";
         
         //        Calculator *tempCalc = (Calculator*)observedObject;
         NSString *newStr = change[@"new"];
         return [newStr isEqualToString:correct];
     }];
    
    float v1 = 1;
    float v2 = 2;
    [calculator_ add:v1 to:v2 completion:nil];
    
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
        NSLog(@"%s, %@", __func__, error.localizedDescription);
    }];
}


XCTestに関して簡単に紹介しました.より詳細な情報はアップルが公式ドキュメントを公開しているので,それを参照してもらえると幸いです.
posted by Seesaa京都スタッフ at 17:16| Comment(0) | iOS | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: