web-dev-qa-db-fra.com

Tests locaux DynamoDB simplifiés

J'utilise DynamoDB local pour les tests unitaires. Ce n'est pas mauvais, mais a quelques inconvénients. Plus précisément:

  • Vous devez en quelque sorte démarrer le serveur avant l'exécution de vos tests
  • Le serveur n'étant ni démarré ni arrêté avant chaque test, les tests deviennent interdépendants, sauf si vous ajoutez du code pour supprimer toutes les tables, etc. après chaque test.
  • Tous les développeurs ont besoin de l'avoir installé

Ce que je veux faire, c'est comme mettre le fichier jar local DynamoDB et les autres fichiers jar dont il dépend dans mon répertoire test/resources (j'écris en Java). Ensuite, avant chaque test, je le lancerais avec -inMemory et, après le test, je l'arrêterais. Ainsi, toute personne qui rédige le rapport git reçoit une copie de tout ce dont elle a besoin pour exécuter les tests et chaque test est indépendant des autres.

J'ai trouvé un moyen de faire ce travail, mais c'est moche, alors je cherche des alternatives. La solution que j'ai est de mettre un fichier .Zip du matériel local DynamoDB dans test/resources, puis dans une méthode @Before, je l'extrais dans un répertoire temporaire et je lance un nouveau processus Java pour l'exécuter. Cela fonctionne, mais c'est moche et présente quelques inconvénients:

  • Tout le monde a besoin de l'exécutable Java sur son $ PATH
  • Je dois décompresser un fichier Zip sur le disque local. Utiliser un disque local est souvent difficile à tester, en particulier avec des versions continues et autres.
  • Je dois créer un processus et attendre son lancement pour chaque test unitaire, puis le tuer après chaque test. En plus d’être lents, le potentiel de processus restants semble très laid.

Il semble qu'il devrait y avoir un moyen plus facile. DynamoDB Local est, après tout, juste du code Java. Est-ce que je ne peux pas en quelque sorte demander à la JVM de se bichonner et de regarder à l'intérieur des ressources pour créer un chemin de classe? Ou, mieux encore, ne puis-je pas simplement appeler la méthode main de DynamoDb Local à partir d'un autre thread afin que tout cela se produise en un seul processus? Des idées?

PS: Je connais Alternator, mais il semble présenter d'autres inconvénients, et je suis donc enclin à rester avec la solution prise en charge par Amazon si je parviens à le faire fonctionner.

33
Oliver Dain

Pour utiliser DynamoDBLocal, vous devez suivre ces étapes.

  1. Obtenir une dépendance directe DynamoDBLocal
  2. Obtenir les dépendances natives SQLite4Java
  3. Configurez sqlite4Java.library.path pour afficher les bibliothèques natives

1. Obtenir une dépendance directe DynamoDBLocal

Celui-ci est le facile. Vous avez besoin de ce référentiel comme expliqué dans 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. Obtenir les dépendances SQLite4Java natives

Si vous n'ajoutez pas ces dépendances, vos tests échoueront avec une erreur interne de 500.

Tout d'abord, ajoutez ces dépendances:

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

Ajoutez ensuite ce plugin pour obtenir des dépendances natives dans un dossier spécifique:

<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. Définir sqlite4Java.library.path pour afficher les bibliothèques natives

Enfin, vous devez définir la propriété système sqlite4Java.library.path dans le répertoire native-libs. Vous pouvez le faire juste avant de créer votre serveur local.

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

Après ces étapes, vous pouvez utiliser DynamoDBLocal à votre guise. Voici une règle Junit qui crée un serveur local pour cela.

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

Vous pouvez utiliser cette règle comme ceci

@RunWith(JUnit4.class)
public class UserDAOImplTest {

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

Vous pouvez utiliser DynamoDB Local comme dépendance de test Maven dans votre code de test, comme indiqué dans cette annonce . Vous pouvez exécuter sur 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();

Vous pouvez également exécuter en mode intégré:

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

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

C’est une reformulation de la réponse de bhdrkn pour les utilisateurs de Gradle (celle-ci est basée sur Maven). Ce sont toujours les mêmes trois étapes:

  1. Obtenir une dépendance directe DynamoDBLocal
  2. Obtenir les dépendances natives SQLite4Java
  3. Définir sqlite4Java.library.path pour afficher les bibliothèques natives

1. Obtenir une dépendance directe DynamoDBLocal

Ajoutez à la section des dépendances de votre fichier build.gradle ...

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

2. Obtenez les dépendances natives SQLite4Java

Les bibliothèques sqlite4Java seront déjà téléchargées en tant que dépendance de DynamoDBLocal, mais les fichiers de la bibliothèque doivent être copiés au bon endroit. Ajoutez à votre fichier build.gradle ...

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

3. Configurez sqlite4Java.library.path pour afficher les bibliothèques natives

Nous devons dire à Gradle d’exécuter copyNativeDeps pour les tests et à sqlite4Java où trouver les fichiers. Ajoutez à votre fichier build.gradle ...

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

J'ai résumé les réponses ci-dessus dans deux JUnit rules qui ne nécessite aucune modification du script de construction car les règles gèrent les éléments de la bibliothèque native. C'est ce que j'ai fait, car j'ai constaté qu'Idea n'aimait pas les solutions Gradle/Maven.

Cela signifie que les étapes sont les suivantes:

  • Obtenez la dépendance AssortmentOfJUnitRules version 1.5.32 ou supérieure 
  • Obtenir la dépendance Direct DynamoDBLocal
  • Ajoutez le LocalDynamoDbRule ou le HttpDynamoDbRule à votre test 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.+"
}

Code:

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

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

En août 2018 Amazon annoncé new image Docker avec Amazon DynamoDB Local intégré. Il n'est pas nécessaire de télécharger et d'exécuter des fichiers JAR, ni d'ajouter des fichiers binaires tiers spécifiques au système d'exploitation (je parle de sqlite4Java).

C’est aussi simple que de démarrer un conteneur Docker avant les tests:

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

Vous pouvez le faire manuellement pour le développement local, comme décrit ci-dessus, ou l'utiliser dans votre pipeline CI. De nombreux services de CI offrent la possibilité de démarrer des conteneurs supplémentaires pendant le pipeline, ce qui peut fournir des dépendances pour vos tests. Voici un exemple pour 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

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

Etc. L'idée est de déplacer toute la configuration que vous pouvez voir dans autre _ { réponses de votre outil de génération et de fournir la dépendance en externe. Pensez-y à l'injection de dépendance/IoC mais à l'ensemble du service, pas à un seul haricot.

Après avoir démarré le conteneur, vous pouvez créer un client pointant vers celui-ci:

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

Passons maintenant aux questions initiales:

Vous devez en quelque sorte démarrer le serveur avant l'exécution de vos tests

Vous pouvez simplement le démarrer manuellement ou préparer un script pour les développeurs. Les IDE fournissent généralement un moyen d'exécuter des commandes arbitraires avant d'exécuter une tâche. Vous pouvez donc make IDE } _ démarrer le conteneur pour vous. Je pense qu'exécuter quelque chose localement ne devrait pas être une priorité absolue dans ce cas, mais vous devriez plutôt vous concentrer sur la configuration de CI et laisser les développeurs démarrer le conteneur comme il leur convient.

Le serveur n'étant ni démarré ni arrêté avant chaque test, les tests deviennent interdépendants, sauf si vous ajoutez du code pour supprimer toutes les tables, etc. après chaque test.

C'est vrai, mais… Vous ne devriez pas commencer et arrêter des choses aussi lourdes Et recréer des tables avant/après chaque test. Les tests de base de données sont presque toujours interdépendants et cela leur convient. Utilisez simplement des valeurs uniques pour chaque cas de test (par exemple, la clé de hachage de l'élément à l'ID de ticket/l'ID de cas de test spécifique sur lequel vous travaillez). En ce qui concerne les données de départ, je vous recommande également de les déplacer de l'outil de génération et du code de test. Créez votre propre image avec toutes les données dont vous avez besoin ou utilisez AWS CLI pour créer des tables et insérer des données. Suivez le principe de responsabilité unique et les principes d'injection de dépendance: votre code de test ne doit faire que des tests. Tout l'environnement (dans ce cas, des tableaux et des données doivent leur être fournis). Créer une table dans un test est une erreur, car dans la vie réelle, cette table existe déjà (sauf si vous testez une méthode qui crée réellement une table, bien sûr).

Tous les développeurs ont besoin de l'avoir installé

Docker devrait être un must pour tous les développeurs en 2018, donc ce n'est pas un problème.


Et si vous utilisez JUnit 5, il peut être judicieux d’utiliser une extension DynamoDB Local qui injectera le client dans vos tests (oui, je fais une auto-promotion):

  1. Ajoutez le référentiel JCenter à votre construction.

    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. Ajouter une dépendance sur 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")
    }
    
  3. Utilisez l'extension dans vos 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

J'ai trouvé que le référentiel Amazon n'avait pas de fichier d'index, il ne semble donc pas fonctionner de manière à vous permettre de l'introduire comme ceci:

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

Le seul moyen de charger les dépendances est de télécharger DynamoDbLocal en tant que fichier jar et de l'inclure dans mon script de construction, comme suit:

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

Bien sûr, cela signifie que toutes les dépendances SQLite et Jetty doivent être importées manuellement. J'essaie toujours de bien comprendre. Si quelqu'un connaît un référentiel fiable pour DynamoDbLocal, j'aimerais vraiment le savoir.

0
Michael Coxon

Dans Hadoop, nous utilisons également DynamoDBLocal pour les travaux de test et de débogage. S'il vous plaît voir comment il est utilisé ici comme exemple à: 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

Pour les tests unitaires au travail, j'utilise Mockito, puis je me moque de AmazonDynamoDBClient. puis simulez les déclarations en utilisant quand. comme ce qui suit:

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

je ne sais pas si c'est ce que vous cherchez, mais c'est comme cela que nous le faisons.

0
Steve Smith

Il existe plusieurs wrappers node.js pour DynamoDB Local. Celles-ci permettent d'exécuter facilement des tests unitaires en combinaison avec des coureurs de tâches comme gulp ou grunt. Essayez dynamodb-localhost , dynamodb-local

0
Ashan