使用ElasticsearchIntegrationTest编写Elasticsearch的JUnit测试

版本信息

    Elasticsearch 1.7.4

根据本文介绍,ElasticsearchIntegrationTest从2.x版本更名为ESIntegTestCase。由此可能带来了重大变化,请注意。

首先

简要说明

这篇文章是Elasticsearch Advent Calendar 2015第三天的条目。

其实,Elasticsearch提供了一个名为ElasticsearchIntegrationTest的类,用于在Java中编写测试。不过,关于这方面的日语资料非常少甚至没有,所以我整理了一些我在调研过程中找到的信息。

使用这个工具,可以在运行测试的机器上快速启动一个简易的Elasticsearch集群,并且可以在测试用例中向该集群插入文档或进行搜索操作。这样就不需要专门启动一个Elasticsearch服务器来进行测试了!

弹性搜索公式文档(Elasticsearch 1.7)

在Elasticsearch的官方文档中提及的是以下这些文章。

    • Java Testing Framework

 

    • Using the elasticsearch test classes

 

    integration tests

考试所需的依赖关系

本文将使用Maven。为了尝试使用elasticsearch 1.7.4的ElasticsearchIntegrationTest,以下是依赖项的描述。

<dependencies>
    <dependency>
        <groupId>org.apache.lucene</groupId>
        <artifactId>lucene-test-framework</artifactId>
        <version>4.10.4</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.carrotsearch.randomizedtesting</groupId>
        <artifactId>randomizedtesting-runner</artifactId>
        <version>2.1.17</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>1.7.3</version>
        <type>test-jar</type>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>1.7.3</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-all</artifactId>
        <version>1.3</version>
        <scope>test</scope>
    </dependency>
  </dependencies>

请注意,如果描述顺序改变,可能会导致无法正常运作。

作为版本选择的方法,首先要指定自己使用的elasticsearch版本。包括test-jar的地方也有2处。本次选择了1.7.3版本。

接下来,我们需要查找当前使用的Elasticsearch所依赖的Lucene版本,并确定lucene-test-framework的版本。我认为在Eclipse的Maven POM编辑器中检查依赖关系,或者查看Maven Repository中该版本的页面可能是一个不错的方法。这次我们可以使用4.10.4版本。

最後,我們需要確定 randomizedtesting-runner 的版本,但我不知道如何查詢。我曾考慮使用 lucene-test-framework 4.10.4 所依賴的 randomizedtesting-runner 2.1.6,但似乎沒有作用。試著指定 2.1 系列的最新版本 2.1.17,結果可以運行。也許最好是在 lucene-test-framework 中排除 randomizedtesting-runner。

测试代码

我们将测试一个类,该类在构造函数中接收一个Elasticsearch客户端,并在search方法中执行一个非常简单的搜索操作。

package jp.akimateras.elasticsearch.esintegtest.service;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;

import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.index.query.MatchQueryBuilder.Operator;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;

public class SimpleSearch {
    // インデックス・タイプ名定義.
    public static final String INDEX_NAME = "simple-search";
    public static final String TYPE_NAME = "documents";

    // フィールド名定義.
    public static final String TITLE_FIELD_NAME = "title";
    public static final String TEXT_FIELD_NAME = "text";

    // 検索結果の返却型.
    public static class Document {
        public final String title;
        public final String text;

        Document(String title, String text) {
            this.title = title;
            this.text = text;
        }
    }

    // Elasticsearchクライアント.
    private final Client client;

    /**
     * コンストラクタ.
     * 
     * @param client 検索に使用するElasticsearchクライアント
     */
    public SimpleSearch(Client client) {
        this.client = client;
    }

    /**
     * 検索メソッド
     * 
     * @param keywords 検索キーワード. スペース区切りで複数のキーワードを指定した場合はAND検索とする.
     * @return 検索結果.
     */
    public Collection<Document> search(String keywords) throws IOException {
        try {
            // 検索クエリの組み立て.
            MultiMatchQueryBuilder query = QueryBuilders
                .multiMatchQuery(keywords, TITLE_FIELD_NAME, TEXT_FIELD_NAME)
                .operator(Operator.AND);

            // 検索の実行.
            SearchResponse response = client.prepareSearch(INDEX_NAME)
                .setTypes(TYPE_NAME)
                .setQuery(query)
                .execute()
                .get();

            // 検索結果を Collection<Document> に詰め替えてreturnする.
            return Arrays.stream(response.getHits().hits())
                .map(SearchHit::sourceAsMap)
                .map(source -> new Document(
                    (String) source.get(TITLE_FIELD_NAME),
                    (String) source.get(TEXT_FIELD_NAME)
                    ))
                .collect(Collectors.toList());
        } catch(Exception e) {
            throw new IOException(e);
        }
    }
}

简单的测试

让我们粗略测试一下之前创建的SimpleSearch类。

要写ElasticsearchIntegrationTest测试的话,

    1. 让ElasticsearchIntegrationTest在测试类中继承。

 

    在测试方法中调用client()会获取Elasticsearch客户端。

只要记住这两点就可以了。

package jp.akimateras.elasticsearch.esintegtest.service;

import static org.hamcrest.Matchers.is;

import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.junit.Before;
import org.junit.Test;

@ElasticsearchIntegrationTest.ClusterScope(numDataNodes = 1 /* テスト時に立ち上げるノード数 */)
public class SimpleSearchTest extends ElasticsearchIntegrationTest {
    // テスト用ノードに設定したい内容はこのメソッドの返却値として表現する.
    @Override
    protected Settings nodeSettings(int nodeOrdinal) {
        return ImmutableSettings.settingsBuilder()
            .put(super.nodeSettings(nodeOrdinal))
            .build();
    }

    // ここでインデックスを作成する.
    @Before
    public void initializeIndex() throws Exception {
        // マッピング定義.
        // 外部JSONファイルとかでマッピング定義をしているなら, そっちから読んだ方が良いと思う.
        XContentBuilder mapping = XContentFactory.jsonBuilder()
            .startObject()
                .startObject(SimpleSearch.TYPE_NAME)
                    .startObject("properties")
                        .startObject(SimpleSearch.TITLE_FIELD_NAME)
                            .field("type", "string")
                            .field("source", true)
                        .endObject()
                        .startObject(SimpleSearch.TEXT_FIELD_NAME)
                            .field("type", "string")
                            .field("source", true)
                        .endObject()
                    .endObject()
                .endObject()
            .endObject();

        // インデックス作成.
        admin().indices() // admin()でアドミンクライアントが取れる!
            .prepareCreate(SimpleSearch.INDEX_NAME)
            .addMapping(SimpleSearch.TYPE_NAME, mapping)
            .execute()
            .actionGet();

        // インデックス作成完了の待機.
        ensureGreen();
    }

    // テスト本体
    @Test
    public void testSearch() throws Exception {
        // ドキュメント投入.
        // これも外部JSONファイルなどを用意して, Bulk APIで投入した方が良いと思う.
        index(SimpleSearch.INDEX_NAME, SimpleSearch.TYPE_NAME, XContentFactory.jsonBuilder()
            .startObject()
                .field(SimpleSearch.TITLE_FIELD_NAME, "Alpaca")
                .field(SimpleSearch.TEXT_FIELD_NAME, "An alpaca is a domesticated species of South American camelid.")
            .endObject());

        index(SimpleSearch.INDEX_NAME, SimpleSearch.TYPE_NAME, XContentFactory.jsonBuilder()
            .startObject()
                .field(SimpleSearch.TITLE_FIELD_NAME, "Llama")
                .field(SimpleSearch.TEXT_FIELD_NAME, "A llama is a domesticated species of South American camelid.")
            .endObject());

        index(SimpleSearch.INDEX_NAME, SimpleSearch.TYPE_NAME, XContentFactory.jsonBuilder()
            .startObject()
                .field(SimpleSearch.TITLE_FIELD_NAME, "Vicugna")
                .field(SimpleSearch.TEXT_FIELD_NAME, "A vicugna is a kind of herbivorous mammals that inhabit the Andes of South America.")
            .endObject());

        // ドキュメント投入完了の待機.
        flushAndRefresh();
        ensureGreen();

        // テスト用クライアントに対してSimpleSearchインスタンスを生成.
        SimpleSearch service = new SimpleSearch(client()); // client()でクライアントが取れる!

        // テスト実施.
        // せっかくなので, 依存のおまけで付いてきたhamcrestを使おう.
        assertThat(service.search("alpaca").size(), is(1));
        assertThat(service.search("llama").size(), is(1));
        assertThat(service.search("vicugna").size(), is(1));
        assertThat(service.search("lama").size(), is(0));

        assertThat(service.search("domesticated").size(), is(2));
        assertThat(service.search("alpaca domesticated").size(), is(1));
        assertThat(service.search("llama domesticated").size(), is(1));
        assertThat(service.search("vicugna domesticated").size(), is(0));
    }
}

由于可以使用client()获取Elasticsearch客户端,因此基本上可以做任何事情。如果需要进行索引创建等管理员操作,请使用client().admin()或admin()进行获取。

在@Before标记下,我们创建了索引,但在@After标记下,我们不需要手动删除索引,它似乎会自动将每个测试方法重置为空状态。非常方便。

在平常看不到的方法ensureGreen()和flushAndRefresh()等出现了,这些是由ElasticsearchIntegrationTest提供的方便方法。例如,如果要自己编写ensureGreen()方法,

client().admin().cluster().prepareHealth()
    .setWaitForGreenStatus()
    .execute()
    .actionGet();

听起来可能会有点长。当然,你也可以自己编写这种操作来进行测试。 有关便利方法的列表,请参考官方文档:integration tests (1.7)。

在这个阶段卡住不动的时候

我会写下我犯过的错误的日志以及相应的处理方法。

型 <类名> 的层次结构是不一致的

型 <クラス名> の階層は不整合です
The hierarchy of the type '<Class Name>' is inconsistent

当在Eclipse中的测试类中扩展`ElasticsearchIntegrationTest`时,如果看到了Elasticsearch测试框架的JAR包,而看不到`randomizedtesting-runner`的JAR包时,会出现上述错误。请确认依赖是否正确编写,并尝试刷新/清理项目等方法。

测试类需要启用断言。

java.lang.Exception: Test class requires enabled assertions, enable globally (-ea) or for Solr/Lucene subpackages only: jp.akimateras.elasticsearch.esintegtest.test.SampleTest
    __randomizedtesting.SeedInfo.seed([E05E1B5BAB0E8E6]:0)
    org.apache.lucene.util.TestRuleAssertionsRequired$1.evaluate(TestRuleAssertionsRequired.java:38)
    org.apache.lucene.util.TestRuleMarkFailure$1.evaluate(TestRuleMarkFailure.java:48)
    org.apache.lucene.util.TestRuleIgnoreAfterMaxFailures$1.evaluate(TestRuleIgnoreAfterMaxFailures.java:65)
    org.apache.lucene.util.TestRuleIgnoreTestSuites$1.evaluate(TestRuleIgnoreTestSuites.java:55)
    [...com.carrotsearch.randomizedtesting.*]
    java.lang.Thread.run(Unknown Source)

如果使用ElasticsearchIntegrationTest,则测试执行时必须启用断言。请在JVM启动选项中添加-ea。

如果要从Eclipse中运行测试,只需打开[运行配置],然后打开特定配置选项的[参数]选项卡,在[VM参数]文本区域中写入-ea即可。

tests-framework.jar和lucene-core.jar发生了什么。

java.lang.AssertionError: fix your classpath to have tests-framework.jar before lucene-core.jar
    __randomizedtesting.SeedInfo.seed([C7B320FCEA3DEB94]:0)
    org.apache.lucene.util.TestRuleSetupAndRestoreClassEnv.before(TestRuleSetupAndRestoreClassEnv.java:211)
    org.apache.lucene.util.AbstractBeforeAfterRule$1.evaluate(AbstractBeforeAfterRule.java:45)
    org.apache.lucene.util.TestRuleStoreClassName$1.evaluate(TestRuleStoreClassName.java:42)
    [...com.carrotsearch.randomizedtesting.*]
    org.apache.lucene.util.TestRuleAssertionsRequired$1.evaluate(TestRuleAssertionsRequired.java:43)
    org.apache.lucene.util.TestRuleMarkFailure$1.evaluate(TestRuleMarkFailure.java:48)
    org.apache.lucene.util.TestRuleIgnoreAfterMaxFailures$1.evaluate(TestRuleIgnoreAfterMaxFailures.java:65)
    org.apache.lucene.util.TestRuleIgnoreTestSuites$1.evaluate(TestRuleIgnoreTestSuites.java:55)
    [...com.carrotsearch.randomizedtesting.*]
    java.lang.Thread.run(Unknown Source)

pom.xml中的描述顺序是错误的。是Java类路径的顺序出了问题吗…?
在依赖于randomizedtesting-runner之前写入对Elasticsearch的依赖会导致出现问题。我不太了解Java,所以不太清楚原因,但是以描述顺序无法正常运行有点让人不悦。

在使用randomizedtesting时出现了NoSuchMethodError错误。

java.lang.NoSuchMethodError: com.carrotsearch.randomizedtesting.RandomizedContext.runWithPrivateRandomness(Lcom/carrotsearch/randomizedtesting/Randomness;Ljava/util/concurrent/Callable;)Ljava/lang/Object;
    __randomizedtesting.SeedInfo.seed([173369D720B9A36A:9F67560D8E45CE92]:0)
    org.elasticsearch.test.ElasticsearchIntegrationTest.buildWithPrivateContext(ElasticsearchIntegrationTest.java:583)
    org.elasticsearch.test.ElasticsearchIntegrationTest.buildAndPutCluster(ElasticsearchIntegrationTest.java:598)
    org.elasticsearch.test.ElasticsearchIntegrationTest.beforeInternal(ElasticsearchIntegrationTest.java:283)
    org.elasticsearch.test.ElasticsearchIntegrationTest.before(ElasticsearchIntegrationTest.java:1946)
    [...sun.*, org.junit.*, java.lang.reflect.*, com.carrotsearch.randomizedtesting.*]
    org.apache.lucene.util.TestRuleSetupTeardownChained$1.evaluate(TestRuleSetupTeardownChained.java:50)
    org.apache.lucene.util.TestRuleFieldCacheSanity$1.evaluate(TestRuleFieldCacheSanity.java:51)
    org.apache.lucene.util.AbstractBeforeAfterRule$1.evaluate(AbstractBeforeAfterRule.java:46)
    [...com.carrotsearch.randomizedtesting.*]
    org.apache.lucene.util.TestRuleThreadAndTestName$1.evaluate(TestRuleThreadAndTestName.java:49)
    org.apache.lucene.util.TestRuleIgnoreAfterMaxFailures$1.evaluate(TestRuleIgnoreAfterMaxFailures.java:65)
    org.apache.lucene.util.TestRuleMarkFailure$1.evaluate(TestRuleMarkFailure.java:48)
    [...com.carrotsearch.randomizedtesting.*]
    org.apache.lucene.util.AbstractBeforeAfterRule$1.evaluate(AbstractBeforeAfterRule.java:46)
    org.apache.lucene.util.TestRuleStoreClassName$1.evaluate(TestRuleStoreClassName.java:42)
    [...com.carrotsearch.randomizedtesting.*]
    org.apache.lucene.util.TestRuleAssertionsRequired$1.evaluate(TestRuleAssertionsRequired.java:43)
    org.apache.lucene.util.TestRuleMarkFailure$1.evaluate(TestRuleMarkFailure.java:48)
    org.apache.lucene.util.TestRuleIgnoreAfterMaxFailures$1.evaluate(TestRuleIgnoreAfterMaxFailures.java:65)
    org.apache.lucene.util.TestRuleIgnoreTestSuites$1.evaluate(TestRuleIgnoreTestSuites.java:55)
    [...com.carrotsearch.randomizedtesting.*]
    java.lang.Thread.run(Unknown Source)

如果在代码中忽略了对randomizedtesting-runner的依赖,或者使用了旧版本的randomizedtesting-runner,也可能会发生这个问题。当使用elasticsearch 1.7.3时,使用randomizedtesting 2.1.6会引发此错误,而使用randomizedtesting 2.1.17则可以正常运行。

使用Hamcrest时出现NoClassDefFoundError错误。

java.lang.NoClassDefFoundError: org/hamcrest/Matchers
    __randomizedtesting.SeedInfo.seed([3F50D3975E9C4C58:B704EC4DF06021A0]:0)
    org.elasticsearch.test.ElasticsearchIntegrationTest.afterInternal(ElasticsearchIntegrationTest.java:628)
    org.elasticsearch.test.ElasticsearchIntegrationTest.after(ElasticsearchIntegrationTest.java:1954)
    [...sun.*, org.junit.*, java.lang.reflect.*, com.carrotsearch.randomizedtesting.*]
    org.apache.lucene.util.TestRuleSetupTeardownChained$1.evaluate(TestRuleSetupTeardownChained.java:50)
    org.apache.lucene.util.TestRuleFieldCacheSanity$1.evaluate(TestRuleFieldCacheSanity.java:51)
    org.apache.lucene.util.AbstractBeforeAfterRule$1.evaluate(AbstractBeforeAfterRule.java:46)
    [...com.carrotsearch.randomizedtesting.*]
    org.apache.lucene.util.TestRuleThreadAndTestName$1.evaluate(TestRuleThreadAndTestName.java:49)
    org.apache.lucene.util.TestRuleIgnoreAfterMaxFailures$1.evaluate(TestRuleIgnoreAfterMaxFailures.java:65)
    org.apache.lucene.util.TestRuleMarkFailure$1.evaluate(TestRuleMarkFailure.java:48)
    [...com.carrotsearch.randomizedtesting.*]
    org.apache.lucene.util.AbstractBeforeAfterRule$1.evaluate(AbstractBeforeAfterRule.java:46)
    org.apache.lucene.util.TestRuleStoreClassName$1.evaluate(TestRuleStoreClassName.java:42)
    [...com.carrotsearch.randomizedtesting.*]
    org.apache.lucene.util.TestRuleAssertionsRequired$1.evaluate(TestRuleAssertionsRequired.java:43)
    org.apache.lucene.util.TestRuleMarkFailure$1.evaluate(TestRuleMarkFailure.java:48)
    org.apache.lucene.util.TestRuleIgnoreAfterMaxFailures$1.evaluate(TestRuleIgnoreAfterMaxFailures.java:65)
    org.apache.lucene.util.TestRuleIgnoreTestSuites$1.evaluate(TestRuleIgnoreTestSuites.java:55)
    [...com.carrotsearch.randomizedtesting.*]
    java.lang.Thread.run(Unknown Source)

我认为文件中没有写,但似乎需要使用hamcrest。当使用elasticsearch 1.7.3时,hamcrest 1.3可以运行。

需要测试插件所需的功能。

简单的测试是否正常工作?实际上,从这里开始才是真正的事情。
如果想要处理日本语言的Elasticsearch,则需要安装诸如日本语分析器等插件。如果自己启动Elasticsearch服务器进行测试,则插件管理可能会变得麻烦,因此在这个阶段将感受到ElasticsearchIntegrationTest的优势。

在正在运行的Elasticsearch服务器中,如果要使用插件,则可能会执行类似于 $ bin/plugin –install <所需插件> 的命令。另一方面,在ElasticsearchIntegrationTest中使用插件时,

    重写继承自ElasticsearchIntegrationTest类的nodeSettings方法,并在其中添加使用所需插件的依存关系描述。

只需要完成两个就可以了。

在依赖关系中添加所需使用的插件。

让我们这次尝试使用作为日本语分析器插件的elasticsearch-analysis-kuromoji,将以下依赖项添加到pom.xml中。

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch-analysis-kuromoji</artifactId>
    <version>2.7.0</version>
</dependency>

与Elasticsearch和kuromoji插件的版本兼容性相当于在安装服务器时一样,可以通过elasticsearch-analysis-kuromoji的GitHub来确认。根据我们的了解,你可以使用对应于es-1.7版本的elasticsearch-analysis-kuromoji 2.7.0。

在使用插件时,记录覆盖nodeSettings的要点。

这件事有两种方式可以做。

将所有存在于类路径中的插件加载

    @Override
    protected Settings nodeSettings(int nodeOrdinal) {
        return ImmutableSettings.settingsBuilder()
            .put(super.nodeSettings(nodeOrdinal))
            .put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, true)
            .build();
    }

如果给定一个由”plugins.”紧接着由org.elasticsearch.plugins.PluginsService.LOAD_PLUGIN_FROM_CLASSPATH指定的字符串常量连接而成的配置名,并设置为true,那么将会去读取存在于类路径上的所有插件。值得一提的是,在elasticsearch 1.7.3中,该配置名被展开为”plugins.load_classpath_plugins”。

指定要加载的插件

    @Override
    protected Settings nodeSettings(int nodeOrdinal) {
        return ImmutableSettings.settingsBuilder()
            .put(super.nodeSettings(nodeOrdinal))
            .put("plugin.types", AnalysisKuromojiPlugin.class.getName())
            .build();
    }

如果将”plugin.types”内的插件类名提供给插件,将加载对应提供的插件名称。由于在测试时明确指定了需要使用的插件,个人更喜欢这种方式。

如果要加载多个插件,可以使用putArray方法来替代put方法,并按照以下方式编写。

    @Override
    protected Settings nodeSettings(int nodeOrdinal) {
        return ImmutableSettings.settingsBuilder()
            .put(super.nodeSettings(nodeOrdinal))
            .putArray("plugin.types",
                AnalysisKuromojiPlugin.class.getName(),
                AnalysisICUPlugin.class.getName()) // elasticsearch-analysis-icu プラグイン
            .build();
    }

根据官方文件(integration tests (2.1)),从 Elasticsearch 2.1 开始,似乎已对指定 nodePlugins 插件进行了修改,使其需要覆盖专用方法。

使用日语的测试用例

让我们验证一下Kuromoji插件是否已经成功加载。

package jp.akimateras.elasticsearch.esintegtest.service;

import static org.hamcrest.Matchers.is;

import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.plugin.analysis.kuromoji.AnalysisKuromojiPlugin;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.junit.Before;
import org.junit.Test;

@ElasticsearchIntegrationTest.ClusterScope(numDataNodes = 1)
public class SimpleSearchTest extends ElasticsearchIntegrationTest {
    @Override
    protected Settings nodeSettings(int nodeOrdinal) {
        return ImmutableSettings.settingsBuilder()
            .put(super.nodeSettings(nodeOrdinal))
            .put("plugin.types", AnalysisKuromojiPlugin.class.getName())
            .build();
    }

    @Before
    public void initializeIndex() throws Exception {
        // インデックス作成.
        XContentBuilder mapping = XContentFactory.jsonBuilder()
            .startObject()
                .startObject(SimpleSearch.TYPE_NAME)
                    .startObject("properties")
                        .startObject(SimpleSearch.TITLE_FIELD_NAME)
                            .field("type", "string")
                            .field("source", true)
                            .field("analyzer", "kuromoji") // kuromojiがないとこれがエラーになる.
                        .endObject()
                        .startObject(SimpleSearch.TEXT_FIELD_NAME)
                            .field("type", "string")
                            .field("source", true)
                            .field("analyzer", "kuromoji")
                        .endObject()
                    .endObject()
                .endObject()
            .endObject();

        admin().indices().prepareCreate(SimpleSearch.INDEX_NAME)
            .addMapping(SimpleSearch.TYPE_NAME, mapping)
            .execute()
            .actionGet();

        // インデックス作成完了の待機.
        ensureGreen();
    }

    @Test
    public void testSearch() throws Exception {
        // ドキュメント投入. 日本語を使います.
        index(SimpleSearch.INDEX_NAME, SimpleSearch.TYPE_NAME, XContentFactory.jsonBuilder()
            .startObject()
                .field(SimpleSearch.TITLE_FIELD_NAME, "アルパカ")
                .field(SimpleSearch.TEXT_FIELD_NAME, "アルパカは, 南アメリカのラクダ科動物の家畜です.")
            .endObject());

        index(SimpleSearch.INDEX_NAME, SimpleSearch.TYPE_NAME, XContentFactory.jsonBuilder()
            .startObject()
                .field(SimpleSearch.TITLE_FIELD_NAME, "リャマ")
                .field(SimpleSearch.TEXT_FIELD_NAME, "リャマは, 南アメリカのラクダ科動物の家畜です.")
            .endObject());

        index(SimpleSearch.INDEX_NAME, SimpleSearch.TYPE_NAME, XContentFactory.jsonBuilder()
            .startObject()
                .field(SimpleSearch.TITLE_FIELD_NAME, "ビクーニャ")
                .field(SimpleSearch.TEXT_FIELD_NAME, "ビクーニャは, 南アメリカのアンデス山脈に生息する草食哺乳類です.")
            .endObject());

        // ドキュメント投入完了の待機.
        flushAndRefresh();
        ensureGreen();

        // テスト用 client を使ってSimpleSearchインスタンスを生成.
        SimpleSearch service = new SimpleSearch(client());

        // テスト実施
        assertThat(service.search("アルパカ").size(), is(1));
        assertThat(service.search("リャマ").size(), is(1));
        assertThat(service.search("ビクーニャ").size(), is(1));
        assertThat(service.search("ラマ").size(), is(0));

        assertThat(service.search("家畜").size(), is(2));
        assertThat(service.search("アルパカ 家畜").size(), is(1));
        assertThat(service.search("リャマ 家畜").size(), is(1));
        assertThat(service.search("ビクーニャ 家畜").size(), is(0));
    }
}

无论怎么样,这个测试不使用Kuromoji也可以通过默认的Standard分析器…非常出色啊。如果把输入文档的标题和文本中的”阿尔帕卡”都改成”阿尔帕卡阿尔帕卡”,Standard分析器会将其作为一个词来识别,所以搜索”阿尔帕卡”也不会有结果了。而如果使用Kuromoji,它会将其识别为两个”阿尔帕卡”,所以可以通过”阿尔帕卡”进行搜索。

需要对脚本所需功能进行测试。

我們將介紹如何測試需要使用Groovy等腳本的功能。

如果作为服务器启动,则Elasticsearch的脚本可以自动读取放置在例如/etc/elasticsearch/scripts这样的位置。为了使脚本在ElasticsearchIntegrationTest中可见,只需将脚本放置在特定目录中,并在nodeSettings方法中指定放置位置即可。

然而,在elasticsearch 1.7.3版本中,无法直接指定脚本目录。它会在由”path.conf”指定的设置目录下搜索名为”scripts”的固定目录。
因此,如果要让它读取名为”src/test/resources/scripts”的目录,可以将”path.conf”设置为”src/test/resources”。

    @Override
    protected Settings nodeSettings(int nodeOrdinal) {
        return ImmutableSettings.settingsBuilder()
            .put(super.nodeSettings(nodeOrdinal))
            .put("plugin.types", AnalysisKuromojiPlugin.class.getName())
            .put("path.conf", "src/test/resources")
            .build();
    }

据官方文档《目录布局 (2.1)》,从Elasticsearch 2.1版本开始,可以通过”path.script”设置项直接指定脚本目录进行改进。

最后

非常感谢发表这篇关于经历了诸多磨难,但最终测试框架终于成功运行的文章。如果有任何错误,请指正。

我尽管经历了很多困难,但因为这个,我非常高兴不再需要管理测试用的Elasticsearch服务器,而且我觉得引入它是值得的。

明天,是takakiku的MySQL慢查询日志可视化文章!

bannerAds