我正在使用DynamoDB local进行单元测试。这不错,但有一些缺点。具体做法是:
我想要做的就是将DynamoDB本地jar和它所依赖的其他jar放在我的test / resources目录中(我正在编写Java)。然后在每次测试之前,我会启动它,与-inMemory
一起运行,在测试之后我会停止它。这样,任何下拉git repo的人都会获得运行测试所需的所有内容的副本,每个测试都独立于其他测试。
我找到了一种方法来完成这项工作,但这很难看,所以我正在寻找替代方案。我的解决方案是将一个DynamoDB本地内容的.zip文件放在test / resources中,然后在@Before方法中,我将它解压缩到一些临时目录并启动一个新的java进程来执行它。这是有效的,但它很难看并且有一些缺点:
似乎应该有一种更简单的方法。毕竟,DynamoDB Local只是Java代码。我不能以某种方式要求JVM自行分配并查看资源来构建类路径吗?或者,更好的是,我不能从其他线程调用DynamoDb Local的main
方法,所以这一切都发生在一个进程中?有什么想法吗?
答案 0 :(得分:56)
要使用DynamoDBLocal,您需要按照以下步骤操作。
sqlite4java.library.path
以显示本机库<强> 1。获取Direct DynamoDBLocal依赖
这个很容易。您需要此存储库,如AWS Forums中所述。
<!--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();
}
答案 1 :(得分:14)
您可以在测试代码中将DynamoDB Local用作Maven测试依赖项,如此announcement所示。您可以通过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();
答案 2 :(得分:12)
这是对Gradle用户的bhdrkn答案的重述(他的是基于Maven)。它仍然是相同的三个步骤:
- 获取Direct DynamoDBLocal Dependency
- 获取原生SQLite4Java依赖项
- 设置sqlite4java.library.path以显示本机库
醇>
添加到build.gradle文件的依赖项部分...
dependencies {
testCompile "com.amazonaws:DynamoDBLocal:1.+"
}
sqlite4java库已经作为DynamoDBLocal的依赖项下载,但是库文件需要复制到正确的位置。添加到build.gradle文件...
task copyNativeDeps(type: Copy) {
from(configurations.compile + configurations.testCompile) {
include '*.dll'
include '*.dylib'
include '*.so'
}
into 'build/libs'
}
我们需要告诉Gradle运行copyNativeDeps
进行测试并告诉sqlite4java在哪里找到这些文件。添加到build.gradle文件...
test {
dependsOn copyNativeDeps
systemProperty "java.library.path", 'build/libs'
}
答案 3 :(得分:5)
我将上面的答案包含在两个JUnit rules中,不需要更改构建脚本,因为规则处理本机库的内容。我这样做是因为我发现Idea不喜欢Gradle / Maven解决方案因为它刚刚开始并且做了自己的事情。
这意味着步骤是:
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>
<强>摇篮:强>
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());
}
}
答案 4 :(得分:2)
2018年8月Amazon announced新Docker image上安装了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管道:
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
以此类推。这个想法是将other answers中可以看到的所有配置移出构建工具,并从外部提供依赖项。将其视为依赖项注入/ 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通常提供一种在执行任务之前运行任意命令的方法,因此您可以make IDE为您启动容器。我认为,在这种情况下,在本地运行某些内容不是最优先考虑的事情,而是您应该专注于配置CI,并让开发人员在感到舒适的情况下启动容器。
在每次测试之前服务器不会启动和停止,因此除非您添加代码以在每次测试后删除所有表等,否则测试将相互依赖。
那是真的,但是……您不应该启动和停止此类重量级的东西 并在每次测试之前/之后重新创建表。数据库测试几乎总是相互依赖的,这对他们来说是可以的。只需为每个测试用例使用唯一的值(例如,将项目的哈希键设置为工单ID /您正在使用的特定测试用例ID)。至于种子数据,我建议也将其从构建工具和测试代码中移出。使用所需的所有数据制作自己的映像,或者使用AWS CLI创建表并插入数据。遵循单一责任原则和依赖项注入原则:测试代码除测试外不得执行任何操作。所有环境(在这种情况下,应为其提供表和数据)。在测试中创建表是错误的,因为在现实生活中该表已经存在(当然,除非您正在测试实际创建表的方法)。
所有开发人员都需要安装它
Docker应该是2018年所有开发人员的必备工具,所以这不是问题。
如果您使用的是JUnit 5,最好使用DynamoDB Local extension,它将在测试中注入客户端(是的,我正在自我推广):
将JCenter存储库添加到您的版本中。
pom.xml :
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>bintray</name>
<url>https://jcenter.bintray.com</url>
</repository>
</repositories>
build.gradle
repositories {
jcenter()
}
添加对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>
build.gradle
dependencies {
testImplementation("by.dev.madhead.aws-junit5:dynamodb-v1:1.0.0")
}
在测试中使用扩展名:
@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();
}
}
答案 5 :(得分:1)
试试tempest-testing!它提供了一个 JUnit4 规则和一个 JUnit5 扩展。它还支持 AWS SDK v1 和 SDK v2。
Tempest 提供了一个用于测试 DynamoDB 客户端的库 使用 DynamoDBLocal .它有两个实现:
DynamoDBProxyServer
支持的 sqlite4java
,
可在 most platforms 获得。特征矩阵:
功能 | tempest-testing-jvm | tempest-testing-docker |
---|---|---|
启动时间 | ~1s | ~10s |
内存使用 | 少 | 更多 |
依赖 | sqlite4java 原生库 | Docker |
要使用 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"
}
对于 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"
}
然后在用 @org.junit.jupiter.api.Test
注释的测试中,您可以添加 TestDynamoDb
作为测试
extension。这个扩展启动了一个
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);
}
}
答案 6 :(得分:0)
对于工作中的单元测试,我使用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;
}
});
不确定这是否是您所寻找的,但这就是我们如何做到的。
答案 7 :(得分:0)
DynamoDB Local有几个node.js包装器。这些允许与gulp或grunt等任务运行器一起轻松执行单元测试。试试dynamodb-localhost, dynamodb-local
答案 8 :(得分:0)
在Hadoop中,我们还使用DynamoDBLocal进行测试和调试工作。请参阅以下示例:https://github.com/apache/hadoop/blob/HADOOP-13345/hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/TestDynamoDBMetadataStore.java#L113
答案 9 :(得分:0)
我发现amazon repo没有索引文件,因此似乎没有以允许你这样的方式运行:
maven {
url = "https://s3-us-west-2.amazonaws.com/dynamodb-local/release"
}
我可以将依赖项加载的唯一方法是将DynamoDbLocal作为jar下载并将其带入我的构建脚本中,如下所示:
dependencies {
...
runtime files('libs/DynamoDBLocal.jar')
...
}
当然这意味着需要手动引入所有SQLite和Jetty依赖项 - 我仍然试图做到这一点。如果有人知道DynamoDbLocal的可靠回购,我真的很想知道。
答案 10 :(得分:0)
您还可以使用此轻量级测试容器'Dynalite'
https://www.testcontainers.org/modules/databases/dynalite/
来自测试容器:
Dynalite是DynamoDB的克隆,可以进行本地测试。很轻 并快速运行。
答案 11 :(得分:0)
最短解决方案,并修复 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() }