私は、一連のリモートWebサービスとの通信を主な目的とするiOSアプリに取り組んでいます。統合テストでは、予測可能な結果をもたらすある種の偽のWebサービスに対してアプリを実行できるようにしたいと考えています。
これまでのところ、2つの提案を見てきました。
コミュニティがこのアプローチのそれぞれについてどう考えているか、そしてこのワークフローをサポートするツールがそこにあるかどうか知りたいです。
更新:次に、具体的な例を示します。ユーザー名とパスワードを入力するログインフォームがあります。 2つの条件を確認したいと思います。
したがって、usernameパラメータを確認して適切な応答をスローするためのコードが必要です。うまくいけば、それが「偽のWebサービス」で必要なすべてのロジックです。これをきれいに管理するにはどうすればよいですか?
オプション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サービスに対してテストする必要があります。実際に何を記述しているのかは、統合テストよりも機能テストに似ています。
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を使用する利点は次のとおりです。
主な制限は、AFNetworking、MKNetworkKit、プレーンなNSURLConnectionなど、NSURLConnectionの上に構築されたHTTPフレームワークでのみ機能することです。
お役に立てれば。他に必要なことがあれば、私がお手伝いします。
私はあなたが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')
。
再生シナリオ:
MockLoginForm.setName('wronuser');
MockLoginForm.setPassword('foo');
MockLoginForm.authenticate();
期待:
getAuthenticator() is called
MockAuthenticator.isRegistered('wrognuser') is called and returns 'false'
再生シナリオ:
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'
再生シナリオ:
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'
これがお役に立てば幸いです。
OHTTPStubs は、多くの牽引力を得ている、やりたいことを実行するための非常に優れたフレームワークです。彼らのgithub readmeから:
OHTTPStubsは、ネットワーク要求を非常に簡単にスタブ化するように設計されたライブラリです。それはあなたを助けることができます:
NSURLConnection
、新しいiOS7/OSX.9のNSURLSession
、AFNetworking
(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ページ を見つけることができます。
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応答ヘッダーのシミュレーションがはるかに難しいことです。