当前位置: 首页 > 知识库问答 >
问题:

是否有使用JUnit5扩展模型的Neo4j测试套件?

姜经武
2023-03-14

在为Neo4j编写测试用例时,我希望只使用JUnit5扩展模型,而不使用org.JUnit.vintagejunit-jupiter-migrationsupport。目前,我只能找到JUnit4的Neo4j测试包,它使用testrule,并且依赖于org.JUnit.vintagejunit-jupiter-migrationsupport

是否有使用扩展模型的JUnit5的Neo4j测试套件?

参考:
Neo4j:Home,GitHub
Neo4jTest-harness:Maven,GitHub,pom.xml
JUnit 4:GitHub
JUnit 4testrule:JUnit 4指南,JUnit 4.12 API,Neo4jRule GitHub
JUnit 5:GitHub
JUnit 5extension model:JUnit 5用户指南,GitHub
JUnit Rationsupport:JUnit 5用户指南,测试pom.xml

我知道可以在混合环境中使用JUnit4和JUnit5,例如混合JUnit4和JUnit5测试。

在JUnit5扩展指南的帮助下,我已经开始编写自己的Neo4j JUnit5扩展,但是如果已经存在使用JUnit5扩展模型的标准Neo4j测试工具,为什么还要创建自己的Neo4j JUnit5扩展。

可能我只是使用了错误的关键字进行查询,这些关键字只是neo4jJUnit5,但仍然出现相同的结果,这些结果都不会导致我所寻求的结果。

由于下面的代码只是概念证明,所以它不是作为公认的答案发布的,但希望在几天内就会发布。

事实证明,将JUnit5Jupiter扩展添加到现有的JUnit TestRlue并不是那么糟糕。在这一过程中有一些困难,如果你和我一样,不生活和呼吸单一的编程语言或工具集,你必须花一些时间来理解其中的精神;如果你问我的话,那应该是个SO标签。

注意:此代码是Neo4j TestRule中的一些代码和JUnit5扩展指南的组合

/*
 * Copyright (c) 2002-2018 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
//package org.neo4j.harness.junit;
package org.egt.neo4j.harness.example_002.junit;

// References:
// GitHub - junit-team - junit5 - junit5/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine - https://github.com/junit-team/junit5/tree/releases/5.3.x/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension

// Notes:
// With JUnit 4 TestRule there was basically one rule that was called at multiple points and for multiple needs.
// With JUnit 5 Extensions the calls are specific to a lifecycle step, e.g. BeforeAll, AfterEach,
// or specific to a need, e.g. Exception handling, maintaining state across test,
// so in JUnit 4 where a single TestRule could be created in JUnit5 many Extensions need to be created.
// Another major change is that with JUnit 4 a rule would wrap around a test which would make
// implementing a try/catch easy, with JUnit 5 the process is broken down into a before and after callbacks
// that make this harder, however because the extensions can be combined for any test,
// adding the ability to handle exceptions does not require adding the code to every extension,
// but merely adding the extension to the test. (Verify this).

import java.io.File;
import java.io.PrintStream;
import java.util.function.Function;

import org.junit.jupiter.api.extension.*;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.config.Setting;

import org.egt.neo4j.harness.example_002.ServerControls;
import org.egt.neo4j.harness.example_002.TestServerBuilder;
import org.egt.neo4j.harness.example_002.TestServerBuilders;

/**
 * A convenience wrapper around {@link org.neo4j.harness.TestServerBuilder}, exposing it as a JUnit
 * {@link org.junit.Rule rule}.
 *
 * Note that it will try to start the web server on the standard 7474 port, but if that is not available
 * (typically because you already have an instance of Neo4j running) it will try other ports. Therefore it is necessary
 * for the test code to use {@link #httpURI()} and then {@link java.net.URI#resolve(String)} to create the URIs to be invoked.
 */
//public class Neo4jRule implements TestRule, TestServerBuilder
public class Neo4jDatabaseSetupExtension implements  BeforeEachCallback, AfterEachCallback, TestServerBuilder
{
    private TestServerBuilder builder;
    private ServerControls controls;
    private PrintStream dumpLogsOnFailureTarget;

    Neo4jDatabaseSetupExtension(TestServerBuilder builder )
    {
        this.builder = builder;
    }

    public Neo4jDatabaseSetupExtension( )
    {
        this( TestServerBuilders.newInProcessBuilder() );
    }

    public Neo4jDatabaseSetupExtension(File workingDirectory )
    {
        this( TestServerBuilders.newInProcessBuilder( workingDirectory ) );
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {

        if (controls != null)
        {
            controls.close();
        }
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        controls = builder.newServer();
    }

    @Override
    public ServerControls newServer() {
        throw new UnsupportedOperationException( "The server cannot be manually started via this class, it must be used as a JUnit 5 Extension." );
    }

    @Override
    public TestServerBuilder withConfig(Setting<?> key, String value) {
        builder = builder.withConfig( key, value );
        return this;
    }

    @Override
    public TestServerBuilder withConfig(String key, String value) {
        builder = builder.withConfig( key, value );
        return this;
    }

    @Override
    public TestServerBuilder withExtension(String mountPath, Class<?> extension) {
        builder = builder.withExtension( mountPath, extension );
        return this;
    }

    @Override
    public TestServerBuilder withExtension(String mountPath, String packageName) {
        builder = builder.withExtension( mountPath, packageName );
        return this;
    }

    @Override
    public TestServerBuilder withFixture(File cypherFileOrDirectory) {
        builder = builder.withFixture( cypherFileOrDirectory );
        return this;
    }

    @Override
    public TestServerBuilder withFixture(String fixtureStatement) {
        builder = builder.withFixture( fixtureStatement );
        return this;
    }

    @Override
    public TestServerBuilder withFixture(Function<GraphDatabaseService, Void> fixtureFunction) {
        builder = builder.withFixture( fixtureFunction );
        return this;
    }

    @Override
    public TestServerBuilder copyFrom(File sourceDirectory) {
        builder = builder.copyFrom( sourceDirectory );
        return this;
    }

    @Override
    public TestServerBuilder withProcedure(Class<?> procedureClass) {
        builder = builder.withProcedure( procedureClass );
        return this;
    }

    @Override
    public TestServerBuilder withFunction(Class<?> functionClass) {
        builder = builder.withFunction( functionClass );
        return this;
    }

    @Override
    public TestServerBuilder withAggregationFunction(Class<?> functionClass) {
        builder = builder.withAggregationFunction( functionClass );
        return this;
    }
}

接下来,为了允许每个测试实例有一个新的GraphDatabaseService,它是用ServerControls创建的,实现一个JUnit5ParameterResolver。

package org.egt.neo4j.harness.example_002.junit;

import org.egt.neo4j.harness.example_002.ServerControls;
import org.egt.neo4j.harness.example_002.TestServerBuilders;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;

public class Neo4jDatabaseParameterResolver implements ParameterResolver {

    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
        boolean result = parameterContext.getParameter()
                .getType()
                .equals(ServerControls.class);

        return result;
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {

        Object result = (ServerControls)TestServerBuilders.newInProcessBuilder().newServer();

        return result;
    }
}

最后,剩下的就是通过@extendwith@test使用Neo4j JUnit5扩展模型:

package org.egt.example_002;

import org.egt.neo4j.harness.example_002.ServerControls;
import org.egt.neo4j.harness.example_002.junit.Neo4jDatabaseParameterResolver;
import org.egt.neo4j.harness.example_002.junit.Neo4jDatabaseSetupExtension;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;

import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith({ Neo4jDatabaseSetupExtension.class, Neo4jDatabaseParameterResolver.class })
public class Neo4jUnitTests {

    private ServerControls sc;
    private GraphDatabaseService graphDb;

    public Neo4jUnitTests(ServerControls sc) {
        this.sc = sc;
        this.graphDb = sc.graph();
    }

    @Test
    public void shouldCreateNode()
    {
        // START SNIPPET: unitTest
        Node n;
        try ( Transaction tx = graphDb.beginTx() )
        {
            n = graphDb.createNode();
            n.setProperty( "name", "Nancy" );
            tx.success();
        }

        long id = n.getId();
        // The node should have a valid id
        assertEquals(0L, n.getId());

        // Retrieve a node by using the id of the created node. The id's and
        // property should match.
        try ( Transaction tx = graphDb.beginTx() )
        {
            Node foundNode = graphDb.getNodeById( n.getId() );
            assertEquals( foundNode.getId(),  n.getId() );
            assertEquals( "Nancy" , (String)foundNode.getProperty("name") );
        }
        // END SNIPPET: unitTest

    }
}

在这样做的过程中,我学到的一件重要的事情是,TestRule代码似乎是在一个类中完成所有事情,而新的扩展模型使用许多扩展来完成相同的事情。因此,Neo4j TestRule的日志记录、异常处理和其他功能都不在这个概念证明中。但是,由于扩展模型允许您混合和匹配扩展,添加日志记录和异常处理就像从另一个地方使用扩展并添加@extendwith一样容易,这就是为什么我没有为这个概念证明创建它们。

另外,您会注意到,我更改了包名,这样做只是为了避免与以独立方式实现代码其他部分的同一项目中的其他代码发生冲突,这样我就可以继续进行这个有效的概念验证。

最后,如果JUnit4 Neo4j TestRule类和JUnit5扩展模型类都可以从基类继承,然后在相同的测试工具中可用,我不会感到惊讶;手指交叉。显然,大部分基类将从Neo4j TestRule类中提取。

共有1个答案

秦光启
2023-03-14

最简单的方法可能是根本不使用扩展。

对Neo4J4.x使用以下依赖关系:

<dependency>
    <groupId>org.neo4j.test</groupId>
    <artifactId>neo4j-harness</artifactId>
    <version>4.0.8</version>
    <scope>test</scope>
</dependency>

然后像下面这样构造JUnit5测试:

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.neo4j.harness.Neo4j;
import org.neo4j.harness.Neo4jBuilders;

public class SimpleTest {

    private static Neo4j embeddedDatabaseServer;

    @BeforeAll
    static void initializeNeo4j() {

        embeddedDatabaseServer = Neo4jBuilders.newInProcessBuilder()
            .withDisabledServer() // Don't need Neos HTTP server
            .withFixture(""
                + "CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})"
            )
            .build();
    }

    @AfterAll
    static void stopNeo4j() {

        embeddedDatabaseServer.close();
    }

    @Test
    void testSomething() {

        try(var tx = embeddedDatabaseServer.databaseManagementService().database("neo4j").beginTx()) {
            var result = tx.execute("MATCH (m:Movie) WHERE m.title = 'The Matrix' RETURN m.released");
            Assertions.assertEquals(1999L, result.next().get("m.released"));
        }
    }
}
@Test
void testSomethingOverBolt() {

    try(var driver = GraphDatabase.driver(embeddedDatabaseServer.boltURI(), AuthTokens.none());
    var session = driver.session()) {
        var result = session.run("MATCH (m:Movie) WHERE m.title = 'The Matrix' RETURN m.released");
        Assertions.assertEquals(1999L, result.next().get("m.released").asLong());
    }
}
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.neo4j.harness.Neo4j;
import org.neo4j.harness.Neo4jBuilders;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class SimpleTest {

    private final Neo4j embeddedDatabaseServer = Neo4jBuilders.newInProcessBuilder()
            .withDisabledServer() // Don't need Neos HTTP server
        .withFixture(""
            + "CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})"
        )
        .build();

    @AfterAll
    void stopNeo4j() {

        embeddedDatabaseServer.close();
    }

    @Test
    void whatever() {
    }
}
 类似资料:
  • 但是现在,我希望这些接口实现也可用于扩展,例如。 我如何设置我的课程来实现这一点?(或者,反对这样做的固有缺陷或代码气味是什么?)

  • 基于此链接中的答案,创建了一个包含测试类的测试套件 返回错误 但是每个添加的测试类都有测试方法,并且单独运行

  • 我正试图用三个额外的日期(时间戳)字段扩展扩展扩展名(新闻),并希望在(新闻)的fluidtemplate中调用这些字段。 我已经连线到目前为止,我可以看到我的后端额外的字段,而无需选择一个外部类型-我已经相应地修改了ext_tables.php,并可以保存数据。 现在,我试图在我的新闻流模板中使用这些字段,在我的Partials/List/Item中使用以下代码。html-{newsItem.d

  • 我们打算将基于JUnit4的项目升级到JUnit5。我按照JUnit5官方网站的说明修改了JUnit4套件:https://JUnit . org/JUnit 5/docs/current/user-guide/# running-tests-JUnit-platform-runner-test-suite。在命令行中使用mvn test时没有执行测试:< code > mvn test-Dtes

  • 为什么要扩展mongoose模型? 我们对业务进行分层处理 service(多模型操作) -> dao(单一模型操作) -> model(模型定义) 所以我们在dao层需要很多单一模型的数据库操作方法的封装,如果业务非常复杂,比如一个超级查询方法,然后又有各种具体业务定义方法,难道我们一个一个的都写在dao层么? 事实上dao只做暴露给service的方法,而具有一定业务约定的方法是可以放到mo