web-dev-qa-db-ja.com

外部APIと対話するための統合テストはどのように書かれていますか?

まず、私の知識は次のとおりです。

単体テストは、小さなコードをテストするものです(ほとんどが単一のメソッドです)。

統合テストは、コードの複数の領域間の相互作用をテストするテストです(すでに独自のユニットテストがあることが望ましい)。テスト対象のコードの一部では、特定の方法で動作するために他のコードが必要になる場合があります。これがモック&スタブの出番です。そのため、非常に具体的に実行するためにコードの一部をモック/スタブします。これにより、統合テストを副作用なしで予測どおりに実行できます。

すべてのテストは、データを共有せずにスタンドアロンで実行できる必要があります。データ共有が必要な場合、これはシステムが十分に分離されていないことを示しています。

次に、私が直面している状況:

外部API(具体的には、POST要求)でライブデータを変更するRESTful API)と対話するとき、そのAPIとの対話を(より雄弁に)モックアウトできることを理解する必要があります。統合テストについては この回答 )に記載されています。また、そのAPIとやり取りする個々のコンポーネント(要求の構築、結果の解析、エラーのスローなど)の単体テストができることも理解しています。取得する方法は、実際にこれを行う方法ではありません。

だから、最後に:私の質問。

副作用のある外部APIとの相互作用をテストするにはどうすればよいですか?

完璧な例は、 Googleのショッピング用Content API です。手元のタスクを実行できるようにするには、かなりの量の準備作業が必要です。次に、実際の要求を実行し、戻り値を分析します。その一部は 「サンドボックス」環境なし です。

これを行うためのコードには、一般に次のような抽象化の層がかなりあります。

<?php
class Request
{
    public function setUrl(..){ /* ... */ }
    public function setData(..){ /* ... */ }
    public function setHeaders(..){ /* ... */ }
    public function execute(..){
        // Do some CURL request or some-such
    }   
    public function wasSuccessful(){
        // some test to see if the CURL request was successful
    }   
}

class GoogleAPIRequest
{
    private $request;
    abstract protected function getUrl();
    abstract protected function getData();

    public function __construct() {
        $this->request = new Request();
        $this->request->setUrl($this->getUrl());
        $this->request->setData($this->getData());
        $this->request->setHeaders($this->getHeaders());
    }   

    public function doRequest() {
        $this->request->execute();
    }   
    public function wasSuccessful() {
        return ($this->request->wasSuccessful() && $this->parseResult());
    }   
    private function parseResult() {
        // return false when result can't be parsed
    }   

    protected function getHeaders() {
        // return some GoogleAPI specific headers
    }   
}

class CreateSubAccountRequest extends GoogleAPIRequest
{
    private $dataObject;

    public function __construct($dataObject) {
        parent::__construct();
        $this->dataObject = $dataObject;
    }   
    protected function getUrl() {
        return "http://...";
    }
    protected function getData() {
        return $this->dataObject->getSomeValue();
    }
}

class aTest
{
    public function testTheRequest() {
        $dataObject = getSomeDataObject(..);
        $request = new CreateSubAccountRequest($dataObject);
        $request->doRequest();
        $this->assertTrue($request->wasSuccessful());
    }
}
?>

注:これはPHP5/PHPUnitの例です

testTheRequestがテストスイートによって呼び出されるメソッドである場合、この例はライブリクエストを実行します。

これで、このライブリクエストは(うまくいけば、すべてがうまくいけば)、ライブデータを変更する副作用があるPOSTリクエストを行います。

これは受け入れられますか?どのような選択肢がありますか?テスト用のRequestオブジェクトをモックアウトする方法がわかりません。そして、私がやったとしても、GoogleのAPIが受け入れるすべての可能なコードパスの結果/エントリポイントを設定することを意味します(この場合は試行錯誤で見つける必要があります)が、フィクスチャの使用を許可します。

さらなる拡張は、特定のリクエストが特定のデータがすでにライブであることに依存している場合です。再び例としてGoogle Content APIを使用して、データフィードをサブアカウントに追加するには、サブアカウントが既に存在している必要があります。

私が考えることができる1つのアプローチは、次の手順です。

  1. testCreateAccount
    1. サブアカウントを作成する
    2. サブアカウントが作成されたことを表明する
    3. サブアカウントを削除する
  2. testCreateDataFeedにエラーがないことをtestCreateAccountに依存させる
    1. testCreateDataFeedで、新しいアカウントを作成します
    2. データフィードを作成する
    3. データフィードが作成されたことをアサートする
    4. データフィードを削除する
    5. サブアカウントを削除する

これにより、さらに疑問が生じます。アカウント/データフィードの削除をテストするにはどうすればよいですか? testCreateDataFeedが汚い-データフィードの作成に失敗した場合テストは失敗するため、サブアカウントは削除されません...作成せずに削除をテストすることはできません。そのため、作成する前にtestDeleteAccountに依存する別のテスト(testCreateAccount)を記述します独自のアカウントを削除します(テスト間でデータを共有しないでください)。

要約すれば

  • ライブデータに影響する外部APIとのやり取りをテストするにはどうすればよいですか?
  • 抽象化レイヤーの背後に隠されている場合、統合テストでオブジェクトをモック/スタブするにはどうすればよいですか?
  • テストが失敗し、ライブデータが一貫性のない状態のままになった場合、どうすればよいですか?
  • コードでどのように実際にこれをすべて行うのですか?

関連:

69
Jess Telford

これは、 1つは既に指定されています に対する追加の回答です。

コードを見ると、class GoogleAPIRequestにはclass Requestのハードエンコードされた依存関係があります。これにより、リクエストクラスとは独立してテストできないため、リクエストをモックすることはできません。

リクエストを注入可能にする必要があるため、テスト中にリクエストをモックに変更できます。これで、実際のAPI HTTPリクエストは送信されず、ライブデータは変更されず、より迅速にテストできます。

8
hakre

接続先のAPIが更新されたため、最近ライブラリを更新する必要がありました。

私の知識は詳細に説明するのに十分ではありませんが、コードを見ることで多くを学びました。 https://github.com/gridiron-guru/FantasyDataAPI

通常のAPIの場合と同じようにリクエストを送信し、そのレスポンスをjsonファイルとして保存し、それをモックとして使用できます。

Guzzleを使用してAPIに接続するこのライブラリのテストをご覧ください。

APIからの応答を模倣しています。ドキュメントには、テストがどのように機能するかに関する多くの情報があります。

ただし、基本的には、必要なパラメーターとともにAPIを手動で呼び出し、応答をjsonファイルとして保存します。

API呼び出しのテストを作成し、同じパラメーターを送信して、ライブAPIを使用するのではなく、模擬でロードするようにすると、作成した模擬のデータに期待値が含まれているかをテストできます。

問題のAPIの私の更新バージョンはここにあります。 更新されたレポ

1

外部APIをテストする方法の1つは、あなたが述べたように、モックを作成し、理解したとおりにハードコードされた動作でモックを作成することです。

このタイプのテストを「契約ベースの」テストと呼ぶ場合があります。このテストでは、観察およびコーディングした動作に基づいてAPIに対してテストを記述でき、それらのテストが失敗すると「契約が破られます」。ダミーデータを使用した単純なRESTベースのテストの場合は、外部プロバイダーに提供して実行することもできます。バージョンを作成するか、下位互換性がないという警告を生成します。

参照: https://www.thoughtworks.com/radar/techniques/consumer-driven-contract-testing

0
dragon788

高い投票の答えが言うことの上に構築します。

  1. モックカールオブジェクトを作成しました
  2. 想定されるパラメーターをモックに伝えます
  3. 関数内のcurl呼び出しの応答をモックアウトします
  4. あなたのコードにそれをさせる

    $curlMock = $this->getMockBuilder('\Curl\Curl')
                     ->setMethods(['get'])
                     ->getMock();
    
    $curlMock
        ->expects($this->once())
        ->method('get')
        ->with($URL .  '/users/' . urlencode($userId));
    
    $rawResponse = <<<EOL
    {
         "success": true,
         "result": {
         ....
         }
    }
    EOL;
    
    $curlMock->rawResponse = $rawResponse;
    $curlMock->error = null;
    
    $apiService->curl = $curlMock;
    
    // call the function that inherently consumes the API via curl
    $result = $apiService->getUser($userId);
    
    $this->assertTrue($result);
    
0
Reza S