我如何单元测试/模拟ElasticSearch

时间:2015-12-07 19:16:25

标签: scala unit-testing elasticsearch

首先,我使用Scala和sbt作为我的应用程序。

我使用elastic4s库的ElasticClient连接到我的ES实例。所以基本上我只需要能够在我的单元测试中测试这些。比如只是验证我的数据实际上已经成为ES和类似的东西。

嘲笑ElasticSearch是最好的方法还是有更有效的方法呢?我将如何处理其中任何一个?

我发现您可以使用ElasticClient.local设置本地客户端,但我似乎找不到很多示例。我们希望继续使用此实现,因此如果您知道如何使用此功能,我希望了解它,但如果有更好或更简单的方法来完成此功能,那么就可以使用。

6 个答案:

答案 0 :(得分:2)

因为elastic search is java而你的代码太过(或兼容),所以最好的办法就是找出一种方法来启动弹性搜索" embeddable" - 只需使用@Before方法启动服务器,然后将其关闭/清除@After

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

答案 1 :(得分:2)

在我自己的代码中,我最近写了一个小的Embeddable弹性搜索进行测试。它将内容存储在磁盘上,然后可以在使用后删除文件。我用它来运行我的各种弹性搜索单元测试。

它构成一个单节点的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。

答案 2 :(得分:1)

对于我们的ElasticSearch测试,我们在每个测试使用的Jenkins构建服务器上使用永远在线的ElasticSearch实例。对于本地测试,您必须启动本地ElasticSearch。我们使用rest接口,而不是java api。

为了使单元测试可并行化,我们使用全局同步名称池(用于索引名称)。每个测试都可以配置索引定义json,如果可以在脏(=已填充)索引上运行。一个小的测试超类(scalatest)将从池中获取索引名称。如果需要干净的索引,则删除(可能)现有索引并创建新索引。如果测试接受脏索引,则检查索引定义是否与配置的索引定义相同。如果没有,则还会重新创建索引。

根据您的测试用例,这使您可以使用一些将在一段时间内重新创建的索引,并经常通过测试重用,从而加快测试执行速度。

答案 3 :(得分:0)

我使用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应用程序。

当然,这是用于单元测试的。

答案 4 :(得分:0)

在没有物理网络设施的情况下回答问题:

使用 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:

  • 索引响应
  • 搜索响应

答案 5 :(得分:0)

use strict;
use warnings;

my $var = "abc thisis long long stringggggg input=xxx WARD=yyy abcsdasdasd";
chomp($var);
my @ext = $var =~ /\b(WARD)\W+(\w+)/g;
print "@ext\n";