pixiv insideは移転しました! ≫ https://inside.pixiv.blog/

GHUnitを使ったiPhoneアプリの非同期通信テスト

「お前はvim派?それともemacs派?」「Xcode派」

社内で肩身の狭い思いをしているiOS担当のshobyです。Xcodeも慣れると使いやすいですよ。

さて、今回はGHUnitというテストフレームワークを用いて、非同期通信の単体テストを行う方法を紹介します。

GHUnitとは

GHUnitとはMac OS XiOS用の単体テストフレームワークです。 Xcode標準のテストフレームワークであるSenTestingKitと互換性があり、 標準フレームワークには無い多くの便利な機能を持っています。

SenTestingKitと比べたGHUnitの利点は以下の通りです。

  • 非同期処理のテストが容易
    • 通信等のテストを簡単に記述できる
  • 実機上でのテストが容易
    • アプリケーションとして端末上で実行される
    • メモリ容量の制限等、ハードウェアに影響を受ける処理のテストを行える
  • テストを部分的に実行可能

pixivのiOSアプリでは、サイズの大きな画像をネットワーク経由で大量に取得する事が多く、 メモリ不足による強制終了や、通信不良による動作速度の低下に悩まされていました。

そのため、画像読み込みの不具合解消と効率化を行うべく、テスト用環境としてGHUnitの導入を行いました。

これから実際の使用使用方法について紹介します。

GHUnitの導入

ビルド

最新版はGithubから取得できます。

https://github.com/gabriel/gh-unit

取得したファイルを以下のようにビルドします。

1
2
cd ./Project-iOS
make

ビルドに成功すると、以下にフレームワークのディレクトリが生成されます。

./Project-iOS/build/Framework/GHUnitIOS.framework

インストール

インストールページを参考にインストールを行います。 インストール方法については割愛します。

非同期テストケースの作成

以下のような、GHAsyncTestCaseのサブクラスを作成します。

1
2
3
4
5
6
7
#import <GHUnitIOS/GHUnit.h> 
@interface PIXNewtorkTests : GHAsyncTestCase
@end
@implementation PIXNewtorkTests
@end

テスト用メソッド

GHAsyncTestCaseを用いたテストの流れは以下の通りです。

  1. prepareを呼び出す
  2. 非同期処理を開始する
  3. waitForStatus:timeout:を呼び出し、完了通知を待つ
  4. 非同期処理完了後、notify:forSelector:で完了を通知する

NSURLConnectionで通信を行う場合は、以下のようなテストメソッドを記述します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)testNSURLConnection
{
  [self prepare];
  NSURL *url = [NSURL URLWithString:@"http://pixiv.net"];
  NSURLRequest *request = [NSURLRequest requestWithURL:url];
  NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
  [connection start];
  [self waitForStatus:kGHUnitWaitStatusSuccess timeout:10.0];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  // 成功時
  [self notify:kGHUnitWaitStatusSuccess forSelector:@selector(testNSURLConnection)];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
  // エラー時
  GHTestLog(@"error:%@", error);
  [self notify:kGHUnitWaitStatusFailure forSelector:@selector(testNSURLConnection)];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  NSString *responseString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
  GHTestLog(@"responseString:%@", responseString);
}

iOS5からはNSURLConnectionに、Blocksを利用した非同期通信用メソッドができましたが、 そちらでも問題なくテストをすることができます。(以下、ARC環境を想定)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)testWithBlocks
{
  [self prepare];
  NSURL *url = [NSURL URLWithString:@"http://pixiv.net"];
  NSURLRequest *request = [NSURLRequest requestWithURL:url];
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
    if (error) {
      GHTestLog(@"error:%@", error);
      [self notify:kGHUnitWaitStatusFailure forSelector:@selector(testWithBlocks)];
    } else {
      NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
      GHTestLog(@"responseString:%@", responseString);
      [self notify:kGHUnitWaitStatusSuccess forSelector:@selector(testWithBlocks)];
    }
  }];
  [self waitForStatus:kGHUnitWaitStatusSuccess timeout:10.0];
}

実行結果

以下のように、テストの結果は分かりやすく表示されます。 実行結果はテストメソッド毎に表示され、それぞれ個別に再実行も可能です。

プロファイラとの組み合わせ

GHUnitはアプリとしてテストが実行されるため、 InstrumentsのLeaksやTime Profilerといったプロファイラと組み合わせた分析が可能です。 テストメソッドを個別に実行する事により、メモリリークボトルネックの検出が行いやすくなります。

まとめ

今回は、GHUnitというテスト用フレームワークを用いて、非同期通信を行う方法を紹介しました。 pixivでは現在、GHUnitを開発に取り入れ、画像読み込みの不具合解消と効率化を目指して開発を行っています。

pixivはWebサービスの開発だけでなく、iOSAndroidといったスマートフォン向けのネイティブアプリケーション開発にも力を入れています。 一緒にXcodeObjective-Cの素晴らしさを語り合える仲間を募集しておりますので、興味のある方は採用サイトからぜひご応募ください!