QA@IT

xctest上でのuiviewにactionsheet

3019 PV

連日すみません。。。

昨日の件は解決できました
ありがとうございました

次にactionsheetに追加する部分が通るか確認するテストコードを書いていたのですが、
また、エラーが。。。

-(void)testElaseSheet
{
    //UIWindow* window = [[[UIApplication sharedApplication] delegate] window];

    [[mockactionsheet expect] showInView:[[[[UIApplication sharedApplication] keyWindow] subviews] lastObject]];



    vc.aActionSheet = mockactionsheet;
    [vc showEraseSheet:mockelaser];
    [mockactionsheet verify];

}

こういったエラーが。。。

<unknown>:0: error: -[ViewControllerTests testElaseSheet] : Sheet can not be presented because the view is not in a window: <UIView: 0xb9df390; frame = (0 0; 320 568); autoresize = W+H; layer = 

ググってみたところいっぱい出てきたのですが、大体ここに書いてある
http://stackoverflow.com/questions/18932544/nsinvalidargumentexception-reason-sheet-can-not-be-presented-because-the-vi

2つとも試したのですが、結果は同じでした。。。

他に何か有効な手段はありますでしょうか

よろしくお願いします

  • エラーから見るにshowEraseSheetで落ちているような気がしますのでその辺りがどう動くのか見てみるといいかもしれません。mockelaserもshowEraseSheetも名前しか出てこないので何とも言えないです。スタックトレースは出てないんでしょうか。 -

回答

根本的なレベルから間違っているので、「ユニットテスト」「テストケース」「JUnit」などを一から勉強しなおしてください。

そもそも今回のテスト対象は何なのでしょう?

テストケースの中にアサーションが一つもないのはありえません。

ユニットテストは、MVCのうち主にModelをテストするものです。
ViewやControllerのようなユーザーインターフェースはほとんどテストできません。

通常ViewControllerに書いてるコードを、TestCaseに書き写してもテストになりません。

編集 履歴 (0)
  • まずocmockの使い方を調べてみてはどうですか? -
  • Mockオブジェクトの話は関係ありません。ユニットテストそのものの話です。 -
  • アサーションがないというのは何をもっていっているのでしょう?調べましたか?ocmockはverifyを呼び出すとあらかじめexpectで設定した期待と異なる動作をした場合に自動的にテストを失敗にしてくれる機能があります。 -
  • verifyは自動的にテストを失敗にするのではなく、例外を発生させるのです。例外をテストしたいなら、例外をテストするアサーションを書くのが普通でしょう? -
  • 例外が起こることを期待するならそうでしょう。これは例外のテストではありません。 -
  • XCTAssertNoThrow()を使って「ここでは例外が発生しない」と書いてください。 -
  • 普段Assert文以外は全部そうされているんですか?
    XCTAssertNoThrowで囲むと余計な失敗情報(「ここでは~」のことじゃないですよ)が増えますし、フォーラムやstackoverflowその他でそのようにテストしている人を見かけたことがないので、私はチュートリアルなどで見かける形で扱っていこうと思います。
    -
  • Assert文以外は全部そうする??XCTAssertNoThrowで囲むと余計な失敗情報が増える??どういう意味ですか?あと、stubの代わりにexpectを使った理由は、そのメソッドをテストの対象にしたいからじゃないんですか? -
  • ocmock作者のiPhoneのサンプルでもSTAssertNoThrowは使っていませんので、XCTAssertNoThrowなどを使うよう推奨すべきというのは作者に提案されるとよろしいかと思います。ここで議論するようなことでもないでしょう。3点について続けて回答させていただきますが、私はサンプルやチュートリアルに従う形をとります。これ以上は議論を広げるつもりもありませんので回答は結構です。 -
  • Assert文以外は~ > ocmock作者の使用例含めネットで見た限りverifyをそのまま呼び出せばよいように見受けられました。それでもテストフレームワーク標準のアサーションを使うべきと主張されておられるので、ではどこまでをアサーションで対処すべきと考えておられるのかがわかりませんでした。verifyのような検証を銘打つメソッドだけなのかocmock由来の例外を返しうるところすべて [つづく] -
  • (例えばniceでないmockの未定義メソッドが呼び出されると例外です。この場合なら呼び出されないことが期待されると考えられるので、mockと関連するオブジェクトのメソッド呼び出し箇所すべても含める)なのか、ocmock関連メソッドすべて(expect,stubなど含む)のか、もっと極端にしかしシンプルに標準フレームワークのアサーション以外例外は [つづく] -
  • 一切起きてほしくないのでAssert文以外すべて例外を補足すべきなのか、それ以外なのか。前述の通り回答は結構です。 -
  • XCTAssertNoThrowで余計な失敗情報 > verifyをそのままの場合の失敗メッセージと、XCTAssertNoThrowなどで囲んだ場合の失敗メッセージを単純に比較した場合、XCTAssertNoThrowのメッセージが先頭に数行付け足されるような恰好になります。 [つづく]
    -
  • 実際に見たいのはverify(またはほかの例外)のメッセージなので、verifyをそのまま呼び出せば良いと思っている私にとっては先頭に余計な情報が追加されるように感じたという意味です。
    -
  • stubの代わりにexpect > 私の回答の前のコードへの指摘かと思います(stripeさんの2つ目の指摘「expectしたメソッドは~」はちょうど自分のコメント書いていて見落としていました。)。stubの代わりにexpectを使ったつもりはありません。 おっしゃる通り expectで指定したものはテスト対象です。単純にコードのミスです。 -
  • 長文・連投失礼いたしました。 -
  • 「stubの代わりにexpect > 私の回答の前のコードへの指摘かと思います」>違います。そもそもなぜexpect - verifyを書いたのかということです。stubを使っていればverifyは書かないのだから、例外について議論になることもなかったはずです。 -
  • あえてexpect - verifyを使った理由は、メソッドが呼ばれたことを(または呼ばれなかったことを)明示的に確認するためじゃないんですか?テストケースにおいて、結果を確認するためにAssertを書いて表明するのは基本中の基本です。逆に確認するつもりがないものについてAssertを書く必要はありません。 -
  • メソッドが呼ばれることを確認したければXCTAssertNoThrow([mock verify])と書き、メソッドが呼ばれないことを確認したければXCTAssertThrow([mock verify])と書き、特に確認するつもりがないならstubを使えばいいのです。 -
  • あなたが見たサンプルは「あくまでもocmockのサンプルコード」だったんじゃないんですか?そもそもXCTAssertNoThrow()で例外を補足しなかったら、後続のAssertが処理されなくなるじゃないですか。 -

すいません、stripさんの指摘の通りコードが間違ってました。
テスト失敗になっていたのは (IBAction)showEraseSheet:(id)sender でActionSheetを作成していたためです。
最初showEraseSheetがただのメソッドだとおもっていたんですが、途中でIBActionだと気付いて意図せずオーバーロードになってしまっていました(ただの言い訳です)。
aActionSheetはプロパティで設定するので showEraseSheetのAction時には設定する必要はありませんでした。

ここを修正するとテストはパスします。
shunsuke_h2006 さんのコードでもaActionSheetがメソッド呼び出しによって変更されていないか確認してみてください。


pod setupの待ち時間の長さに挫折しそうになりましたが試しにやってみました。
説明がなかった部分は想像で書いています。
最初コードが間違っていたため再現したように見えましたが、修正によりテストは通過するようになりました。

Single View Applicationにボタンを置き、以下のようなコードで発生します。

ViewController.h

@interface ViewController : UIViewController

@property UIActionSheet *aActionSheet;
@property (weak, nonatomic) IBOutlet UIButton *showerasebutton;
- (IBAction)showEraseSheet:(id)sender;

@end

ViewController.m

// @endの前あたりに以下の2メソッド追加

// 追記の際に修正: 以下は不要になりました。
// - (void)showEraseSheet{
// }

- (IBAction)showEraseSheet:(id)sender {
    // 追記の際に修正: showInViewのみになりました。

    [[self aActionSheet] showInView:self.view];
}

テストコード
~~Test.m

- (void)testElaseSheet
{
    id mockactionsheet = [OCMockObject mockForClass:[UIActionSheet class]];
    id mockeraser = [OCMockObject mockForClass:[UIButton class]];

    ViewController *vc = [[ViewController alloc]init];
    vc.aActionSheet = mockactionsheet;


    [[mockactionsheet expect] showInView:vc.view];

    [vc showEraseSheet:mockeraser];

    [mockactionsheet verify];
}

で、これを書く前に試しで書いてみたコードだとうまくいきました。
上記コードに以下の様に追記します。

ViewController.h

@interface ViewController : UIViewController
〜中略〜

// 以下の3行を追加
@property (weak, nonatomic) IBOutlet UIButton *button;
- (IBAction)buttonClick:(id)sender;
- (UIActionSheet*) generateActionSheet;

@end

ViewController.m

// @implementation を変更
@implementation ViewController{
    UIActionSheet *actionSheet;
}

〜中略〜

// 以下3メソッドを@endの前に追加

- (UIActionSheet*) generateActionSheet{
    return [[UIActionSheet alloc]
            initWithTitle:@"title"
            delegate:nil
            cancelButtonTitle:nil
            destructiveButtonTitle:nil
            otherButtonTitles:@"button1", nil];
}

- (void)showSheet{
    actionSheet = [self generateActionSheet];
    [actionSheet showInView:self.view];
    actionSheet = nil;
}

- (IBAction)buttonClick:(id)sender {
    [self showSheet];
}

テストコード
~~Test.m

- (void)testActionSheet
{
    id mockAC = [OCMockObject mockForClass:[UIActionSheet class]];
    id mockButton = [OCMockObject mockForClass:[UIButton class]];

    ViewController *vc = [[ViewController alloc]init];
    vc.button = mockButton;
    id mockVC = [OCMockObject partialMockForObject:vc];
    [[[mockVC stub] andReturn:mockAC] generateActionSheet];


    [[mockAC expect] showInView:vc.view];

    [vc buttonClick:mockButton];

    [mockAC verify];
}

参考までにどうぞ。

編集 履歴 (2)
  • おおお、ホントいつもありがとうございます!試してみます -
  • 修正後のコードがうまく行っているように見えるのは、ocmockの機能を使って本来発生するはずのエラーを消し込んでいるからだと思います。 -
  • expectしたメソッドは、実際には呼ばれないということを知っていますよね? -
  • 本来発生するはずのエラーを消し込んでいるとは具体的にどこのことでしょう。
    ちなみに普通に起動してiOSシミュレータで起動実行できます(質問者の形式のコードもシミュレータでは動作します。テストランナーからだとエラーですが)。
    -
  • [[mockAC expect] showInView:vc.view];のことです。mockACのshowInView:が呼ばれることをテストできますが、UIActionSheetのshowInView:が呼ばれる訳ではありません。だからエラーは出ません。 -
  • 二つ目見落としてました、すいません。たしかにテストコードが間違っていました。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。