1

SQLite を DB として使用している単体テストの既存のセットを含むアプリケーションがあります。私は最近、ES を介して検索機能を追加しました。これは、DB を直接クエリするために使用されていたエンドポイント アクションの多くを置き換えました。ES 自体をテストせずに、これらのエンドポイントに関連するすべてのビジネス ロジックをテストしたいので、ES サーバーを使用できません。実行頻度を減らすために、一連の統合テストで ES 自体をテストする予定です。

私の問題は、実行フローで何が起こっているかを正確に追跡しようとすることです。

私の最初の傾向は、インデックス用に FOSElasticaBundle が作成する ES Finder のモック オブジェクトを単純に作成することでした。ページネーションを使用しているため、思ったよりも複雑であることがわかりました。

    // code context: test method in unit test extending Symfony's WebTestCase
    $client = $this->getClient();


    $expectedHitCount = 10;

    // Setup real objects which (as far as I can tell) don't act upon the ES client
    // and instead only hold / manipulate the data.
    $responseString = file_get_contents(static::SEARCH_RESULT_FILE_RESOURCE);
    $query = SearchRepository::getProximitySearchQuery($lat, $lng, $radius, $offset, $limit);
    $response = new Response($responseString, 200);        
    $resultSet = new RawPartialResults(new ResultSet($response, $query ));

    // Create a mock pagination adapter which is what my service expects to be returned from
    // the search repository.
    $adapter = $this->getMockBuilder('FOS\ElasticaBundle\Paginator\RawPaginatorAdapter')
                    ->disableOriginalConstructor()
                    ->getMock();
    $adapter->method('getTotalHits')->will($this->returnValue($expectedTotalCount));
    $adapter->method('getResults')->will($this->returnValue($resultSet));
    $adapter->method('getQuery')->will($this->returnValue($query));

    $es = $this->getMockBuilder(get_class($client->getContainer()->get(static::ES_FINDER_SERVICE)))
               ->disableOriginalConstructor()
               ->getMock();
    $es->method('createPaginatorAdapter')->will($this->returnValue($adapter));

    // Replace the client container's service definition with our mock object
    $client->getContainer()->set(static::ES_FINDER_SERVICE, $es);

これは実際には、コントローラーからビューを返すまでずっと機能します。私のサービスは、ファイルに保存した (その後 ResultSet オブジェクトに渡した) JSON 検索応答から事前に入力された結果セットを含むモック paginatior アダプターを返します。ただし、ビューを返すと、既に渡した ResultSet を使用する代わりに、クエリを使用して ES を再度クエリしようとするリスナーが関係しているようです。

このリスナーが見つからないようです。また、 ResuletSet が既に存在するときにクエリを実行しようとする理由もわかりません。

私は FOSRestBundle も使用しており、それらの ViewListener を使用して、返されたものを自動シリアル化しています。その流れにも容疑者は見えません。結果セットのシリアル化と関係があるのではないかと思いますが、これまでのところ、問題のあるコードを追跡できていません。

以前にこれと同様のことを試みた人はいますか?現在のセットアップをデバッグする方法、またはこのタイプのテストで ES をモックするための代替のより良いセットアップのいずれかについて提案がありますか?

4

2 に答える 2

3

掘り下げた後、モックオブジェクトを使用しない代替ソリューションを見つけました。誰かがより良いアプローチをとった場合に備えて、当分の間これを開いたままにしますが、その間に取ることにしたアプローチは、テスト環境でクライアントをオーバーライドすることです。

FOSElasticaBundle には、クライアントをオーバーライドする例があります: https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/cookbook/suppress-server-errors.md

リクエストから一意のキーを作成し、そのキーに基づいて応答を提供できるようにクライアントをオーバーライドし、基本的にすべての既知のリクエストに対してサーバーをスタブ化することができました。一致しないリクエストについては、デフォルトの空のレスポンスを返します。これは私にとっては十分に機能します。

クライアントコード

<?php

namespace Acme\DemoBundle\Tests\Elastica;

use Elastica\Request;
use Elastica\Response;
use FOS\ElasticaBundle\Client as BaseClient;

class Client extends BaseClient
{
    /**
     * This array translates a key which is the md5 hash of the Request::toString() into
     * a human friendly name so that we can load the proper response from a file in the
     * file system.
     * 
     * @var array
     */
    protected $responseLookup = array(
        '7fea3dda860a424aa974b44f508b6678' => 'proximity-search-response.json'
    );

    /**
     * {@inheritdoc}
     */
    public function request($path, $method = Request::GET, $data = array(), array $query = array())
    {
        $request = new Request($path, $method, $data, $query);
        $requestKey = md5($request->toString());
        $this->_log($request);
        $this->_log("Test request lookup key: $requestKey");

        if (!isset($this->responseLookup[$requestKey])
            || !$response = file_get_contents(__DIR__ . "/../DataFixtures/Resources/search/{$this->responseLookup[$requestKey]}")) {
            return $this->getNullResponse();
        }

        return new Response($response);
    }

    public function getNullResponse()
    {
        $this->_log("Returning NULL response");
        return new Response('{"took":0,"timed_out":false,"hits":{"total":0,"max_score":0,"hits":[]}}');
    }
}

構成変更

// file: config_test.yml
parameters:
    fos_elastica.client.class: Acme\DemoBundle\Tests\Elastica\Client

サンプル応答ファイル (proximity-search-response.json)

{
  "took": 7,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": null,
    "hits": [
      {
        "_index": "search",
        "_type": "place",
        "_id": "1",
        "_score": null,
        "_source": {
          "location": "40.849100,-73.644800",
          "id": 1,
          "name": "My Place"
        },
        "sort": [
          322.52855474383045
        ]
      }
    ]
  }
}

このソリューションはうまく機能し、高速ですが、メンテナンスが面倒です。リクエストに関する変更があった場合は、ログから新しいリクエスト キーを取得し、配列内でそれを更新し、新しいリクエストの新しいレスポンス データでファイルを更新する必要があります。私は通常、サーバーを直接カールさせ、そこから変更します。

もっと簡単な他の解決策を見たいと思っていますが、それまでの間、これが他の誰かに役立つことを願っています!

于 2014-07-26T06:01:27.150 に答える
1

config_test.yml (またはテスト環境名) でイベント リスナーを無効にすることができます。

fos_elastica:
    indexes:
        your_index_name:
            types:
                your_type_name:
                    persistence:
                        listener:
                            insert: false
                            update: false
                            delete: false
于 2014-09-01T17:13:56.670 に答える