更简单的 DynamoDB 本地测试

2022-08-31 13:16:46

我正在使用 DynamoDB local 进行单元测试。这还不错,但有一些缺点。具体说来:

  • 在测试运行之前,您必须以某种方式启动服务器
  • 服务器在每次测试之前都不会启动和停止,因此测试变得相互依赖,除非您在每次测试后添加代码以删除所有表等。
  • 所有开发人员都需要安装它

我想做的是将 DynamoDB 本地 jar 及其所依赖的其他 jar 放在我的目录中(我是用 Java 编写的)。然后在每次测试之前,我都会启动它,运行,在测试之后,我会停止它。这样,任何拉下git存储库的人都可以获得运行测试所需的所有内容的副本,并且每个测试都独立于其他测试。test/resources-inMemory

我已经找到了一种方法来使这项工作,但它很丑陋,所以我正在寻找替代方案。我的解决方案是将 DynamoDB 本地内容的 .zip 文件放入 ,然后在方法中,将其解压缩到某个临时目录并启动一个新的 java 进程来执行它。这有效,但它很丑陋,并且有一些缺点:test/resources@Before

  • 每个人都需要java可执行文件在他们的$PATH
  • 我必须解压缩一个zip到本地磁盘。使用本地磁盘通常很难进行测试,尤其是在连续构建等情况下。
  • 我必须为每个单元测试生成一个进程并等待它启动,然后在每次测试后终止该进程。除了速度慢之外,剩余过程的可能性似乎很丑陋。

似乎应该有一种更简单的方法。毕竟,DynamoDB Local 只是 Java 代码。难道我不能以某种方式要求JVM自己分叉并查看资源内部以构建类路径吗?或者,更好的是,我不能直接从其他线程调用 DynamoDB Local 的方法,以便这一切都发生在单个进程中吗?有什么想法吗?main

PS:我知道Acternator,但它似乎有其他缺点,所以如果可以让它工作,我倾向于坚持使用亚马逊支持的解决方案。


答案 1

要使用 DynamoDBLocal,您需要执行以下步骤。

  1. 获取直接 DynamoDB 本地依赖关系
  2. 获取原生 SQLite4Java 依赖项
  3. 设置为显示本机库sqlite4java.library.path

1. 获取直接 DynamoDB 本地依赖项

这个很简单。您需要此存储库,如此所述。

<!--Dependency:-->
<dependencies>
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>DynamoDBLocal</artifactId>
        <version>1.11.0.1</version>
        <scope></scope>
    </dependency>
</dependencies>
<!--Custom repository:-->
<repositories>
    <repository>
        <id>dynamodb-local</id>
        <name>DynamoDB Local Release Repository</name>
        <url>https://s3-us-west-2.amazonaws.com/dynamodb-local/release</url>
    </repository>
</repositories>

2. 获取原生 SQLite4Java 依赖项

如果不添加这些依赖项,测试将失败,并显示 500 内部错误。

首先,添加以下依赖项:

<dependency>
    <groupId>com.almworks.sqlite4java</groupId>
    <artifactId>sqlite4java</artifactId>
    <version>1.0.392</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.almworks.sqlite4java</groupId>
    <artifactId>sqlite4java-win32-x86</artifactId>
    <version>1.0.392</version>
    <type>dll</type>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.almworks.sqlite4java</groupId>
    <artifactId>sqlite4java-win32-x64</artifactId>
    <version>1.0.392</version>
    <type>dll</type>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.almworks.sqlite4java</groupId>
    <artifactId>libsqlite4java-osx</artifactId>
    <version>1.0.392</version>
    <type>dylib</type>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.almworks.sqlite4java</groupId>
    <artifactId>libsqlite4java-linux-i386</artifactId>
    <version>1.0.392</version>
    <type>so</type>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.almworks.sqlite4java</groupId>
    <artifactId>libsqlite4java-linux-amd64</artifactId>
    <version>1.0.392</version>
    <type>so</type>
    <scope>test</scope>
</dependency>

然后,添加此插件以将本机依赖项获取到特定文件夹:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <version>2.10</version>
            <executions>
                <execution>
                    <id>copy</id>
                    <phase>test-compile</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <includeScope>test</includeScope>
                        <includeTypes>so,dll,dylib</includeTypes>
                        <outputDirectory>${project.basedir}/native-libs</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

3. 设置 sqlite4java.library.path 以显示本机库

作为最后一步,您需要将系统属性设置为本机库目录。在创建本地服务器之前执行此操作是可以的。sqlite4java.library.path

System.setProperty("sqlite4java.library.path", "native-libs");

完成这些步骤后,您可以根据需要使用 DynamoDBLocal。下面是一个 Junit 规则,用于为此创建本地服务器。

import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.local.main.ServerRunner;
import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer;
import org.junit.rules.ExternalResource;

import java.io.IOException;
import java.net.ServerSocket;

/**
 * Creates a local DynamoDB instance for testing.
 */
public class LocalDynamoDBCreationRule extends ExternalResource {

    private DynamoDBProxyServer server;
    private AmazonDynamoDB amazonDynamoDB;

    public LocalDynamoDBCreationRule() {
        // This one should be copied during test-compile time. If project's basedir does not contains a folder
        // named 'native-libs' please try '$ mvn clean install' from command line first
        System.setProperty("sqlite4java.library.path", "native-libs");
    }

    @Override
    protected void before() throws Throwable {

        try {
            final String port = getAvailablePort();
            this.server = ServerRunner.createServerFromCommandLineArgs(new String[]{"-inMemory", "-port", port});
            server.start();
            amazonDynamoDB = new AmazonDynamoDBClient(new BasicAWSCredentials("access", "secret"));
            amazonDynamoDB.setEndpoint("http://localhost:" + port);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void after() {

        if (server == null) {
            return;
        }

        try {
            server.stop();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public AmazonDynamoDB getAmazonDynamoDB() {
        return amazonDynamoDB;
    }

    private String getAvailablePort() {
        try (final ServerSocket serverSocket = new ServerSocket(0)) {
            return String.valueOf(serverSocket.getLocalPort());
        } catch (IOException e) {
            throw new RuntimeException("Available port was not found", e);
        }
    }
}

您可以像这样使用此规则

@RunWith(JUnit4.class)
public class UserDAOImplTest {

    @ClassRule
    public static final LocalDynamoDBCreationRule dynamoDB = new LocalDynamoDBCreationRule();
}

答案 2

2018 年 8 月,亚马逊宣布了新的 Docker 映像,其中加入了 Amazon DynamoDB Local。它不需要下载和运行任何JAR以及使用第三方特定于操作系统的二进制文件进行添加(我正在谈论)。sqlite4java

这就像在测试之前启动 Docker 容器一样简单:

docker run -p 8000:8000 amazon/dynamodb-local

如上所述,您可以手动执行此操作以进行本地开发,也可以在CI管道中使用它。许多 CI 服务都提供了在管道期间启动其他容器的功能,这些容器可以为测试提供依赖项。以下是 Gitlab CI/CD 的一个示例:

test:
  stage: test
  image: openjdk:8-alpine
  services:
    - name: amazon/dynamodb-local
      alias: dynamodb-local
  script:
    - DYNAMODB_LOCAL_URL=http://dynamodb-local:8000 ./gradlew clean test

或 Bitbucket Pipelines:

definitions:
  services:
    dynamodb-local:
      image: amazon/dynamodb-local
…
step:
  name: test
  image:
    name: openjdk:8-alpine
  services:
    - dynamodb-local
  script:
    - DYNAMODB_LOCAL_URL=http://localhost:8000 ./gradlew clean test

等等。我们的想法是将您在其他答案中看到的所有配置移出构建工具,并在外部提供依赖项。可以将其视为依赖注入/ IoC,但对于整个服务,而不仅仅是单个bean。

启动容器后,可以创建指向该容器的客户端:

private AmazonDynamoDB createAmazonDynamoDB(final DynamoDBLocal configuration) {
    return AmazonDynamoDBClientBuilder
        .standard()
        .withEndpointConfiguration(
            new AwsClientBuilder.EndpointConfiguration(
                "http://localhost:8000",
                Regions.US_EAST_1.getName()
            )
        )
        .withCredentials(
            new AWSStaticCredentialsProvider(
                // DynamoDB Local works with any non-null credentials
                new BasicAWSCredentials("", "")
            )
        )
        .build();
}

现在回到最初的问题:

在测试运行之前,您必须以某种方式启动服务器

您可以手动启动它,也可以为它准备一个开发人员的脚本。IDE 通常提供一种在执行任务之前运行任意命令的方法,因此您可以使 IDE 为您启动容器。我认为在这种情况下,在本地运行某些东西不应该是重中之重,相反,你应该专注于配置CI,让开发人员在舒适的时候启动容器。

服务器在每次测试之前都不会启动和停止,因此测试变得相互依赖,除非您在每次测试后添加代码以删除所有表等。

这是真的,但是...您不应该在每次测试之前/之后开始和停止这种重量级的事情并重新创建表格。数据库测试几乎总是相互依赖的,这对他们来说没关系。只需为每个测试用例使用唯一值(例如,将项目的哈希键设置为工单 ID/您正在处理的特定测试用例 ID)。至于种子数据,我建议将其从构建工具中移出并测试代码。您可以使用所需的所有数据创建自己的映像,也可以使用 AWS CLI 创建表和插入数据。遵循单一责任原则和依赖注入原则:测试代码除了测试之外,不得执行任何操作。所有环境(在本例中应为它们提供表和数据)。在测试中创建表是错误的,因为在现实生活中,该表已经存在(当然,除非您正在测试实际创建表的方法)。

所有开发人员都需要安装它

Docker应该是2018年每个开发人员的必备品,所以这不是问题。


如果您使用的是 JUnit 5,最好使用 DynamoDB 本地扩展,它将在测试中注入客户端(是的,我正在做自我推广):

  1. 添加依赖项me.madhead.aws-junit5:dynamo-v1

    pom.xml

    <dependency>
        <groupId>me.madhead.aws-junit5</groupId>
        <artifactId>dynamo-v1</artifactId>
        <version>6.0.1</version>
        <scope>test</scope>
    </dependency>
    

    build.gradle

    dependencies {
        testImplementation("me.madhead.aws-junit5:dynamo-v1:6.0.1")
    }
    
  2. 在测试中使用该扩展程序:

    @ExtendWith(DynamoDBLocalExtension.class)
    class MultipleInjectionsTest {
        @DynamoDBLocal(
            url = "http://dynamodb-local-1:8000"
        )
        private AmazonDynamoDB first;
    
        @DynamoDBLocal(
            urlEnvironmentVariable = "DYNAMODB_LOCAL_URL"
        )
        private AmazonDynamoDB second;
    
        @Test
        void test() {
            first.listTables();
            second.listTables();
        }
    }
    

推荐