単体テストは、小さなコードをテストするものです(ほとんどが単一のメソッドです)。
統合テストは、コードの複数の領域間の相互作用をテストするテストです(すでに独自のユニットテストがあることが望ましい)。テスト対象のコードの一部では、特定の方法で動作するために他のコードが必要になる場合があります。これがモック&スタブの出番です。そのため、非常に具体的に実行するためにコードの一部をモック/スタブします。これにより、統合テストを副作用なしで予測どおりに実行できます。
すべてのテストは、データを共有せずにスタンドアロンで実行できる必要があります。データ共有が必要な場合、これはシステムが十分に分離されていないことを示しています。
外部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つのアプローチは、次の手順です。
testCreateAccount
testCreateDataFeed
にエラーがないことをtestCreateAccount
に依存させるtestCreateDataFeed
で、新しいアカウントを作成しますこれにより、さらに疑問が生じます。アカウント/データフィードの削除をテストするにはどうすればよいですか? testCreateDataFeed
が汚い-データフィードの作成に失敗した場合テストは失敗するため、サブアカウントは削除されません...作成せずに削除をテストすることはできません。そのため、作成する前にtestDeleteAccount
に依存する別のテスト(testCreateAccount
)を記述します独自のアカウントを削除します(テスト間でデータを共有しないでください)。
関連:
これは、 1つは既に指定されています に対する追加の回答です。
コードを見ると、class GoogleAPIRequest
にはclass Request
のハードエンコードされた依存関係があります。これにより、リクエストクラスとは独立してテストできないため、リクエストをモックすることはできません。
リクエストを注入可能にする必要があるため、テスト中にリクエストをモックに変更できます。これで、実際のAPI HTTPリクエストは送信されず、ライブデータは変更されず、より迅速にテストできます。
接続先のAPIが更新されたため、最近ライブラリを更新する必要がありました。
私の知識は詳細に説明するのに十分ではありませんが、コードを見ることで多くを学びました。 https://github.com/gridiron-guru/FantasyDataAPI
通常のAPIの場合と同じようにリクエストを送信し、そのレスポンスをjsonファイルとして保存し、それをモックとして使用できます。
Guzzleを使用してAPIに接続するこのライブラリのテストをご覧ください。
APIからの応答を模倣しています。ドキュメントには、テストがどのように機能するかに関する多くの情報があります。
ただし、基本的には、必要なパラメーターとともにAPIを手動で呼び出し、応答をjsonファイルとして保存します。
API呼び出しのテストを作成し、同じパラメーターを送信して、ライブAPIを使用するのではなく、模擬でロードするようにすると、作成した模擬のデータに期待値が含まれているかをテストできます。
問題のAPIの私の更新バージョンはここにあります。 更新されたレポ
外部APIをテストする方法の1つは、あなたが述べたように、モックを作成し、理解したとおりにハードコードされた動作でモックを作成することです。
このタイプのテストを「契約ベースの」テストと呼ぶ場合があります。このテストでは、観察およびコーディングした動作に基づいてAPIに対してテストを記述でき、それらのテストが失敗すると「契約が破られます」。ダミーデータを使用した単純なRESTベースのテストの場合は、外部プロバイダーに提供して実行することもできます。バージョンを作成するか、下位互換性がないという警告を生成します。
参照: https://www.thoughtworks.com/radar/techniques/consumer-driven-contract-testing
高い投票の答えが言うことの上に構築します。
あなたのコードにそれをさせる
$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);