web-dev-qa-db-ja.com

iOSアプリのWebサービスのスタブ化/モックアップ

私は、一連のリモートWebサービスとの通信を主な目的とするiOSアプリに取り組んでいます。統合テストでは、予測可能な結果を​​もたらすある種の偽のWebサービスに対してアプリを実行できるようにしたいと考えています。

これまでのところ、2つの提案を見てきました。

  1. 静的な結果をクライアントに提供するWebサーバーを作成します(例 here )。
  2. 異なるWebサービス通信コードを実装します。コンパイル時間フラグに基づいて、Webサービスまたはローカルファイルから応答をロードするコード( および 別の1つ )を呼び出します。

コミュニティがこのアプローチのそれぞれについてどう考えているか、そしてこのワークフローをサポートするツールがそこにあるかどうか知りたいです。

更新:次に、具体的な例を示します。ユーザー名とパスワードを入力するログインフォームがあります。 2つの条件を確認したいと思います。

  1. [email protected]ログインの拒否と
  2. [email protected]ログインに成功しました。

したがって、usernameパラメータを確認して適切な応答をスローするためのコードが必要です。うまくいけば、それが「偽のWebサービス」で必要なすべてのロジックです。これをきれいに管理するにはどうすればよいですか?

33
EightyEight

オプション1に関しては、過去にCocoaHTTPServerを使用してこれを実行し、サーバーをOCUnitテストに直接埋め込みました。

https://github.com/robbiehanson/CocoaHTTPServer

これをユニットテストで使用するためのコードをここに配置します。 https://github.com/quellish/UnitTestHTTPServer

結局のところ、HTTPは設計上、要求/応答のみです。

Webサービスをモックする場合、モックHTTPサーバーを作成する場合も、コードでモックWebサービスを作成する場合も、ほぼ同じ量の作業になります。テストするXコードパスがある場合、モックで処理するXコードパスが少なくともあります。

オプション2の場合、Webサービスと通信しないWebサービスをモックするには、代わりに既知の応答を持つモックオブジェクトを使用します。 [MyCoolWebService performLogin:username withPassword:password]

あなたのテストでは

[MyMockWebService performLogin:username withPassword:password]重要な点は、MyCoolWebServiceとMyMockWebServiceが同じコントラクトを実装することです(objective-cでは、これはプロトコルになります)。 OCMockには、始めるためのドキュメントがたくさんあります。

ただし、統合テストの場合、QA /ステージング環境などの実際のWebサービスに対してテストする必要があります。実際に何を記述しているのかは、統合テストよりも機能テストに似ています。

2
quellish

Nocilla を使用することをお勧めします。 Nocillaは、シンプルなDSLでHTTPリクエストをスタブするためのライブラリです。

Google.comから404を返したいとしましょう。あなたがしなければならないすべては:

stubRequest(@"GET", "http://www.google.com").andReturn(404); // Yes, it's ObjC

その後、google.comへのHTTPは404を返します。

より完全な例では、POSTを特定の本文およびヘッダーと照合し、返信定型文を返します。

stubRequest(@"POST", @"https://api.example.com/dogs.json").
withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}).
withBody(@"{\"name\":\"foo\"}").
andReturn(201).
withHeaders(@{@"Content-Type": @"application/json"}).
withBody(@"{\"ok\":true}");

任意のリクエストを照合し、任意の応答を偽造できます。詳細については、READMEを確認してください。

他のソリューションよりもNocillaを使用する利点は次のとおりです。

  • これは速い。実行するHTTPサーバーはありません。テストは非常に高速に実行されます。
  • 管理する必要のないクレイジーな依存関係はありません。その上、CocoaPodsを使用できます。
  • 十分にテストされています。
  • コードを理解し、維持することを本当に簡単にするすばらしいDSL。

主な制限は、AFNetworking、MKNetworkKit、プレーンなNSURLConnectionなど、NSURLConnectionの上に構築されたHTTPフレームワークでのみ機能することです。

お役に立てれば。他に必要なことがあれば、私がお手伝いします。

25
luisobo

私はあなたがObjective-Cを使用していると想定しています。 Objective-Cの場合 OCMock はモック/ユニットテストで広く使用されています(2番目のオプション)。

私はOCMockを1年以上前に最後に使用しましたが、私が覚えている限り、それは本格的なモックフレームワークであり、以下で説明するすべてのことを実行できます。

モックについて重要なことの1つは、オブジェクトの実際の機能のほとんどまたはほとんどを使用できないことです。 「空の」モックを作成し(すべてのメソッドはオブジェクトですが、何もしません)、テストで必要なメソッドのみをオーバーライドできます。これは通常、モックに依存する他のオブジェクトをテストするときに行われます。

または、実際のオブジェクトが動作するように動作するモックを作成し、そのレベルでテストしたくない一部のメソッドをスタブアウトすることもできます(たとえば、実際にデータベースにアクセスするメソッド、ネットワーク接続が必要なメソッドなど)。これは通常、モックされたオブジェクト自体をテストするときに行われます。

モックを作成するのは一度きりではないことを理解することが重要です。すべてのテストでは、テスト対象に基づいて、同じオブジェクトのモックを新たに作成できます。

モックについてのもう1つの重要なことは、シナリオ(呼び出しのシーケンス)とそれらに対する「期待」(シーンの背後にあるメソッドを呼び出す必要があるパラメーター、順序で)を「記録」し、次に「再生」できることです。シナリオ-期待が満たされない場合、テストは失敗します。これが、クラシックTDDとモックリストTDDの主な違いです。これには長所と短所があります(Martin Fowlerの記事を参照)。

次に、具体的な例を見てみましょう(C++またはJava Objective Cではなく)のような擬似構文を使用します):

入力したログイン情報を表すクラスLoginFormのオブジェクトがあるとします。 (とりわけ)メソッドsetName(String)setPassword(String)bool authenticateUser()、およびAuthenticator* getAuthenticator()があります。

また、(特に)メソッドbool isRegistered(String user)bool authenticate(String user, String password)、およびbool isAuthenticated(String user)を持つクラスAuthenticatorのオブジェクトもあります。

いくつかの簡単なシナリオをテストする方法は次のとおりです。

上記の4つを除いて、すべてのメソッドを空にしてMockLoginFormモックを作成します。最初の3つの方法は、実際のLoginForm実装を使用します。 getAuthenticator()は、MockAuthenticatorを返すようにスタブされます。

いくつかの偽のデータベース(内部データ構造やファイルなど)を使用して3つのメソッドを実装するMockAuthenticatorモックを作成します。データベースにはタプルが1つだけ含まれます:('rightuser','rightpassword')

TestUserNotRegistered

再生シナリオ:

MockLoginForm.setName('wronuser');
MockLoginForm.setPassword('foo');
MockLoginForm.authenticate();

期待:

getAuthenticator() is called
MockAuthenticator.isRegistered('wrognuser') is called and returns 'false'

TestWrongPassword

再生シナリオ:

MockLoginForm.setName('rightuser');
MockLoginForm.setPassword('foo');
MockLoginForm.authenticate();

期待:

getAuthenticator() is called
MockAuthenticator.isRegistered('rightuser') is called and returns 'true'
MockAuthenticator.authenticate('rightuser','foo') is called and returns 'false'

TestLoginOk

再生シナリオ:

MockLoginForm.setName('rightuser');
MockLoginForm.setPassword('rightpassword');
MockLoginForm.authenticate();
result = MockAuthenticator.isAuthenticated('rightuser')

期待:

getAuthenticator() is called
MockAuthenticator.isRegistered('rightuser') is called and returns 'true'
MockAuthenticator.authenticate('rightuser','rightpassword') is called and returns 'true'
result is 'true'

これがお役に立てば幸いです。

7
malenkiy_scot

OHTTPStubs は、多くの牽引力を得ている、やりたいことを実行するための非常に優れたフレームワークです。彼らのgithub readmeから:

OHTTPStubsは、ネットワーク要求を非常に簡単にスタブ化するように設計されたライブラリです。それはあなたを助けることができます:

  • 偽のネットワークデータ(ファイルからスタブ)を使用してアプリをテストし、低速ネットワークをシミュレートして、悪いネットワーク条件でのアプリケーションの動作を確認します
  • フィクスチャからの偽のネットワークデータを使用する単体テストを記述します。

NSURLConnection、新しいiOS7/OSX.9のNSURLSessionAFNetworking(1.xと2.xの両方)、またはCocoaのURLロードシステムを使用するネットワークフレームワークで動作します。

OHHTTPStubsヘッダーは、ヘッダーファイルでAppledocのような/ Headerdocのようなコメントを使用して完全に文書化されています。 ここでオンラインドキュメントを読むこともできます

次に例を示します。

[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
    return [request.URL.Host isEqualToString:@"mywebservice.com"];
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
    // Stub it with our "wsresponse.json" stub file
    NSString* fixture = OHPathForFileInBundle(@"wsresponse.json",nil);
    return [OHHTTPStubsResponse responseWithFileAtPath:fixture
              statusCode:200 headers:@{@"Content-Type":@"text/json"}];
}];

追加の使用例 wikiページ を見つけることができます。

6
memmons

NSURLProtocolサブクラスを使用すると、モックWebサービスを非常に効果的に作成できます。

ヘッダ:

@interface MyMockWebServiceURLProtocol : NSURLProtocol
@end

実装:

@implementation MyMockWebServiceURLProtocol

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    return [[[request URL] scheme] isEqualToString:@"mymock"];
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
    return request;
}

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
    return [[a URL] isEqual:[b URL]];
}

- (void)startLoading
{
    NSURLRequest *request = [self request];
    id <NSURLProtocolClient> client = [self client];
    NSURL *url = request.URL;
    NSString *Host = url.Host;
    NSString *path = url.path;
    NSString *mockResultPath = nil;
    /* set mockResultPath here … */
    NSString *fileURL = [[NSBundle mainBundle] URLForResource:mockResultPath withExtension:nil];
    [client URLProtocol:self
 wasRedirectedToRequest:[NSURLRequest requestWithURL:fileURL]
       redirectResponse:[[NSURLResponse alloc] initWithURL:url
                                                  MIMEType:@"application/json"
                                     expectedContentLength:0
                                          textEncodingName:nil]];
    [client URLProtocolDidFinishLoading:self];
}

- (void)stopLoading
{
}

@end

興味深いルーチンは-startLoadingです。このルーチンでは、要求を処理し、クライアントをそのファイルURLにリダイレクトする前に、App Bundleで応答に対応する静的ファイルを見つけます。

あなたはプロトコルをインストールします

[NSURLProtocol registerClass:[MyMockWebServiceURLProtocol class]];

そして、次のようなURLで参照してください

mymock://mockhost/mockpath?mockquery

これは、実際のWebサービスをリモートマシンまたはアプリ内でローカルに実装するよりもかなり簡単です。トレードオフは、HTTP応答ヘッダーのシミュレーションがはるかに難しいことです。

6
Phil Willoughby