mixi-inc / iOSTraining-TODO-App

iOS Training で使う教材用TODOアプリのリポジトリです
Apache License 2.0
16 stars 37 forks source link

入力時にバリデーションを行い、単体テストを書く #7

Open ginrou opened 9 years ago

ginrou commented 9 years ago

TODOアプリを作ってみようシリーズの最終回の演習課題です。

内容

TODO入力時のバリデーションと単体テストを取り扱います。

todo7 ↑バリデーションの様子

todo8 ↑テスト実行の様子. この例では失敗してます。

目的

前回( #6 )のゴール地点であるブランチlocal-notification からスタートしてください。

ゴール地点

ブランチ unit-testを見ると解答例が書いてあります。

教材

まず、入力されたTODOが仕様を満たすかどうかをチェックするメソッドを作ります。 仕様を満たす場合は YES を、満たさない場合は NO を返すようにします。

そしてTODOが正しくない場合はアラートを表示します。 アラートの表示にはUIAlertControllerを用います。使い方は差分のコードを見てください。 リファレンスは UIAlertController Class Reference になります。

このUIAlertControllerはiOS8でクラス名が変更になったクラスで、以前はUIAlertViewを利用していました。

単体テストを追加する b4eb18b

次に単体テストを追加していきます。テストフレームワークであるXCTestがプロジェクト作成時に導入されているのでそれを利用します。 今回テストを行いたい対象はAddTodoViewControllerなのでAddTodoViewController.mのターゲットにTodoTestsを追加します。

2015-06-01 3 57 40 pm

次に新規テストファイルを追加します。NewFileからテンプレートは"Test Case Class"を選択し、subclass of はXCTestCaseを選択します。 ファイル名は特に制約はありませんがAddTodoViewControllerTestsなどにすると分かりやすいです。

テストファイルが追加できたらテストターゲットのヘッダファイルをimportしてテストを記述します。 XCTestによるテストの記述方法は教材を参照してください。 テストには、成功するパターンや失敗するパターン、異常な入力などの様々なパターンを網羅するとより安心して開発に望むことができます。 ちなみに 4a49faf では以下のケースに対するケアが漏れていて、テストを実行すると失敗となります。

ginrou commented 9 years ago

テストの実行は

で実行できます。

ginrou commented 9 years ago

以下のテストケースを実装してみてください

    // 順正常系 : NOが返るパターン
    // 1. titleが空文字列の場合
    // 2. titleがキーとして存在しない場合
    // 3. titleが空白のみの場合
    // 4. dateが昔のパターン
    // 5. dateがキーとして存在しない場合パターン
    // 6. 引数にnilを渡したパターン
    // 7. dateの型がNSStringの場合
    // 8. 余分なkey-valueペアがある場合 : 成功するパターン
ginrou commented 9 years ago

テストケース解答例

    // 正常系
    todo = @{@"title": title, @"date": date};

    XCTAssertTrue([vc isValidToDo:todo]);

    // 順正常系 : NOが返るパターン
    // 1. titleが空文字列の場合
    todo = @{@"title": @"", @"date": date};
    XCTAssertFalse([vc isValidToDo:todo]);

    // 2. titleがキーとして存在しない場合
    todo = @{@"date": date};
    XCTAssertFalse([vc isValidToDo:todo]);

    // 3. titleが空白のみの場合
    todo = @{@"title": @"     ", @"date": date};
    XCTAssertFalse([vc isValidToDo:todo]);

    // 4. dateが昔のパターン
    todo = @{@"title": title, @"date": [NSDate dateWithTimeIntervalSinceNow:-100]};
    XCTAssertFalse([vc isValidToDo:todo]);

    // 5. dateがキーとして存在しない場合パターン
    todo = @{@"title": title};
    XCTAssertFalse([vc isValidToDo:todo]);

    // 6. 引数にnilを渡したパターン
    XCTAssertFalse([vc isValidToDo:nil]);

    // 7. dateの型がNSStringの場合
    todo = @{@"title": title, @"date": @"hogehoge"};
    XCTAssertFalse([vc isValidToDo:todo]);

    // 8. 余分なkey-valueペアがある場合 : 成功するパターン
    todo = @{@"title": title, @"date": date, @"hoge": @(10)};
    XCTAssertTrue([vc isValidToDo:todo]);

メソッドisValidTodo: も以下のようになります

- (BOOL)isValidToDo:(NSDictionary *)todo
{
    NSString *title = todo[@"title"];
    NSDate *date = todo[@"date"];

    NSString *trimedTitle = [title stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    if (date == nil) return NO;
    if ([date isKindOfClass:[NSDate class]] == NO) return NO;

    if (trimedTitle.length == 0) return NO;
    if ([date timeIntervalSinceNow] < 0.0) return NO;

    return YES;
}