LoginSignup
21
21

More than 5 years have passed since last update.

黒魔術を使って UI のテストを自動化する

Last updated at Posted at 2014-12-14

Qiita で知り合った方との勉強会で発表(しようとして全然間に合わず帰ってから実装)した UI テスト自動化の話。

iOS のイベント処理の仕組み

これ→ https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/EventHandlingiPhoneOS.pdf

日本語はこちら→ https://developer.apple.com/jp/devcenter/ios/library/documentation/EventHandlingiPhoneOS.pdf

黒魔術

iOS において、ユーザーが画面をタップしたり、デバイス自体を傾けたりシェイクしたり、別のリモコンを使って音楽を再生したりといった「行為」は、UIEvent というオブジェクトにまとめられ、それを UIViewUIViewControllerUIGestureRecognizer がハンドリングすることで、「行為」に対する「アクション」を実現している。

UI のテストを自動化しようと思うと、まずこの UIEvent のモックを作って、それを例えば UIViewtouchesBegan:withEvent: に渡すことでタッチイベントを模倣することが考えられる。だだし iOS は UIEvent をものごっつカプセル化しており、タッチイベントの実体である UITouchesEventUIEvent の孫クラス)に至っては公開すらされていない。

それでも無理やり自動化しようと思うと、これはもう黒魔術を使わざるを得ない。Objective C の場合、全てのメッセージパッシングは id 型に対して行われるため、非公開のメソッド(例えば UITouchesEvent_firstTouchForView:)であっても、Mock クラスに同名のメソッドを用意してやれば問題なく動かすことが出来る。ただし、内部実装はいつ変更されるかわからないため、このテスト手法が将来にわたって使えることは保証できない。

サンプルコード

流れとしては、

  • windowUIWindow インスタンス) に対してタップした場所 pointCGPoint)を渡し、最初にタッチイベントをハンドリングできる view を探す(hitTest:withEvent:を呼び出す)
  • 取得した view に対して、 touchesBegan:withEvent: (タッチ開始)及び touchesEnded:withEvent: メソッドを呼び出す。
  • これで画面上の point をタッチし、離したというイベントが view に渡り、それがボタンであること、期待する処理が行われること、をテストできる。
@interface AMOUITestViewControllerTests : XCTestCase

@end

@implementation AMOUITestViewControllerTests

- (void)window:(UIWindow *)window tapSimulationWithPoint:(CGPoint)point
{
    // UIEvent のモック
    MockUIEvent *event = [MockUIEvent new];

    // 当たり判定
    UIView *view = [window hitTest:point withEvent:event];

    // UITouch のモック
    MockUITouch *touch = [MockUITouch new];
    touch.aTouchPoint = touch.aPreviousTouchPoint = [view convertPoint:point fromView:nil];
    touch.aWindow = window;
    touch.aView = view;
    touch.aTapCount = 1;

    NSSet *touches = [NSSet setWithObject:touch];
    event.aTouches = touches;

    // タッチ開始    
    touch.aPhase = UITouchPhaseBegan;
    [view touchesBegan:touches withEvent:event];

    // タッチ終了
    touch.aPhase = UITouchPhaseEnded;
    [view touchesEnded:touches withEvent:event];
}

- (void)testCase
{
    UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 320, 568)];
    AMOUITestViewController *vc = [AMOUITestViewController new];
    window.rootViewController = vc;
    [window makeKeyAndVisible];
    CGPoint touchPoint = CGPointMake(vc.view.center.x, vc.view.center.y / 2);

    // 最初の色は vc.deactiveColor である
    XCTAssertEqualObjects(vc.someView.backgroundColor, vc.deactiveColor);

    // ボタンをタッチした
    [self window:window tapSimulationWithPoint:touchPoint];

    // タッチ後の色は vc.activeColor である
    XCTAssertEqualObjects(vc.someView.backgroundColor, vc.activeColor);

    // もう一度タッチした
    [self window:window tapSimulationWithPoint:touchPoint];

    // タッチ後の色は vc.deactiveColor に戻っている
    XCTAssertEqualObjects(vc.someView.backgroundColor, vc.deactiveColor);
}

@end

github

以下でサンプルコードを公開している。
https://github.com/amo12937/AMOUITestApplicationSample/tree/20141214_for_qiita

git clone -b 20141214_for_qiita git@github.com:amo12937/AMOUITestApplicationSample.git

マイルストン

スワイプ操作の模倣

touchesMoved:withEvent: を使えば、スワイプなどもきっと模倣できる。ただしどんな 非公開メソッドを使っているかはわからないので MockUIEventMockUITouch には修正が必要かもしれない。

ライブラリ化

テストは確かに黒魔術だが、アプリバイナリとして申請する際にはテストコードは含まれないので、これをもってリジェクトされることは無いはず。ただし、黒魔術を使ったテストを信じるべきではない、という意見は確かにその通りで、受託案件では特に、これを持って結合試験の代わりにはできないだろう(参考程度にはなるかも)。ご利用は飽くまで自己責任で。

21
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
21