wake-up-neo.com

Einfacheres lokales Testen von DynamoDB

Ich benutze DynamoDB local für Unit-Tests. Das ist nicht schlimm, hat aber einige Nachteile. Speziell:

  • Sie müssen den Server irgendwie starten, bevor Ihre Tests ausgeführt werden
  • Der Server wird vor jedem Test nicht gestartet und gestoppt. Die Tests werden also voneinander abhängig, wenn Sie nicht Code hinzufügen, um alle Tabellen usw. nach jedem Test zu löschen
  • Alle Entwickler müssen es installiert haben

Was ich tun möchte, ist etwas wie die lokale DynamoDB-JAR und die anderen Jars, auf die es angewiesen ist, in mein Test/Resources-Verzeichnis (ich schreibe Java). Dann würde ich vor jedem Test mit -inMemory starten und nach dem Test den Test abbrechen. Auf diese Weise erhält jeder, der das Git Repo herunterzieht, eine Kopie von allem, was er zur Durchführung der Tests benötigt, und jeder Test ist unabhängig von den anderen.

Ich habe einen Weg gefunden, um diese Arbeit zu machen, aber es ist hässlich, also suche ich nach Alternativen. Die Lösung, die ich habe, ist, eine .Zip-Datei des lokalen DynamoDB-Materials in test/resources zu packen, dann in einer @Before-Methode in ein temporäres Verzeichnis zu extrahieren und einen neuen Java-Prozess zu starten, um es auszuführen. Das funktioniert, aber es ist hässlich und hat einige Nachteile:

  • Jeder benötigt die Java-Programmdatei auf seinem $ PATH
  • Ich muss eine Zip auf die lokale Festplatte auspacken. Die Verwendung lokaler Festplatten ist für Tests oft wählerisch, insbesondere bei kontinuierlichen Builds und dergleichen.
  • Ich muss einen Prozess starten und warten, bis er für jeden Unit-Test gestartet wird, und diesen Prozess dann nach jedem Test beenden. Abgesehen davon, dass es langsam ist, scheint das Potenzial für übrig gebliebene Prozesse hässlich zu sein.

Es scheint, als sollte es einen einfacheren Weg geben. DynamoDB Local ist schließlich nur Java-Code. Kann ich die JVM nicht irgendwie bitten, sich selbst zu verzweigen und in die Ressourcen zu schauen, um einen Klassenpfad zu erstellen? Oder, noch besser, kann ich nicht einfach die main-Methode von DynamoDb Local aus einem anderen Thread aufrufen, sodass dies alles in einem einzigen Prozess geschieht? Irgendwelche Ideen?

PS: Ich kenne Alternator, aber es scheint andere Nachteile zu haben, also neige ich dazu, bei Amazon zu bleiben, wenn es funktioniert.

33
Oliver Dain

Um DynamoDBLocal verwenden zu können, müssen Sie die folgenden Schritte ausführen.

  1. Holen Sie sich direkte DynamoDBLocal-Abhängigkeit
  2. Erhalten Sie native SQLite4Java-Abhängigkeiten
  3. Legen Sie sqlite4Java.library.path fest, um native Bibliotheken anzuzeigen

1. Direkte DynamoDBLocal-Abhängigkeit erhalten

Dieser ist der leichte. Sie benötigen dieses Repository wie in AWS-Foren beschrieben.

<!--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. Native SQLite4Java-Abhängigkeiten abrufen

Wenn Sie diese Abhängigkeiten nicht hinzufügen, schlagen Ihre Tests mit einem internen Fehler von 500 fehl.

Fügen Sie zunächst diese Abhängigkeiten hinzu:

<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>

Fügen Sie dieses Plugin hinzu, um native Abhängigkeiten in bestimmten Ordnern zu erhalten:

<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. Setze sqlite4Java.library.path, um native Bibliotheken anzuzeigen

Als letzten Schritt müssen Sie die Systemeigenschaft sqlite4Java.library.path auf das Verzeichnis native-libs setzen. Dies ist in Ordnung, bevor Sie Ihren lokalen Server erstellen.

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

Nach diesen Schritten können Sie DynamoDBLocal beliebig verwenden. Hier ist eine Junit-Regel, mit der ein lokaler Server erstellt wird.

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

Sie können diese Regel so verwenden

@RunWith(JUnit4.class)
public class UserDAOImplTest {

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

Sie können DynamoDB Local als Testabhängigkeit für Maven in Ihrem Testcode verwenden, wie in dieser announcement gezeigt. Sie können über HTTP laufen:

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

Sie können auch im eingebetteten Modus ausgeführt werden:

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

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

Dies ist eine Wiederholung der Antwort von bhdrkn für Gradle-Benutzer (seine basiert auf Maven). Es sind immer noch die gleichen drei Schritte:

  1. Holen Sie sich direkte DynamoDBLocal-Abhängigkeit
  2. Erhalten Sie native SQLite4Java-Abhängigkeiten
  3. Legen Sie sqlite4Java.library.path fest, um native Bibliotheken anzuzeigen

1. Holen Sie sich eine direkte DynamoDBLocal-Abhängigkeit

Fügen Sie den Abschnitt "Abhängigkeiten" Ihrer build.gradle-Datei hinzu ...

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

2. Holen Sie sich native SQLite4Java-Abhängigkeiten

Die sqlite4Java-Bibliotheken werden bereits als Abhängigkeit von DynamoDBLocal heruntergeladen. Die Bibliotheksdateien müssen jedoch an die richtige Stelle kopiert werden. Fügen Sie Ihrer build.gradle-Datei hinzu ...

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

3. Legen Sie für sqlite4Java.library.path fest, dass native Bibliotheken angezeigt werden

Wir müssen Gradle anweisen, copyNativeDeps zum Testen auszuführen und sqlite4Java zu sagen, wo die Dateien zu finden sind. Fügen Sie Ihrer build.gradle-Datei hinzu ...

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

Ich habe die obigen Antworten in zwei JUnit-Regeln eingepackt , die keine Änderungen am Erstellungsskript erfordern, da die Regeln das native Bibliothekskriterium behandeln. Ich tat dies, als ich feststellte, dass Idea die Gradle/Maven-Lösungen nicht mochte, da es einfach losging und seine eigenen Sachen machte.

Dies bedeutet die Schritte:

  • Rufen Sie die AssortmentOfJUnitRules-Version 1.5.32 oder höher ab 
  • Holen Sie sich die Direct DynamoDBLocal-Abhängigkeit
  • Fügen Sie Ihrem JUnit-Test die LocalDynamoDbRule oder HttpDynamoDbRule hinzu.

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

Code:

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

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

Im August 2018 von Amazon angekündigt neues Docker-Image mit lokalem Amazon DynamoDB Local. Es ist nicht erforderlich, JARs herunterzuladen und auszuführen sowie OS-spezifische Binärdateien von Drittanbietern hinzuzufügen (ich spreche von sqlite4Java).

Es ist so einfach, einen Docker-Container vor den Tests zu starten:

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

Sie können dies wie oben beschrieben manuell für die lokale Entwicklung durchführen oder in Ihrer CI-Pipeline verwenden. Viele CI-Services bieten die Möglichkeit, während der Pipeline zusätzliche Container zu starten, die Abhängigkeiten für Ihre Tests bereitstellen können. Hier ist ein Beispiel für 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

Oder 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

Und so weiter. Die Idee ist, die gesamte Konfiguration, die Sie in otheranswers sehen, aus Ihrem Build-Tool zu entfernen und die Abhängigkeit extern bereitzustellen. Stellen Sie sich das als Abhängigkeitsinjektion/IoC vor, aber für den gesamten Service, nicht nur eine einzelne Bean.

Nachdem Sie den Container gestartet haben, können Sie einen Client erstellen, der darauf verweist:

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

Nun zu den ursprünglichen Fragen:

Sie müssen den Server irgendwie starten, bevor Ihre Tests ausgeführt werden

Sie können es einfach manuell starten oder ein Entwicklungsskript dafür vorbereiten. IDEs bieten normalerweise eine Möglichkeit, beliebige Befehle auszuführen, bevor eine Task ausgeführt wird. Sie können also make IDE den Container für Sie starten. Ich denke, dass das Ausführen von etwas lokal nicht in diesem Fall oberste Priorität haben sollte. Stattdessen sollten Sie sich auf die Konfiguration von CI konzentrieren und die Entwickler den Container so starten lassen, wie es für sie angenehm ist.

Der Server wird vor jedem Test nicht gestartet und gestoppt. Die Tests werden also voneinander abhängig, wenn Sie nicht Code hinzufügen, um alle Tabellen usw. nach jedem Test zu löschen

Das stimmt, aber ... Sie sollten solche schwergewichtigen Dinge nicht starten und stoppen Tabellen vor/nach jedem Test neu erstellen. DB-Tests sind fast immer voneinander abhängig und das ist in Ordnung für sie. Verwenden Sie einfach eindeutige Werte für jeden Testfall (z. B. den Hash-Schlüssel des Set-Elements für die Ticket-ID/spezifische Testfall-ID, an der Sie gerade arbeiten). Für die Seed-Daten würde ich empfehlen, sie auch aus dem Build-Tool zu entfernen und den Code zu testen. Erstellen Sie entweder Ihr eigenes Image mit allen benötigten Daten oder verwenden Sie AWS CLI, um Tabellen zu erstellen und Daten einzufügen. Befolgen Sie das Prinzip der Einzelverantwortung und das Prinzip der Abhängigkeitsinjektion: Ihr Testcode darf nur Tests ausführen. Die gesamte Umgebung (Tabellen und Daten sollten in diesem Fall für sie bereitgestellt werden). Das Erstellen einer Tabelle in einem Test ist falsch, da diese Tabelle in der Praxis bereits existiert (es sei denn, Sie testen eine Methode, mit der tatsächlich eine Tabelle erstellt wird).

Alle Entwickler müssen es installiert haben

Docker sollte 2018 für jeden Entwickler ein Muss sein, das ist also kein Problem.


Wenn Sie JUnit 5 verwenden, kann es sinnvoll sein, ein DynamoDB Local-Erweiterung zu verwenden, das den Client in Ihre Tests einfügt (ja, ich mache eine Eigenwerbung):

  1. Fügen Sie Ihrem Build JCenter Repository hinzu.

    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()
    }
    
  2. Fügen Sie eine Abhängigkeit von by.dev.madhead.aws-junit5:dynamodb-v1 hinzu.

    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")
    }
    
  3. Verwenden Sie die Erweiterung in Ihren 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();
        }
    }
    
2
madhead

Ich habe festgestellt, dass das Amazon-Repo keine Indexdatei ist, also nicht so zu funktionieren, dass Sie es wie folgt einbringen können:

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

Die einzige Möglichkeit, die Abhängigkeiten zum Laden zu bringen, ist das Herunterladen von DynamoDbLocal als Jar und das Einfügen in mein Build-Skript wie folgt:

dependencies {
    ...
    runtime files('libs/DynamoDBLocal.jar')
    ...
}

Das bedeutet natürlich, dass alle Abhängigkeiten von SQLite und Jetty von Hand eingebracht werden müssen - ich versuche immer noch, dies richtig zu machen. Wenn jemand ein zuverlässiges Repo für DynamoDbLocal kennt, würde ich es wirklich gerne wissen.

0
Michael Coxon

In Hadoop verwenden wir auch DynamoDBLocal zum Testen und Debuggen. Wie das Beispiel dort verwendet wird, finden Sie unter: https://github.com/Apache/hadoop/blob/HADOOP-13345/hadoop-tools/hadoop-aws/src/test/Java/org/Apache/hadoop/ fs/s3a/s3guard/TestDynamoDBMetadataStore.Java # L113

0
Mingliang Liu

Für Unit-Tests bei der Arbeit verwende ich Mockito, dann mockte ich nur den AmazonDynamoDBClient. dann simulieren Sie die Renditen mit wann. wie das Folgende:

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

ich bin nicht sicher, ob es das ist, wonach Sie suchen, aber so machen wir es.

0
Steve Smith

Für DynamoDB Local gibt es einige node.js-Wrapper. Dies ermöglicht die einfache Durchführung von Unit-Tests in Kombination mit Task-Läufern wie Schlucken oder Grunzen. Versuchen Sie dynamodb-localhost , dynamodb-local

0
Ashan