Scala 如何进行单元测试/模拟ElasticSearch
首先,我在应用程序中使用Scala和sbt 我正在使用elastic4s库的ElasticClient连接到我的ES实例。所以基本上我只需要能够在我的单元测试中测试这些。比如验证我的数据是否真的变成了ES之类的东西 模仿ElasticSearch是最好的方法还是有更有效的方法?我该怎么做呢 我发现您可以使用ElasticClient.local设置本地客户端,但我似乎找不到很多示例。我们希望使用此实现,因此如果您知道如何使用此功能,我很想听听您的看法,但如果有更好或更简单的方法来完成此功能,则会起作用。因为您的代码太多(或兼容),最好的方法是找到一种引导弹性搜索“可嵌入”的方法-只需在Scala 如何进行单元测试/模拟ElasticSearch,scala,unit-testing,
elasticsearch,Scala,Unit Testing,
elasticsearch,首先,我在应用程序中使用Scala和sbt 我正在使用elastic4s库的ElasticClient连接到我的ES实例。所以基本上我只需要能够在我的单元测试中测试这些。比如验证我的数据是否真的变成了ES之类的东西 模仿ElasticSearch是最好的方法还是有更有效的方法?我该怎么做呢 我发现您可以使用ElasticClient.local设置本地客户端,但我似乎找不到很多示例。我们希望使用此实现,因此如果您知道如何使用此功能,我很想听听您的看法,但如果有更好或更简单的方法来完成此功能,则会
@Before
方法中启动服务器,然后在@After
中关闭/清除服务器即可
幸运的是,似乎有人已经有了完全相同的想法-对于我们的ElasticSearch测试,我们在Jenkins build服务器上使用了一个始终运行的ElasticSearch实例,每个测试都使用该实例。对于本地测试,您必须启动本地ElasticSearch。我们使用rest接口,而不是JavaAPI 为了使单元测试可并行化,我们使用一个全局的、同步的名称池(用于索引名)。每个测试都可以配置一个索引定义json,如果可以在脏(=已填充)索引上运行。一个小的测试超类(scalatest)将从池中获取索引名。如果需要干净的索引,则会删除(可能)现有索引并创建新索引。如果测试接受脏索引,将检查索引定义是否与配置的索引定义相同。如果没有,也会重新创建索引
根据您的测试用例,这使您能够使用一些索引,这些索引将偶尔重新创建一次,并经常被测试重用,从而加快测试执行速度。在我自己的代码中,我最近编写了一个用于测试的小型可嵌入弹性搜索。它将东西存储在磁盘上,使用后可以删除文件。我用它来运行我的各种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。我使用junit+mock server(),这将在localhost:9200上打开一个Netty并侦听传入连接
您必须绑定以下规则:
- “/”->给出ES版本
- “/\u搜索”->发送此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")); }
当然,这是用于单元测试。要回答没有物理网络设施的问题: 使用
SearchRequest
和SearchResponse
import com.sksamuel.elastic4s.{RequestSuccess,Executor,Functor,Handler,JacksonSupport}
导入com.sksamuel.elastic4s.requests.searches.{SearchHit,SearchHits,SearchRequest,SearchResponse,Total}
def toSearchResponse(点击:数组[SearchHit]):SearchResponse={
val searchHits=searchHits(总数(hits.size,“”),10,hits)
//成功,失败,全部
val shards=碎片(1,1,1)
SearchResponse(10L,//take
false,//isTimedOut
false,//isTerminatedEarly
Map.empty,//建议
碎片,//\u碎片
无,//滚动ID
Map.empty,//\u聚合asmap
searchHits)//hits
}
def jsonToHit():SearchHit={
val json=JacksonSupport.mapper.readTree(
"""{
“_id”:“b141597ad13f9a3af3879c5ea64761f7”,
“\u索引”:“开发人员本地采集人员”,
“_分数”:4.9972124,
“\u类型”:“\u单据”,
“_source”:{“some”:“elasticsearch response”}
}"""
)
JacksonSupport.mapper.treeToValue[SearchHit](json)
}
val mockElasticClient=mock[ElasticClient]
val searchResponse=toSearchResponse(数组(jsonToHit))
val futureResponse=未来{
RequestSuccess[SearchResponse](200,有些(“”),Map.empty,SearchResponse)
}
(mockElasticClient.execute(\ux:SearchRequest)(
_:遗嘱执行人[未来],
_:函子[未来],
_:Handler[SearchRequest,SearchResponse],
_:清单[SearchResponse])
).预期(*,*,*,*,*,*)。返回(未来响应)
这可以概括为这样的概念
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:
- 索引请求
- 搜索请求
- 指数响应
- 搜索响应
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");