Mac版KIFによるCocoaアプリケーションの自動受け入れテスト(実験的)

iOS向けの受け入れテストフレームワークはたくさん出揃っているんだけどMacアプリ向けのは全然話聞かないなあ、と思っていたので試してみた。

KIFとは

KIFはiOSアプリ向けの受け入れテストツール。ユーザーから見たアプリの正しい振舞いを検証する。ユーザー操作をエミュレーションするAPIを備えたライブラリを使ってシナリオを記述し、アプリに含めてビルドして実行する。

Mac版KIFとは

Mac版KIFはGithub上で@joshaberによってフォークされている。iOS版KIFをベースにいくつかのMac版のサポートコードが追加されている。

はじめよう

まずXcodeからテスト対象となるCocoaアプリケーションを作成します。今回は"KIFMac01"という面白気のない名前で作成しました。
仕様としてはウィンドウ中央にあるHelloボタンが押せるというだけのアプリケーションです。

MainWondows.xibを開いてPushボタンのパーツを追加します。そしてダブルクリックしてタイトルをHelloにします。これでアプリケーションは完成です(!)。

ビルド実行してアプリケーションがただしく使えるかを手動でテストしてみてください。

そうしたら次は自動テスト用のビルドの為に下準備をします。

一旦黒い画面を開いてコマンドラインからgit-submodule を利用して取得したMac版KIFのソースをプロジェクトにインポートします。

cd KIFMac01
git init
mkdir Vendors
git submodule add https://github.com/joshaber/KIF.git  Vendors/KIF-Mac

Xcode上でで Vendors/KIF-Mac/KIF.xcodeproj を取り込みましょう。xcodeprojを自分のプロジェクトに含めるコツは"Create folder references ..." で作成することです。そうすると自分のプロジェクトから子プロジェクトのターゲットや静的ライブラリ、フレームワークファイルなどが参照できます。

http://gyazo.com/5330e94b68865acdd6eed06c1feac810.png

通常アプリケーションとテスト実行ターゲットを分けるために、元のターゲットを複製します。

http://gyazo.com/7351228918fbdd2d6b603a2deba9bd04.png

作成したターゲットの依存ターゲットにKIFMacを追加、リンクバイナリにKIF.framewrokを追加します。

プリプロセッサマクロに RUN_KIF_TESTS=1 を設定し、共通のAppDelegateからテスト実行に分岐させます。


// AppDelegate.m

#import "AppDelegate.h"

#ifdef RUN_KIF_TESTS
#import "KMTestController.h"
#endif

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
#ifdef RUN_KIF_TESTS
  [[KMTestController sharedInstance] startTestingWithCompletionBlock:^{
    exit([[KIFTestController sharedInstance] failureCount]);
  }];
#endif
}

@end

KMTestController はわたしたちがこれから作るテストコントローラーです。この中でテストシナリオを追加していきます。
以降はKIFMac01のアプリ本体のターゲットではなく、テスト用に作成したターゲットにソースを追加していきます。
Helloボタンを押すテストと、Goodbyテストを押すテストの2パターンを作ります。
自動テストの成功と失敗、栄光と挫折を体験する為にわざと失敗するテストも入れました。Goodbyボタンはまだ作ってないので押すことができない=失敗するはずです。

// KMTestController.h

#import <KIF/KIFMac.h>

@interface KMTestController : KIFTestController
@end
// KMTestController.m

#import "KMTestController.h"
#import "KIFTestScenario+Additions.h"

@implementation KMTestController

- (void)initializeScenarios;
{
  [self addScenario:[KIFTestScenario scenarioToHello]];
  [self addScenario:[KIFTestScenario scenarioToGoodBy]]; // GoodByボタンを設置してないので失敗するはず
}

@end

シナリオの具体的な処理はKIFTestScenarioのObject-Cカテゴリとして自分で作成します。標準で操作のタイムアウト(対象のViewが見付からないとか)が確か30秒ぐらいなので1秒に変更してます。

// KIFTestScenario+Additions.h

#import <KIF/KIFMac.h>

@interface KIFTestScenario (Additions)
+ (id)scenarioToHello;
+ (id)scenarioToGoodBy;
@end
// KIFTestScenario+Additions.m

#import "KIFTestScenario+Additions.h"
#import <KIF/KIFMac.h>

@implementation KIFTestScenario (Additions)
+ (id)scenarioToHello;
{
  KIFTestScenario *scenario = [KIFTestScenario scenarioWithDescription:@"Test that user can click &#39;Hello&#39; button."];
  KIFTestStep* step = [KIFTestStep stepToClickViewWithTitle:@"Hello"];
  step.timeout = 1.0;
  [scenario addStep:step];
  
  return scenario;
}

+ (id)scenarioToGoodBy
{
  KIFTestScenario *scenario = [KIFTestScenario scenarioWithDescription:@"Test that user can click &#39;GoodBy&#39; buton."];
  KIFTestStep* step = [KIFTestStep stepToClickViewWithTitle:@"GoodBy"];
  step.timeout = 1.0;
  [scenario addStep:step];
  
  return scenario;
}
@end

テストの手筈が整いました。ではテストターゲットを実行してみましょう。
以下のようにコンソールに結果が出力され、アプリケーションは自動で終了します。

2012-10-13 22:00:28.431 KIFMac01IntegeRationTests[9208:303] Logging KIF test activity to /Users/laiso/Library/Logs/KIF Tests Oct 13, 2012 10.00.28 PM GMT+09.00.log
2012-10-13 22:00:28.431 KIFMac01IntegeRationTests[9208:303] BEGIN KIF TEST RUN: 2 scenarios
2012-10-13 22:00:28.432 KIFMac01IntegeRationTests[9208:303]  
2012-10-13 22:00:28.433 KIFMac01IntegeRationTests[9208:303] ---------------------------------------------------
2012-10-13 22:00:28.433 KIFMac01IntegeRationTests[9208:303] BEGIN SCENARIO 1/2 (1 steps)
2012-10-13 22:00:28.434 KIFMac01IntegeRationTests[9208:303] Test that user can click &#39;Hello&#39; button.
2012-10-13 22:00:28.435 KIFMac01IntegeRationTests[9208:303] ---------------------------------------------------
2012-10-13 22:00:29.052 KIFMac01IntegeRationTests[9208:303] PASS (0.62s): Click view with title "Hello"
2012-10-13 22:00:29.052 KIFMac01IntegeRationTests[9208:303] ---------------------------------------------------
2012-10-13 22:00:29.053 KIFMac01IntegeRationTests[9208:303] END OF SCENARIO (duration 0.62s)
2012-10-13 22:00:29.053 KIFMac01IntegeRationTests[9208:303] ---------------------------------------------------
2012-10-13 22:00:29.054 KIFMac01IntegeRationTests[9208:303]  
2012-10-13 22:00:29.054 KIFMac01IntegeRationTests[9208:303] ---------------------------------------------------
2012-10-13 22:00:29.055 KIFMac01IntegeRationTests[9208:303] BEGIN SCENARIO 2/2 (1 steps)
2012-10-13 22:00:29.055 KIFMac01IntegeRationTests[9208:303] Test that user can click &#39;GoodBy&#39; buton.
2012-10-13 22:00:29.056 KIFMac01IntegeRationTests[9208:303] ---------------------------------------------------
2012-10-13 22:00:30.067 KIFMac01IntegeRationTests[9208:303] FAIL (1.01s): Click view with title "GoodBy"
2012-10-13 22:00:30.067 KIFMac01IntegeRationTests[9208:303] FAILING ERROR: Error Domain=KIFTest Code=0 "The step timed out after 1.00 seconds." UserInfo=0x100199920 {NSLocalizedDescription=The step timed out after 1.00 seconds., NSUnderlyingError=0x100119c80 "Failed to find accessibility element with the title "GoodBy""}
2012-10-13 22:00:30.068 KIFMac01IntegeRationTests[9208:303] ---------------------------------------------------
2012-10-13 22:00:30.068 KIFMac01IntegeRationTests[9208:303] END OF SCENARIO (duration 1.01s)
2012-10-13 22:00:30.068 KIFMac01IntegeRationTests[9208:303] ---------------------------------------------------
2012-10-13 22:00:30.069 KIFMac01IntegeRationTests[9208:303]  
2012-10-13 22:00:30.069 KIFMac01IntegeRationTests[9208:303] ---------------------------------------------------
2012-10-13 22:00:30.069 KIFMac01IntegeRationTests[9208:303] KIF TEST RUN FINISHED: 1 failures (duration 1.64s)
2012-10-13 22:00:30.070 KIFMac01IntegeRationTests[9208:303] ---------------------------------------------------
2012-10-13 22:00:30.070 KIFMac01IntegeRationTests[9208:303] *** KIF TESTING FINISHED: 1 failures

Helloボタンのクリックテストは成功し、Goodbyボタンの方はタイムアウトが発生し失敗したことがわかります。

例によってGithubにこのチュートリアルソースコードを上げておいたのでうまくいかない時などにチェックアウトして参考にしてください。

Mac-Smaples/KIFMac01 at master · laiso/Mac-Samples · GitHub

まとめ

Mac版KIFが動いた。うれしい。

他に候補としてはSIKULI(画像処理を使った汎用的なインティグレーションツール)なんかが考えられるが、そもそもMac向けデスクトップ自動操作するソリューション自体は結構あるので、あとはそれに正しさを検証する仕組みが加わればいい感じもする。

ただMac版KIFも一年ぐらい前のKIFからフォークされたものでその後作者によって更新はされていないので最近のKIFの機能のサポートや将来性なんかには不安がありそう(明日のメンテナは君だ!)。

おまけ

iOS/Macアプリケーション開発で以下のスライド資料のような受け入れテストとクラスベースのユニットテストをサイクルに取り入れた開発を行いたいと思っているので。iOS/Mac向けテストツール・モックフレームワークなどの情報を日々もとめています。何かお得情報があったら@laisoなどへ教えてください。

テスト駆動開発の進化 (デブサミ関西 2012)