モバイルクライアントなしでサーバー側コードをテストする方法



Badooは、主要なプラットフォーム向けのWebサイトおよびモバイルアプリケーションとして利用可能なデートサービスです。 昨年の初めに、私たちはサイトをグローバルに再設計しました。その結果、サイトは「脂肪クライアント」に変わり、モバイルアプリケーションと同じように動作し始めました。 これらの2つの部分は異なる開発者によって作成され、原則として、サーバーの準備が整った後にクライアント部分が行われます。 問題があります:新しい機能の開発者は、まだクライアントがなく、チェックするものがない場合にサーバーパーツが正常に動作していることを確認するにはどうすればよいですか?







サーバータスクでこの問題を解決するには、この記事で説明する統合テストを作成する必要があります。







それは何であり、どのように機能しますか?



私たちの場合、これらのテストはPHPUnitのアドオンであり、そのおかげでテストはプロトコルを使用してサーバーにアクセスするクライアントアプリケーションになります。 同時に、アクセスするサーバーを構成することもできます。 可能性があります:









最初のケースでは、クライアントとサーバーの両方が1つのPHPプロセスのフレームワーク内で動作し、残りはテストが他のサーバーにリクエストを送信する際の本格的なクライアントサーバーになります。







他のユーザーにギフトを提示したユーザーが自分のプロファイルにこのギフトを表示することを確認する同様のテストの例を次に示します。







class ServerGetUserGiftsTest extends BmaFunctionalTestCase { public function testGiftsSending() { // Given $ClientGiftSender = $this->getLoginedConnection( \BmaFunctionalConfig::USER_TYPE_NEW, [ 'app_build' => 'Android', 'supported_features' => [ \Mobile\Proto\Enum\FeatureType::ALLOW_GIFTS, ], ] ); $ClientGiftReceiver = $this->getLoginedConnection(); $gift_type = 1; $gift_add_result = $ClientGiftSender->QaApiClient->addGiftToUser( $ClientGiftReceiver->getUserId(), $ClientGiftSender->getUserId(), $gift_type ); $this->assertGiftAddSuccess($gift_add_result, "Precondition failed: cannot add gift from sender to receiver"); // When $Response = $ClientGiftSender->ServerGetUser( [ 'user_id' => $ClientGiftReceiver->getUserId(), 'client_source' => \Mobile\Proto\Enum\ClientSource::OTHER_PROFILE, 'user_field_filter' => [ 'projection' => [\Mobile\Proto\Enum\UserField::RECEIVED_GIFTS], ], ] ); // Then $this->assertResponseHasMessageType(\Mobile\Proto\Enum\MessageType::CLIENT_USER, $Response); $user_received_gifts = $Response->CLIENT_USER['received_gifts']; $this->assertArrayHasKey('gifts', $user_received_gifts, "No gifts list at received_gifts field"); $this->assertCount(1, $user_received_gifts['gifts'], "Unexpected received gifts count"); $gift_info = reset($user_received_gifts['gifts']); $this->assertEquals($ClientGiftSender->getUserId(), $gift_info['from_user_id'], "Wrong from_user_id value"); } }
      
      





この例を部分的に見てみましょう。







各テストは、BmaFunctionalTestCaseクラス(PHPUnit_Framework_TestCaseの下位クラス)を継承します。 いくつかの補助メソッドを実装します。その主なものは、サーバーにリクエストを送信できるクライアントオブジェクトを取得する機能です。







 $ClientGiftSender = $this->getLoginedConnection( \BmaFunctionalConfig::USER_TYPE_MALE, [ 'app_build' => 'Android', 'supported_features' => [\Mobile\Proto\Enum\FeatureType::ALLOW_GIFTS], ] );
      
      





ここでは、サポートされている独自の機能セットを使用して、特定のバージョンのクライアントに「自己紹介」できます。 このメソッドを実行すると、特定のアプリケーションを使用して登録ユーザーに代わってリクエストを送信できるオブジェクトができます。







この登録ユーザーは、 テストユーザーの特別なプールから取得します 。 一定数の「クリーンな」ユーザー、つまり それらはすべて同じ初期状態です。 テストでgetLoginedConnection()メソッドが呼び出されると、これらのユーザーのいずれかが選択され、他のテストでの使用がブロックされます。 既知の状態のユーザーに常に対処するために、ブロックが必要です。 このユーザーをブロックした後、任意の操作を実行できます。テストが完了すると、クリーニングメカニズムが起動し、ユーザーが初期の「クリーン」状態に戻り、そのユーザーが再びテストで使用できるようになります。 すべてのテストユーザーは同じ場所にいて、実際のユーザーはいません。 そのため、一方ではテストでは予測可能な環境を扱い、他方では実際のユーザーにはテスト環境が表示されません。







原則として、クライアントオブジェクトを受け取った直後にスキャンを実行することはできません。 テストに必要な環境を作成する必要があります (この例では、ギフトを別のユーザーに送信します)。 クライアントオブジェクトを介してサーバーにリクエストを送信することにより、これを「正直に」行うことができますが、これが常に可能であるとは限りません。 ギフトの場合、「正直な」方法は複雑すぎます。ユーザーのアカウントを補充し、利用可能なギフトのリストを取得し、送信し、送信スクリプトで処理されるのを待つ必要があります。 これにより、テストが複雑になり、開発と実行の時間が長くなります。







これを簡素化するために、 QaAPIと呼ばれる内部ツールを使用します(同僚のDmitry Marushchenkoが既にそれについて話しました 。プレゼンテーションとビデオはHabrにあります )。 多くの小さなメソッドで構成され、各メソッドを使用すると、標準のメカニズムをバイパスしてユーザーに対して個別のアクションを実行したり、ユーザーに関する情報を取得したりできます。 これを使用すると、写真をユーザーに追加してすぐにモデレートし、キューをバイパスしてモデレーターが確認できます。 プロファイル内の個々のフィールドの値を変更したり、「デート」で他のユーザーに投票したりします。







この例では、アカウントを補充せずにキューをバイパスせずにギフトを提供します。







 $gift_add_result = $ClientGiftSender->QaApiClient->addGiftToUser( $ClientGiftReceiver->getUserId(), $ClientGiftSender->getUserId(), $gift_type_id ); $this->assertGiftAddSuccess($gift_add_result, "Precondition failed: cannot add gift from sender to receiver");
      
      





QaAPI応答を確認することは非常に重要です。エラーが発生した場合、ユーザーは予想される状態とはまったく異なる状態になり、それ以上のチェックは無意味になるためです。 この例について話すと、プロファイル内のギフトの存在を確認できなかった場合にそれを確認するのは奇妙です。







何らかの理由でユーザーを「正直に」正しい状態にしたくない場合は、 リモートのモックオブジェクトを使用できます 。 ローカルのものとは異なり、それらは使い捨て(1つのコマンドのみで動作)および永続的(テストの終了まで動作)です。







技術的には、モックオブジェクトは他のソリューションSoftMocksを使用して実装されます。 これは、直接(開発者のサイトで、テストが単一プロセスの一部として実行される場合)、またはmemcacheの形式で「配置」を介して(リモートサイトで)使用されます。 2番目のケースでは、テスト中に、新しいモックオブジェクトに関する情報を使い捨てまたは永続的なモックオブジェクトの配列に入れ、サーバーにリクエストを送信する前に、これら2つの配列を組み合わせて、サーバー部分がそれらを取得できるmemcacheに入れます。







必要なテキストが答えに含まれていることを確認する必要がある場合、このようなモックオブジェクトを使用してトークンをチェックすることがよくあります。 これは「正直に」行うことができますが、あまり便利ではありません。テキストは時間とともに変化する可能性があり(これによりテストが中断されます)、さらに言語によって異なる場合があります。 これらの問題を回避するために、トークンを事前定義された値に置き換えるか、テキストに進む途中で置き換えます。







一般に、モックオブジェクトを使用すると、テストが高速になります。 1つまたは複数のリモート呼び出しを取り除くことができますが、サーバーコードへの依存関係が追加され、信頼性が低下します。より頻繁に中断し、より多くの嘘をつきます。







目的の環境を作成した後、サーバーに要求を送信して応答を取得できます。







 $Response = $ClientGiftSender->ServerGetUser( [ 'user_id' => $ClientGiftReceiver->getUserId(), 'user_field_filter' => [ 'projection' => [\Mobile\Proto\Enum\UserField::RECEIVED_GIFTS], ], ] );
      
      





このようなテストでは、サーバーコードはブラックボックスを表します 。そこで何が行われているか、どのコードがリクエストを処理しているかはわかりません。 私たちにできることは、サーバーの応答が期待と一致することを確認することだけです。







このプロトコルにより、サーバーは同じコマンドに対して異なるタイプの応答を返すことができます。 コマンドは、さまざまなタイプの応答を返すことができます。 たとえば、ほとんどすべてのコマンドがエラーを返す可能性があります。 このため、期待されるタイプのメッセージがそこにあるかどうかで応答のチェックを開始します。







 $this->assertResponseHasMessageType(\Mobile\Proto\Enum\MessageType::CLIENT_USER, $Response);
      
      





適切なメッセージがあることを確認したら、回答をより詳細に確認し、ギフトにギフトが含まれていることを確認できます。







 $user_received_gifts = $Response->CLIENT_USER['received_gifts']; $this->assertArrayHasKey('gifts', $user_received_gifts, "No gifts list at received_gifts field"); $this->assertCount(1, $user_received_gifts['gifts'], "Unexpected received gifts count"); $gift_info = reset($user_received_gifts['gifts']); $this->assertEquals($ClientGiftSender->getUserId(), $gift_info['from_user_id'], "Wrong from_user_id value");
      
      





ユーザーの状態を変更するコマンドの場合、サーバーの応答を確認するだけでは不十分です。 たとえば、ギフトを削除するコマンドを送信した場合、答えが成功するだけでは不十分です。ギフトが本当に削除されたことを確認する必要もあります。 これを行うには、他のコマンドを呼び出してその回答を確認するか、確認するパラメーターの状態を返すメソッドを呼び出して同じQaAPIを使用します。 ギフトを削除する例では、QaAPIメソッドを呼び出して、ギフトのリストを返し、削除されていないことを確認できます。







利点は何ですか?



このようなテストの主な利点は、新しい機能が期待どおりに機能することを理解していることです。 このようなテストの形式でシナリオを説明して合格した場合、すべての機能が機能し、実際のクライアントアプリケーションで使用できることを理解します







別の重要なプラス:回帰テストを実施し、変更により、新しい機能を使用できない古い顧客が壊れないことを確認できます。 これらのテストにより、アプリケーションの異なるバージョン(以前のバージョンで使用していた古いパス)とクライアントでサポートされている特定の機能セット(これが現在使用している新しいパス)を示すことでこれを行うことができます。







欠点は何ですか?



これらのテストの主な欠点は、 動作時間長いことと、レベルが高いために不安定になることです。 通常、テストは1つのプロトコルコマンドの結果を確認しますが、通常のクライアントと同じデータベースとサービスで動作する本格的な環境を作成します。 これらすべてと、サーバーに対する他の要求(多くの場合1つまたは2つではない)を必要とする「正直な」環境の再作成には時間がかかります。







一部の機能では複雑な初期化が必要であり 、テストメソッドのサイズが大きくなります。 結局のところ、テストメソッドを呼び出す前に、初期化のための要求を送信するだけでなく、それらが期待どおりに機能したことを確認する必要もあります。 たとえば、チャットを確認する場合は、2人のクライアントを取得し、お互いに「チャット」する機会を与え、メッセージを送信して、実際に行ったことを確認する必要があります。 遅延が発生することがあり、データの配信を待つ必要がある場合があります。







この複雑さのために、テストは非常に「脆弱」になります。環境の再作成に失敗すると、テストが中断され、問題はテスト対象には当てはまりませんが、テストは失敗します。 そのようなテストでは、正確に何が壊れているかはわかりませんが、何かが機能していないことがわかるだけです。 特定のメソッドを探す必要がありますが、その変更によりテストが中断され、場合によってはこれが非常に困難になる可能性があります。







おわりに



上記の欠点にもかかわらず、これらのテストは問題を解決し、開発者が誰もが知っている単体テストと同じ形式でテストを作成できるようにします。







Viktor Pryazhnikov、機能開発者








All Articles