簡體   English   中英

我如何單元測試/模擬 ElasticSearch

[英]How do I Unit test/mock ElasticSearch

首先,我在我的應用程序中使用 Scala 和 sbt。

我正在使用 elastic4s 庫的 ElasticClient 連接到我的 ES 實例。 所以基本上我只需要能夠在我的單元測試中測試這些。 比如驗證我的數據是否真的進入了 ES 之類的東西。

嘲笑 ElasticSearch 是最好的方法還是有更有效的方法? 我將如何處理其中任何一個?

我發現您可以使用 ElasticClient.local 設置本地客戶端,但我似乎找不到很多示例。 我們想使用這個實現,所以如果你知道如何使用它,我想聽聽它,但如果有更好或更簡單的方法來完成這個工作。

由於彈性的搜索是用java和你的代碼太(或兼容的)最好的辦法是找出一種方法來啟動彈性搜索“嵌入” -剛開始他們的服務器在你的@Before方法和其關閉/在清除它@After

幸運的是,似乎有人已經有了完全相同的想法 - https://orrsella.com/2014/10/28/embedded-elasticsearch-server-for-scala-integration-tests/

在我自己的代碼中,我最近編寫了一個小的 Embeddable 彈性搜索用於測試。 它將東西存儲在磁盤上,然后可以在使用后刪除文件。 我用它來運行我的各種 elasticsearch 單元測試。

它構成了一個單節點的 Elasticsearch 集群。 該節點支持完整的elasticsearch API。

 /**
 * A simple embeddable Elasticsearch server. This is great for integration testing and also
 * stand alone tests.
 *
 * Starts up a single ElasticSearch node and client.
 */
public class EmbeddedElasticsearchServer
{
  public EmbeddedElasticsearchServer(String storagePath) {

    storagePath_ = storagePath;
    ImmutableSettings.Builder elasticsearchSettings = ImmutableSettings.settingsBuilder()
      .put("http.enabled", "false")
      .put("path.data", storagePath_);

    node_ = new NodeBuilder()
      .local(true)
      .settings(elasticsearchSettings.build())
      .node();

    client_ = node_.client();
  }



  public Client getClient() {
    return client_;
  }


  public void shutdown()
  {
    node_.close();
  }

  public void deleteStorage() throws IOException
  {
    File storage = new File(storagePath_);

    if(storage.exists())
    {
      FileUtils.deleteDirectory(storage);
    }

  }

  private Client client_;
  private Node node_;
  private String storagePath_;
}

要使用它,只需調用 getClient ,然后您就可以使用 Elasticsearch Java API 就好了。

對於我們的 ElasticSearch 測試,我們在每個測試使用的 Jenkins 構建服務器上使用一個始終在線的 ElasticSearch 實例。 對於本地測試,您必須啟動本地 ElasticSearch。 我們使用rest接口,而不是java api。

為了使單元測試可並行化,我們使用全局同步名稱池(用於索引名稱)。 每個測試都可以配置一個索引定義 json,如果可以在臟的(= 已經填充的)索引上運行。 一個小的測試超類(scalatest)將從池中獲取一個索引名稱。 如果需要一個干凈的索引,則刪除(可能)現有索引並創建新索引。 如果測試接受臟索引,則檢查索引定義是否與配置的相同。 如果沒有,索引也會重新創建。

根據您的測試用例,這使您可以使用一些索引,這些索引將不時重新創建,並經常被測試重用,從而加快測試執行速度。

我使用 junit + 模擬服務器( http://www.mock-server.com/ ),這會在 localhost:9200 上打開一個 Netty 並偵聽傳入連接。

您必須綁定規則,例如:

  • "/" -> 給出 ES 版本
  • "/_search" -> 發送這個 JSON
  • 等等..

     @Rule public MockServerRule mockServerRule = new MockServerRule(9200, this); private MockServerClient mockClient; @Before public void setup() { mockClient.when( HttpRequest.request("/") ).respond(HttpResponse.response("{\\"name\\" : \\"mock\\", \\"cluster_name\\" : \\"mock\\", \\"version\\": { \\"number\\" : \\"2.4\\"}}").withHeader("Content-Type", "application/json")); mockClient.when( HttpRequest.request() .withPath("*/_count") ).respond(HttpResponse.response("{\\"count\\": 0}").withHeader("Content-Type", "application/json")); mockClient.when( HttpRequest.request() .withPath("*/_search") ).respond(HttpResponse.response("{\\"hits\\": {\\"total\\" : 0}}").withHeader("Content-Type", "application/json")); mockClient.when( HttpRequest.request() ).respond(HttpResponse.response("{\\"ok\\":\\"2.4\\"}").withHeader("Content-Type", "application/json")); }

這不是保存任何數據,它是一個非常簡單的模擬。 我正在使用它來測試使用 elasticsearch-hadoop 插件的 Java Spark 應用程序。

當然,這是為了單元測試。

要在沒有物理網絡設施的情況下回答問題:

使用SearchRequestSearchResponse的示例

import com.sksamuel.elastic4s.{RequestSuccess, Executor, Functor, Handler, JacksonSupport}
import com.sksamuel.elastic4s.requests.searches.{SearchHit, SearchHits, SearchRequest, SearchResponse, Total}

def toSearchResponse(hits: Array[SearchHit]): SearchResponse = {
  val searchHits = SearchHits(Total(hits.size,""), 10, hits)
  // successful, failed, total
  val shards = Shards(1, 1, 1)
  SearchResponse(10L, //took
    false, // isTimedOut
    false, // isTerminatedEarly
    Map.empty, // suggest
    shards, // _shards
    None, // scrollId
    Map.empty, // _aggregationsAsMap
    searchHits) // hits
}

def jsonToHit(): SearchHit = {
    val json = JacksonSupport.mapper.readTree(
      """{
        "_id": "b141597ad13f9a3af3879c5ea64761f7",
        "_index": "dev_local_collectr_person",
        "_score": 4.9972124,
        "_type": "_doc",
        "_source": {"some":"elasticsearch response"}
      }"""
    )

    JacksonSupport.mapper.treeToValue[SearchHit](json)
}

val mockElasticClient = mock[ElasticClient]
val searchResponse = toSearchResponse(Array(jsonToHit))
val futureResponse = Future {
    RequestSuccess[SearchResponse](200, Some(""), Map.empty, searchResponse)
}

(mockElasticClient.execute(_: SearchRequest)(
    _: Executor[Future],
    _: Functor[Future],
    _: Handler[SearchRequest,SearchResponse],
    _: Manifest[SearchResponse])
).expects(*, *, *, *, *).returning(futureResponse)

這可以概括為這樣的概念

def to[U](hits: Array[SearchHit]): U = {
  val searchHits = SearchHits(Total(hits.size,""), 10, hits)
  // successful, failed, total
  val shards = Shards(1, 1, 1)
  U( ... ) 
}

val mockElasticClient = mock[ElasticClient]
val searchResponse = toSearchResponse(Array(jsonToHit))
val futureResponse = Future {
    RequestSuccess[U](200, Some(""), Map.empty, searchResponse)
}

(mockElasticClient.execute(_: T)(
    _: Executor[Future],
    _: Functor[Future],
    _: Handler[T,U],
    _: Manifest[U])
).expects(*, *, *, *, *).returning(futureResponse)

其中T是傳遞給 execute 的請求對象, U是嵌套的返回對象。

其他一些示例 T:

  • 索引請求
  • 搜索請求

其他一些樣本 U:

  • 索引響應
  • 搜索響應
SearchResponse searchResponse = mock(SearchResponse.class);
SearchResponse scrollResponse = mock(SearchResponse.class);
when(analysisRestClient.search(any(), Mockito.any(RequestOptions.class))).thenReturn(searchResponse);
SearchHits searchHits = mock(SearchHits.class);
SearchHit searchHit = mock(SearchHit.class);
when(searchResponse.getHits()).thenReturn(searchHits);
when(searchHits.iterator()).thenReturn(Iterators.singletonIterator(searchHit));
EventDto eventDto = new EventDto();
when(searchHit.getSourceAsString()).thenReturn(JSON.toJSONString(eventDto));
when(searchHits.getTotalHits()).thenReturn(1L);
when(analysisRestClient.scroll(any(), Mockito.any(RequestOptions.class))).thenReturn(scrollResponse);
when(searchResponse.getScrollId()).thenReturn("ddd");

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM