更轻松的DynamoDB本地测试

Oli*_*ain 46 java junit unit-testing amazon-dynamodb

我正在使用DynamoDB本地进行单元测试.这不错,但有一些缺点.特别:

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

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

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

  • 每个人都需要$ PATH上的java可执行文件
  • 我必须将zip解压缩到本地磁盘.使用本地磁盘通常是测试的冒险,特别是对于连续构建等.
  • 我必须生成一个进程并等待它开始进行每个单元测试,然后在每次测试后终止该进程.除了缓慢之外,剩余过程的可能性似乎很难看.

似乎应该有一个更简单的方法.毕竟,DynamoDB Local只是Java代码.我不能以某种方式要求JVM自行分配并查看资源以构建类路径吗?或者,更好的是,我不能只main从其他线程调用DynamoDb Local 的方法,所以这一切都发生在一个进程中?有任何想法吗?

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

bhd*_*rkn 63

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

  1. 获取Direct DynamoDBLocal依赖关系
  2. 获取Native SQLite4Java依赖项
  3. 设置sqlite4java.library.path为显示本机库

1.获取Direct DynamoDBLocal依赖关系

这个很容易.您需要此存储库,如AWS论坛中所述.

<!--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>
Run Code Online (Sandbox Code Playgroud)

2.获取Native 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>
Run Code Online (Sandbox Code Playgroud)

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

<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>
Run Code Online (Sandbox Code Playgroud)

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

最后一步,您需要将sqlite4java.library.pathsystem属性设置为native-libs目录.在创建本地服务器之前,可以这样做.

System.setProperty("sqlite4java.library.path", "native-libs");
Run Code Online (Sandbox Code Playgroud)

完成这些步骤后,您可以根据需要使用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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以像这样使用此规则

@RunWith(JUnit4.class)
public class UserDAOImplTest {

    @ClassRule
    public static final LocalDynamoDBCreationRule dynamoDB = new LocalDynamoDBCreationRule();
}
Run Code Online (Sandbox Code Playgroud)

  • 我发现DynamoDBLocal依赖项自动引入了sqlite4java,并且不需要手动指定额外的依赖项. (5认同)
  • 非常好的答案.我将native-libs放在目标中:`<outputDirectory> $ {project.basedir}/native-libs </ outputDirectory>`和`System.setProperty("sqlite4java.library.path","target/native-libs" );` (4认同)
  • 我使用`com.amazonaws:DynamoDBLocal:1.+`.我认为最好保持最新状态,因为服务本身也会更新我是否喜欢.现在这已经达到了1.11.0. (2认同)

Ale*_*kis 15

您可以在测试代码中将DynamoDB Local用作Maven测试依赖项,如本公告所示.您可以通过HTTP运行:

import com.amazonaws.services.dynamodbv2.local.main.ServerRunner;
import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer;

final String[] localArgs = { "-inMemory" };
DynamoDBProxyServer server = ServerRunner.createServerFromCommandLineArgs(localArgs);
server.start();
AmazonDynamoDB dynamodb = new AmazonDynamoDBClient();
dynamodb.setEndpoint("http://localhost:8000");
dynamodb.listTables();
server.stop();
Run Code Online (Sandbox Code Playgroud)

您还可以在嵌入模式下运行:

import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded;

AmazonDynamoDB dynamodb = DynamoDBEmbedded.create();
dynamodb.listTables();
Run Code Online (Sandbox Code Playgroud)

  • 对于嵌入式版本,我在`initializeMetadataTables`方法中从SQLLite获得了一个`NullPointerException`.:( (7认同)
  • 在官方[包含示例代码的存储库]中似乎没有关于此的任何信息(https://github.com/awslabs/aws-dynamodb-examples/blob/master/src/test/java/com/amazonaws /services/dynamodbv2/DynamoDBLocalFixture.java). (4认同)

Jef*_*ski 14

这是对于Gradle用户的bhdrkn答案的重述(他基于Maven).它仍然是相同的三个步骤:

  1. 获取Direct DynamoDBLocal依赖关系
  2. 获取Native SQLite4Java依赖项
  3. 设置sqlite4java.library.path以显示本机库

1.获取Direct DynamoDBLocal依赖关系

添加到build.gradle文件的依赖项部分...

dependencies {
    testCompile "com.amazonaws:DynamoDBLocal:1.+"
}
Run Code Online (Sandbox Code Playgroud)

2.获取Native SQLite4Java依赖项

sqlite4java库已经作为DynamoDBLocal的依赖项下载,但库文件需要复制到正确的位置.添加到build.gradle文件...

task copyNativeDeps(type: Copy) {
    from(configurations.compile + configurations.testCompile) {
        include '*.dll'
        include '*.dylib'
        include '*.so'
    }
    into 'build/libs'
}
Run Code Online (Sandbox Code Playgroud)

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

我们需要告诉Gradle运行copyNativeDeps测试并告诉sqlite4java在哪里找到文件.添加到build.gradle文件...

test {
    dependsOn copyNativeDeps
    systemProperty "java.library.path", 'build/libs'
}
Run Code Online (Sandbox Code Playgroud)


mad*_*ead 11

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

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

docker run -p 8000:8000 amazon/dynamodb-local
Run Code Online (Sandbox Code Playgroud)

如上所述,您可以手动进行本地开发,也可以在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
Run Code Online (Sandbox Code Playgroud)

或Bitbucket管道:

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
Run Code Online (Sandbox Code Playgroud)

等等。这个想法是将您在其他 答案中看到的所有配置移出构建工具,并从外部提供依赖项。可以将其视为依赖项注入/ 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();
}
Run Code Online (Sandbox Code Playgroud)

现在到原始问题:

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

您可以手动启动它,也可以为其准备开发人员脚本。IDE通常提供一种在执行任务之前运行任意命令的方法,因此您可以使IDE为您启动容器。我认为在这种情况下,在本地运行某些内容不是最优先的事情,而是您应该专注于配置CI,并让开发人员在对容器感到满意的情况下启动容器。

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

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

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

Docker应该成为2018年所有开发人员的必备条件,所以这不是问题。


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

  1. JCenter存储库添加到您的构建中。

    pom.xml

    <repositories>
        <repository>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <id>central</id>
            <name>bintray</name>
            <url>https://jcenter.bintray.com</url>
        </repository>
    </repositories>
    
    Run Code Online (Sandbox Code Playgroud)

    build.gradle

    repositories {
        jcenter()
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 添加对 by.dev.madhead.aws-junit5:dynamodb-v1

    pom.xml

    <dependency>
        <groupId>by.dev.madhead.aws-junit5</groupId>
        <artifactId>dynamodb-v1</artifactId>
        <version>1.0.0</version>
        <scope>test</scope>
    </dependency>
    
    Run Code Online (Sandbox Code Playgroud)

    build.gradle

    dependencies {
        testImplementation("by.dev.madhead.aws-junit5:dynamodb-v1:1.0.0")
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在测试中使用扩展名:

    @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();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

  • 事实上,在 2019 年害怕 Docker 的开发人员是一个糟糕的开发人员。您仍然可以将这种方法用于 CI/CD,无论如何,一切都在 Docker 中进行(大多数现代 CI 服务器都是基于 Docker 的,甚至 Jenkins 也可以使用 Docker)。我的观点是,您不会用启动代码污染您的测试代码库,而只需在外部提供服务(DynamoDB)。它可以是 Docker 容器。或者它可以是“DynamoDBLocal.jar”。或者您可以运行 [localstack](https://github.com/localstack/localstack)。所有测试都需要知道所有情况下的 URL。 (3认同)
  • @madhead相反,当你的 CI/CD 的构建依赖于运行子容器时,这是一个巨大的痛苦。运行 docker-in-docker 充满了问题。 (2认同)

Mic*_*mlk 5

我将上面的答案包含在两个JUnit规则中,这些规则不需要更改构建脚本,因为规则处理本机库的内容.我这样做了,因为我发现Idea不喜欢Gradle/Maven解决方案因为它刚刚开始并且做了自己的事情.

这意味着步骤是:

  • 获取AssortmentOfJUnitRules版本1.5.32或更高版本的依赖项
  • 获取Direct DynamoDBLocal依赖项
  • 将LocalDynamoDbRule或HttpDynamoDbRule添加到JUnit测试中.

Maven的:

<!--Dependency:-->
<dependencies>
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>DynamoDBLocal</artifactId>
        <version>1.11.0.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.github.mlk</groupId>
      <artifactId>assortmentofjunitrules</artifactId>
      <version>1.5.36</version>
      <scope>test</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>
Run Code Online (Sandbox Code Playgroud)

摇篮:

repositories {
  mavenCentral()

   maven {
    url = "https://s3-us-west-2.amazonaws.com/dynamodb-local/release"
  }
}

dependencies {
    testCompile "com.github.mlk:assortmentofjunitrules:1.5.36"
    testCompile "com.amazonaws:DynamoDBLocal:1.+"
}
Run Code Online (Sandbox Code Playgroud)

码:

public class LocalDynamoDbRuleTest {
  @Rule
  public LocalDynamoDbRule ddb = new LocalDynamoDbRule();

  @Test
  public void test() {
    doDynamoStuff(ddb.getClient());
  }
}
Run Code Online (Sandbox Code Playgroud)


Zhi*_*Lai 5

尝试暴风雨测试!它附带了 JUnit4 规则和 JUnit5 扩展。它还支持 AWS SDK v1 和 SDK v2。

Tempest 提供了一个使用DynamoDBLocal测试 DynamoDB 客户端的库 。它有两个实现:

  • JVM:这是首选选项,运行DynamoDBProxyServer由 JVM 支持sqlite4java,可在大多数平台上使用。
  • Docker:这在 Docker 容器中运行dynamodb-local 。

特征矩阵:

特征 暴风雨测试jvm 暴风雨测试 docker
启动时间 〜1秒 〜10秒
内存使用情况 较少的 更多的
依赖性 sqlite4java 本机库 码头工人

要使用tempest-testing,首先将此库添加为测试依赖项:

对于 AWS 开发工具包 1.x:

dependencies {
  testImplementation "app.cash.tempest:tempest-testing-jvm:1.5.2"
  testImplementation "app.cash.tempest:tempest-testing-junit5:1.5.2"
}
// Or
dependencies {
  testImplementation "app.cash.tempest:tempest-testing-docker:1.5.2"
  testImplementation "app.cash.tempest:tempest-testing-junit5:1.5.2"
}
Run Code Online (Sandbox Code Playgroud)

对于 AWS 开发工具包 2.x:

dependencies {
  testImplementation "app.cash.tempest:tempest2-testing-jvm:1.5.2"
  testImplementation "app.cash.tempest:tempest2-testing-junit5:1.5.2"
}
// Or
dependencies {
  testImplementation "app.cash.tempest:tempest2-testing-docker:1.5.2"
  testImplementation "app.cash.tempest:tempest2-testing-junit5:1.5.2"
}
Run Code Online (Sandbox Code Playgroud)

然后在用 注释的测试中@org.junit.jupiter.api.Test,您可以添加TestDynamoDb作为测试 扩展。此扩展启动 DynamoDB 服务器。它在测试之间共享服务器并保持其运行直到进程退出。它还为您管理测试表,在每次测试之前重新创建它们。

dependencies {
  testImplementation "app.cash.tempest:tempest-testing-jvm:1.5.2"
  testImplementation "app.cash.tempest:tempest-testing-junit5:1.5.2"
}
// Or
dependencies {
  testImplementation "app.cash.tempest:tempest-testing-docker:1.5.2"
  testImplementation "app.cash.tempest:tempest-testing-junit5:1.5.2"
}
Run Code Online (Sandbox Code Playgroud)


Ste*_*ith 1

对于工作中的单元测试,我使用 Mockito,然后模拟 AmazonDynamoDBClient。然后使用when模拟出回报。像下面这样:

when(mockAmazonDynamoDBClient.getItem(isA(GetItemRequest.class))).thenAnswer(new Answer<GetItemResult>() {
        @Override
        public GetItemResult answer(InvocationOnMock invocation) throws Throwable {
            GetItemResult result = new GetItemResult();
            result.setItem( testResultItem );
            return result;
        }
    });
Run Code Online (Sandbox Code Playgroud)

不确定这是否是您想要的,但这就是我们的做法。

  • 谢谢你的想法。模拟是可以的,但是很难让协议完全正确。因此,您最终会测试您的代码是否可以正常工作,假设 Dynamo(或其他任何东西)的行为方式与您认为的行为方式(您模拟它的方式)相同,但您并没有测试您的代码是否确实适用于 Dynamo。如果您对 Dynamo 工作方式的理解是错误的,您的代码和测试会做出相同的假设,因此事情会通过,但会出现错误。 (5认同)
  • 这听起来像是您在进行集成测试,因为您不应该进行很多测试。您只想确保您可以执行基本操作。基本上验证您的连接是否正确。过去我会做的是在测试中启动本地实例。那么您就可以从本地数据库中保存、读取和删除硬编码值。除此之外,您还进行哪些测试?我总是建议进行单元测试(测试只是我嘲笑的一件事)和集成测试(一切都是真实的) (3认同)
  • 理论上mock是单元测试的正确方式,但是本地DDB可以以更有希望的方式确保代码正确。 (3认同)