更便捷的DynamoDB本地测试

79

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

  • 您必须在测试运行之前以某种方式启动服务器
  • 服务器未在每个测试之前启动和停止,因此测试会相互依赖,除非您添加代码以删除所有表等,这样每个测试都是独立的
  • 所有开发人员都需要安装它

我的想法是将DynamoDB本地版jar文件及其依赖项放入我的test/resources目录中(我用Java编写)。然后在每个测试之前,我会以-inMemory模式启动它,并在测试完成后停止它。这样任何拉取git repo的人都会获得运行测试所需的所有内容,每个测试都与其他测试无关。

我找到了一种方法使它能够工作,但它很丑陋,所以我正在寻找替代方案。我现在的解决方案是在test/resources中放置DynamoDB本地版的.zip文件,然后在@Before方法中,我会将它提取到某个临时目录,并启动一个新的Java进程来执行它。这方法可行,但有一些缺点:

  • 每个人都需要在$PATH上拥有Java可执行文件
  • 我必须将zip文件解压缩到本地磁盘。对于测试来说,使用本地磁盘通常很棘手,特别是在连续构建等情况下。
  • 我必须为每个单元测试生成并等待进程启动,然后在每个测试之后杀死该进程。除了速度慢之外,残留进程的潜在风险也很丑陋。

看起来应该有更简单的方法。毕竟,DynamoDB Local只是Java代码。我能不能以某种方式要求JVM分叉自身并查看资源以构建类路径?或者,更好的是,我能否从其他线程中调用DynamoDB Local的main方法,这样所有操作都在单个进程中完成呢?有什么想法吗?

PS:我知道Alternator,但它似乎有其他缺点,因此如果我能让它正常工作,我倾向于坚持使用Amazon支持的解决方案。


2
既然您说想编写单元测试而不是集成测试,为什么不使用模拟?例如DynamoDB-mock。这个库允许将其封装为库以进行测试。具体可以参考使用DynamoDB-mock进行测试 - cheffe
3
@cheffe,感谢你的建议。那似乎正是我想要的,但它是用Python编写的,而不是Java,所以我仍然需要像使用DynamoDB Local一样从我的测试中生成外部可执行文件(并确保所有用户都安装了正确版本的Python,并将其添加到他们的$PATH等)。我正在寻找非常类似于Python版本的解决方案,但使用Java编写。请注意,创建自己的模拟将是一个巨大的任务,因为Dynamo API相当丰富。 - Oliver Dain
13个回答

87

使用DynamoDBLocal需要按照以下步骤进行:

  1. 获取Direct DynamoDBLocal依赖
  2. 获取本地SQLite4Java依赖
  3. 设置sqlite4java.library.path以显示本地库

1. 获取Direct DynamoDBLocal依赖

这个很简单。您可以按照此处的说明获取所需的代码库。

<!--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 系统属性指向native-libs目录。在创建本地服务器之前进行此操作是可以的。

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();
}

9
我发现 DynamoDBLocal 依赖自动引入了 sqlite4java,不需要手动指定额外的依赖项。 - Jeffery Grajkowski
@JefferyGrajkowski,我也试过了,但是没有本地库就无法使其正常工作。你的 DDB 本地版本是多少?也许他们更新了依赖库。 - bhdrkn
2
我使用 com.amazonaws:DynamoDBLocal:1.+。我认为最好保持最新版本,因为服务本身也会更新,无论我是否喜欢。目前最新版本是1.11.0。 - Jeffery Grajkowski
1
我尝试了@JefferyGrajkowski的建议以及这个解决方案,效果非常好。谢谢。 - Mingliang Liu
5
非常好的回答。我会将原生库放在目标位置: <outputDirectory>${project.basedir}/native-libs</outputDirectory>,并设置系统属性 "sqlite4java.library.path" 为 "target/native-libs"。 - Bart Swennenhuis
显示剩余3条评论

27
2018年8月,亚马逊宣布推出新的Docker镜像,其中包含Amazon DynamoDB Local。它不需要下载和运行任何JAR文件,也不需要使用第三方OS特定的二进制文件(我指的是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,并让开发人员根据他们的舒适度启动容器。

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

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

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

在2018年,Docker应该成为每个开发者必备的工具,这并不是问题。


如果您正在使用JUnit 5,建议使用DynamoDB本地扩展将客户端注入到测试中(是的,我在进行自我推广):

  1. Add a dependency on 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. Use the extension in your tests:

    @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();
        }
    }
    

3
2019年,一个害怕Docker的开发人员是个糟糕的开发人员。您仍然可以将这种方法用于CI/CD,其中所有操作都在Docker中进行(大多数现代CI服务器都基于Docker,即使Jenkins也可以使用Docker)。我的意思是,您不会通过初始化代码污染测试代码库,而是外部提供服务(DynamoDB)。它可以是一个Docker容器,或者可以是“DynamoDBLocal.jar”,或者您可以运行一个localstack。在所有情况下,所有测试需要知道的只是URL。 - madhead
2
相反地,当您的CI/CD构建依赖于运行子容器时,这是一个巨大的痛点。运行docker-in-docker存在许多问题。 - Magnus
1
为测试启动Docker镜像速度很慢。另外,如果你依赖手动设置和维护的外部资源,这会使得你的测试变得脆弱,也会让开发人员难以运行。 - Adrian Baker
1
dynamodb-local docker镜像所做的就是运行相同的jar文件(https://hub.docker.com/layers/amazon/dynamodb-local/1.16.0/images/sha256-0c8f11e69ccf895c7ec97a7c9991d4654971f0e5e3ae42292de1c514f3e03cfa?context=explore)。Docker并不总是唯一或最佳的方法,更像是最低公共分母。 - Adrian Baker
1
tar 文件只是 https://mvnrepository.com/artifact/com.amazonaws/DynamoDBLocal/1.15.0 加上运行时依赖项。因此,当您将其添加到类路径时,您会得到完全相同的二进制文件。 - Adrian Baker
显示剩余20条评论

20

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

  1. 获取Direct DynamoDBLocal依赖
  2. 获取Native SQLite4Java依赖
  3. 将sqlite4java.library.path设置为显示本地库

1. 获取Direct DynamoDBLocal依赖

在你的build.gradle文件的dependencies部分中添加以下内容...

dependencies {
    testCompile "com.amazonaws:DynamoDBLocal:1.+"
}

2. 获取SQLite4Java本地依赖

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

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

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

我们需要告诉 Gradle 在测试时运行 copyNativeDeps,并告诉 sqlite4java 在哪里找到这些文件。请在您的 build.gradle 文件中添加以下内容...

test {
    dependsOn copyNativeDeps
    systemProperty "java.library.path", 'build/libs'
}

@Jeffery 我遇到了以下错误:testMapRtbUser STANDARD_ERROR 17:39:41.931 [DEBUG] [TestEventLogger] 2017-08-29 17:39:41.929:WARN:oejs.AbstractHttpConnection:/ 17:39:41.931 [DEBUG] [TestEventLogger] java.lang.NoSuchMethodError: com.amazon.dynamodb.grammar.DynamoDbExpressionParser.parseAttributeValuesMapKeys(Ljava/lang/String;Lorg/antlr/v4/runtime/ANTLRErrorListener;)V然而,如果我在Eclipse中将其作为Junit测试运行,则可以正常运行相同的测试。只有在gradle作为测试运行时才会失败。后来这个操作超时了。请帮忙! - Roy
这听起来像是运行时类路径问题。该类和该方法以及其签名在最新版本的JAR中肯定存在。尝试清除Gradle缓存的所有内容,然后再试一次。 - Jeffery Grajkowski
我按照这个做,但是当我运行我的测试时,我一直收到 java.lang.RuntimeException: com.amazonaws.SdkClientException: Unable to execute HTTP request: The target server failed to respond 的错误。有什么想法是我做错了什么吗? - Red
你是否已经成功地运行并编写了DynamoDBLocal的用户测试,当手动启动它时?在自动化之前,先尝试编写手动、简单的内容。 - Jeffery Grajkowski
1
在IntelliJ中运行测试,需要在“运行/调试配置”中的“VM选项”中添加“-Djava.library.path=build/libs”。 - Simon Forsberg
你不需要复制文件。它们已经在你的系统上,你只需要告诉JVM在哪里找到它们。更多信息请参见我的答案 - forresthopkinsa

19

您可以在测试代码中使用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();

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

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

AmazonDynamoDB dynamodb = DynamoDBEmbedded.create();
dynamodb.listTables();

1
对于第一个选项,使用ServerRunner启动正常,但是一旦我尝试创建表格,就会出现“AmazonServiceException请求处理失败,因为发生了未知错误、异常或故障。(服务: AmazonDynamoDBv2;状态代码: 500;错误代码: InternalFailure;请求ID: ea0eff34-65e4-49d5-8ae9-3bfbfec9136e)”的错误提示。 - leonardoborges
7
对于嵌入式版本,我在initializeMetadataTables方法中从SQLLite得到了一个“NullPointerException”。:( - leonardoborges
4
在包含示例代码的官方存储库中似乎没有关于那个的任何信息。 - leonardoborges
我今天尝试了提供的解决方案,但似乎 http://dynamodb-local.s3-website-us-west-2.amazonaws.com/release 上的仓库不存在(或权限设置拒绝任何访问),因此我无法下载 https://forums.aws.amazon.com/ann.jspa?annID=3148 中描述的工件。 - Daniel Gruszczyk
@AlexanderPatrikalakis 你好,Alexander,问题最终是我的Maven配置问题。现在它确实可以工作了。感谢你的帮助。 - Daniel Gruszczyk
显示剩余6条评论

6
我已将上述答案分成两个 JUnit rules,不需要更改构建脚本,因为规则处理本机库内容。我这样做是因为我发现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>

Gradle:

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.+"
}

代码:

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

  @Test
  public void test() {
    doDynamoStuff(ddb.getClient());
  }
}

我在这个问题上遇到了麻烦。我在LocalDynamoDbRule.java:38中得到了一个NPEDynamoDBEmbedded.create()返回null)。在此之前,本地SQLite部分记录如下: com.almworks.sqlite4java.SQLiteException: [-91] cannot load library: com.almworks.sqlite4java.SQLiteException: [-91] sqlite4java cannot find native library。我使用的是com.github.mlk:DynamoDBLocal:1.11.119com.github.mlk:assortmentofjunitrules:1.5.39。难道LocalDynamoDbRule不应该处理所有本地SQLite的事情吗? - scho
这是我收到的错误完整堆栈跟踪:[链接](https://gist.github.com/scho/3101c83488e61d32234ce79c5db6ac06)。 - scho
它应该为您处理本地库的事情。我会研究一下。谢谢。 - Michael Lloyd Lee mlk
请发送你的 POM/gradle/任何相关部分的片段给我,谢谢。 - Michael Lloyd Lee mlk
我已经能够使用最小的POM进行复制。看起来这是Maven的一个问题,不会影响Gradle。我将继续调查。https://github.com/mlk/AssortmentOfJUnitRules/issues/2 - Michael Lloyd Lee mlk

5

试用 tempest-testing!它提供了一个 JUnit4 规则和一个 JUnit5 扩展,支持 AWS SDK 版本 1 和 2。

Tempest 提供了一个用于测试 DynamoDB 客户端库的工具,使用 DynamoDBLocal 进行测试。它包含两种实现方法:

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

功能矩阵:

特点 tempest-testing-jvm tempest-testing-docker
启动时间 约 1 秒 约 10 秒
内存使用量 较少 较多
依赖 sqlite4java 本地库 Docker

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

对于 AWS SDK 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"
}

对于AWS SDK 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"
}

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

class MyTest {
  @RegisterExtension
  TestDynamoDb db = new TestDynamoDb.Builder(JvmDynamoDbServer.Factory.INSTANCE) // or DockerDynamoDbServer
      // `MusicItem` is annotated with `@DynamoDBTable`. Tempest recreates this table before each test.
      .addTable(TestTable.create(MusicItem.TABLE_NAME, MusicItem.class))
      .build();

  @Test
  public void test() {
    PutItemRequest request = // ...;
    // Talk to the local DynamoDB.
    db.dynamoDb().putItem(request);
  }

}

我将一些现有的基于容器的测试代码迁移到了该库的JVM模式,运行良好并且提高了速度。 - Adrian Baker

2
看起来应该有更简单的方法。毕竟,DynamoDB本地仅仅是Java代码。我能不能以某种方式要求JVM分叉自身并查看资源以构建类路径呢?你可以沿着这些线路做一些事情,但要简单得多:以编程方式搜索类路径以查找本地库的位置,然后在启动DynamoDB之前设置sqlite4java.library.path属性。这是在tempest-testing中实现的方法,也是在这个答案(这里的代码)中实现的方法,这就是为什么它们可以作为纯库/classpath依赖关系而正常工作,什么都不需要。在我的例子中,我需要在JUnit扩展之外访问DynamoDB,但我仍然希望在库代码中获得自包含的东西,因此我提取了它所采取的方法。
import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded;
import com.amazonaws.services.dynamodbv2.local.shared.access.AmazonDynamoDBLocal;
import com.google.common.collect.MoreCollectors;
import java.io.File;
import java.util.Arrays;
import java.util.stream.Stream;
import org.junit.jupiter.api.condition.OS;

... 

  public AmazonDynamoDBLocal embeddedDynamoDb() {
    final OS os = Stream.of(OS.values()).filter(OS::isCurrentOs)
        .collect(MoreCollectors.onlyElement());
    final String prefix;
    switch (os) {
      case LINUX:
        prefix = "libsqlite4java-linux-amd64-";
        break;
      case MAC:
        prefix = "libsqlite4java-osx-";
        break;
      case WINDOWS:
        prefix = "sqlite4java-win32-x64-";
        break;
      default:
        throw new UnsupportedOperationException(os.toString());
    }
  
    System.setProperty("sqlite4java.library.path",
        Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator))
            .stream()
            .map(File::new)
            .filter(file -> file.getName().startsWith(prefix))
            .collect(MoreCollectors.onlyElement())
            .getParent());
    return DynamoDBEmbedded.create();
  }

很遗憾没有在许多平台上进行测试,而且错误处理可能需要改进。

可惜AWS没有花时间使这个库更加友好,因为这可以在库代码本身中轻松实现。


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;
        }
    });

不确定那是否是你寻找的,但那就是我们的做法。


5
谢谢您的想法。Mock测试是可以的,但很难完全正确地模拟协议。所以你最终测试的只是假设 Dynamo(或任何其他东西)表现得像你所想象的那样(即所模拟的方式),而不是真正测试你的代码是否与 Dynamo 实际上能够正常工作。如果你对 Dynamo 的行为做出错误的假设,你的代码和测试也会做出同样的假设,因此测试通过了,但实际上还是有漏洞的。 - Oliver Dain
3
听起来你正在进行集成测试,因此你不应该有太多的测试。你只需要确保可以执行基本操作,即验证连接是否正确。过去我会在测试中启动本地实例,然后使用硬编码值保存、读取和从本地数据库删除。除此之外,你进行了哪些测试?我总是建议进行单元测试(只测试一个组件,其他的组件用模拟)和集成测试(使用真实环境)。 - Steve Smith
3
从理论上讲,模拟是单元测试的正确方式,但本地DDB可以更可靠地确保代码是否正确。 - Cherish

1

最短的解决方案,用于修复sqlite4java.SQLiteException UnsatisfiedLinkError,如果它是使用gradle构建的java/kotlin项目(不需要更改$PATH)。

repositories {
    // ... other dependencies
    maven { url 'https://s3-us-west-2.amazonaws.com/dynamodb-local/release' } 
}

dependencies {
    testImplementation("com.amazonaws:DynamoDBLocal:1.13.6")
}

import org.gradle.internal.os.OperatingSystem
test {
    doFirst {
        // Fix for: UnsatisfiedLinkError -> provide a valid native lib path
        String nativePrefix = OperatingSystem.current().nativePrefix
        File nativeLib = sourceSets.test.runtimeClasspath.files.find {it.name.startsWith("libsqlite4java") && it.name.contains(nativePrefix) } as File
        systemProperty "sqlite4java.library.path", nativeLib.parent
    }
}

在测试类 (src/test) 中直接使用:

private lateinit var db: AmazonDynamoDBLocal

@BeforeAll
fun runDb() { db = DynamoDBEmbedded.create() }

@AfterAll
fun shutdownDb() { db.shutdown() }

0

我发现亚马逊的仓库没有索引文件,因此似乎无法像这样导入它:

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

唯一能让依赖项加载的方法是将 DynamoDbLocal 下载为 jar 文件,并像这样将其引入到我的构建脚本中:
dependencies {
    ...
    runtime files('libs/DynamoDBLocal.jar')
    ...
}

当然,这意味着所有的SQLite和Jetty依赖都需要手动引入 - 我仍在努力弄清楚这一点。如果有人知道一个可靠的DynamoDbLocal仓库,我真的很想知道。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接