Gemakkelijker DynamoDB lokaal testen

Ik gebruik DynamoDB lokaalvoor eenheid testen. Het is niet slecht, maar heeft enkele nadelen. Specifiek:

  • Je moet de server op de een of andere manier starten voordat je tests worden uitgevoerd
  • De server wordt niet voor elke test gestart en gestopt, dus tests worden onderling afhankelijk, tenzij u code toevoegt om na elke test alle tabellen te verwijderen, enz.
  • Alle ontwikkelaars moeten het geïnstalleerd hebben

Wat ik wil doen is zoiets als de DynamoDB lokale jar, en de andere jars waarvan het afhankelijk is, in mijn test/resourcesdirectory plaatsen (ik schrijf in Java). Dan zou ik het voor elke test opstarten, draaiend met -inMemory, en na de test zou ik het stoppen. Op die manier krijgt iedereen die de git-repo neerhaalt een kopie van alles wat ze nodig hebben om de tests uit te voeren en is elke test onafhankelijk van de andere.

Ik heb een manier gevonden om dit te laten werken, maar het is lelijk, dus ik ben op zoek naar alternatieven. De oplossing die ik heb is om een ​​.zip-bestand van de lokale DynamoDB-dingen in test/resourceste plaatsen, en vervolgens in de @Before-methode, zou ik het uitpakken naar een tijdelijke directory en start een nieuw Java-proces om het uit te voeren. Dat werkt, maar het is lelijk en heeft enkele nadelen:

  • Iedereen heeft het uitvoerbare Java-bestand nodig op zijn $PATH
  • Ik moet een zip uitpakken naar de lokale schijf. Het gebruik van een lokale schijf is vaak lastig om te testen, vooral bij continue builds en dergelijke.
  • Ik moet een proces spawnen en wachten tot het start voor elke eenheidstest, en dan dat proces na elke test beëindigen. Behalve dat het traag is, lijkt het potentieel voor overgebleven processen lelijk.

Het lijkt erop dat er een eenvoudigere manier zou moeten zijn. DynamoDB Local is tenslotte gewoon Java-code. Kan ik de JVM niet op de een of andere manier vragen om zichzelf te splitsen en in de bronnen te kijken om een ​​klassenpad te bouwen? Of, nog beter, kan ik niet gewoon de mainmethode van DynamoDB Local aanroepen vanuit een andere thread, zodat dit allemaal in één proces gebeurt? Enig idee?

PS: Ik ben op de hoogte van Alternator, maar het lijkt andere nadelen te hebben, dus ik ben geneigd om bij de door Amazon ondersteunde oplossing te blijven als ik het kan laten werken.


Antwoord 1, autoriteit 100%

Om DynamoDBLocal te gebruiken, moet je deze stappen volgen.

  1. Ontvang directe DynamoDBLocal-afhankelijkheid
  2. Native SQLite4Java-afhankelijkheden ophalen
  3. Stel sqlite4java.library.pathin om native bibliotheken weer te geven

1. Directe DynamoDBLocal-afhankelijkheid verkrijgen

Dit is de makkelijke. Je hebt deze repository nodig zoals hier.

<!--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-afhankelijkheden ophalen

Als u deze afhankelijkheden niet toevoegt, zullen uw tests mislukken met een interne fout van 500.

Voeg eerst deze afhankelijkheden toe:

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

Voeg vervolgens deze plug-in toe om native afhankelijkheden naar een specifieke map te krijgen:

<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. Stel sqlite4java.library.pathin om native bibliotheken weer te geven

Als laatste stap moet u de systeemeigenschap sqlite4java.library.pathinstellen op de map native-libs. Het is oké om dat te doen net voordat je je lokale server aanmaakt.

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

Na deze stappen kunt u DynamoDBLocal gebruiken zoals u wilt. Hier is een Junit-regel die daarvoor een lokale server maakt.

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

U kunt deze regel als volgt gebruiken

@RunWith(JUnit4.class)
public class UserDAOImplTest {
    @ClassRule
    public static final LocalDynamoDBCreationRule dynamoDB = new LocalDynamoDBCreationRule();
}

Antwoord 2, autoriteit 26%

In augustus 2018 Amazon kondigdenieuwe Docker-afbeeldingmet Amazon DynamoDB Local aan boord. Je hoeft geen JAR’s te downloaden en uit te voeren en je hoeft er ook geen externe OS-specifieke binaire bestanden voor te gebruiken (ik heb het over sqlite4java).

Het is net zo eenvoudig als het starten van een Docker-container vóór de tests:

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

Je kunt dat handmatig doen voor lokale ontwikkeling, zoals hierboven beschreven, of het gebruiken in je CI-pijplijn. Veel CI-services bieden de mogelijkheid om tijdens de pijplijn extra containers te starten die afhankelijkheden voor uw tests kunnen opleveren. Hier is een voorbeeld voor 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

Of Bitbucket-pijplijnen:

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

En ga zo maar door. Het idee is om alle configuraties te verplaatsen die je kunt zien in otherantwoordenvanuit uw build-tool en zorgen voor de afhankelijkheid extern. Zie het als afhankelijkheidsinjectie / IoC, maar voor de hele service, niet alleen een enkele boon.

Nadat je de container hebt gestart, kun je een client maken die ernaar verwijst:

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

Nu naar de oorspronkelijke vragen:

U moet de server op de een of andere manier starten voordat uw tests worden uitgevoerd

Je kunt het gewoon handmatig starten of er een ontwikkelaarsscript voor voorbereiden. IDE’s bieden meestal een manier om willekeurige opdrachten uit te voeren voordat een taak wordt uitgevoerd, dus u kunt maak IDEom de container voor je te starten. Ik denk dat het lokaal draaien van iets in dit geval geen topprioriteit zou moeten zijn, maar dat je je in plaats daarvan moet concentreren op het configureren van CI en de ontwikkelaars de container moet laten starten zoals het voor hen prettig is.

De server wordt niet voor elke test gestart en gestopt, dus tests worden onderling afhankelijk, tenzij u code toevoegt om na elke test alle tabellen te verwijderen, enz.

Dat klopt, maar… Je moet zulke zware dingen niet beginnen en stoppen
en maak tabellen opnieuw voor / na elke test. DB-tests zijn bijna altijd onderling afhankelijk en dat is oké voor hen. Gebruik gewoon unieke waarden voor elke testcase (stel bijvoorbeeld de hash-sleutel van het item in op ticket-ID / specifieke testcase-ID waaraan u werkt). Wat betreft de seed-gegevens, raad ik aan deze ook uit de build-tool en testcode te verplaatsen. Maak je eigen afbeelding met alle gegevens die je nodig hebt of gebruik AWS CLI om tabellen te maken en gegevens in te voegen. Volg het principe van de enkele verantwoordelijkheid en de afhankelijkheidsinjectie: uw testcode mag niets anders doen dan tests. Alle omgevingen (tabellen en gegevens moeten in dit geval voor hen worden verstrekt). Een tabel maken in een test is verkeerd, omdat die tabel in het echte leven al bestaat (tenzij je een methode test die daadwerkelijk een tabel maakt natuurlijk).

Alle ontwikkelaars moeten het geïnstalleerd hebben

Docker zou in 2018 een must moeten zijn voor elke ontwikkelaar, dus dat is geen probleem.


En als je JUnit 5 gebruikt, kan het een goed idee zijn om een ​​DynamoDB Local-extensie te gebruiken die de klant in je tests zal injecteren (ja, ik doe een zelfpromotie):

  1. Voeg een afhankelijkheid toe aan me.madhead.aws-junit5:dynamodb-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. Gebruik de extensie in uw 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();
        }
    }
    

Antwoord 3, autoriteit 24%

Dit is een herformulering van het antwoord van bhdrkn voor Gradle-gebruikers (de zijne is gebaseerd op Maven). Het zijn nog steeds dezelfde drie stappen:

  1. Ontvang directe DynamoDBLocal-afhankelijkheid
  2. Native SQLite4Java-afhankelijkheden ophalen
  3. Stel sqlite4java.library.path in om native bibliotheken weer te geven

1. Directe DynamoDBLocal-afhankelijkheid verkrijgen

Toevoegen aan het gedeelte afhankelijkheden van uw build.gradle-bestand…

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

2. Native SQLite4Java-afhankelijkheden ophalen

De sqlite4java-bibliotheken worden al gedownload als een afhankelijkheid van DynamoDBLocal, maar de bibliotheekbestanden moeten naar de juiste plaats worden gekopieerd. Voeg toe aan je build.gradle-bestand…

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

3. Stel sqlite4java.library.path in om native bibliotheken weer te geven

We moeten Gradle vertellen om copyNativeDepsuit te voeren om te testen en sqlite4java vertellen waar de bestanden te vinden zijn. Voeg toe aan je build.gradle-bestand…

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

Antwoord 4, autoriteit 23%

U kunt DynamoDB Local gebruiken als een Maven-testafhankelijkheid in uw testcode, zoals wordt getoond in deze aankondiging. U kunt over HTTP lopen:

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

U kunt ook in ingesloten modus uitvoeren:

import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded;
AmazonDynamoDB dynamodb = DynamoDBEmbedded.create();
dynamodb.listTables();

Antwoord 5, autoriteit 7%

Ik heb de bovenstaande antwoorden verpakt in twee JUnit-regelsdat vereist geen wijzigingen in het buildscript, aangezien de regels de native bibliotheekdingen afhandelen. Ik deed dit omdat ik ontdekte dat Idea de Gradle/Maven-oplossingen niet leuk vond, omdat het gewoon afliep en hoe dan ook zijn eigen ding deed.

Dit betekent dat de stappen zijn:

  • Verkrijg de AssortmentOfJUnitRules versie 1.5.32 of hoger afhankelijkheid
  • Verkrijg de directe DynamoDBLocal-afhankelijkheid
  • Voeg de LocalDynamoDbRule of HttpDynamoDbRule toe aan uw JUnit-test.

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>

Gradel:

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

Antwoord 6, autoriteit 4%

Probeer tempest-testing! Het levert een JUnit4-regel en een JUnit5-extensie. Het ondersteunt ook zowel AWS SDK v1 als SDK v2.

Tempest biedt een bibliotheek voor het testen van DynamoDB-clients
met behulp van DynamoDBLocal
. Het wordt geleverd met twee implementaties:

  • JVM:dit is de voorkeursoptie, met een DynamoDBProxyServerondersteund door sqlite4java,
    die beschikbaar is op de meeste platforms.
  • Docker:dit draait dynamodb-localin een Docker
    container.

Kenmerkenmatrix:

Functie tempest-testing-jvm tempest-testing-docker
Opstarttijd ~1s ~10s
Geheugengebruik Minder Meer
Afhankelijkheid sqlite4java native bibliotheek Docker

Antwoord 7

Voor het testen van eenheden op het werk gebruik ik Mockito en bespot dan gewoon de AmazonDynamoDBClient. bespot vervolgens de rendementen met wanneer. zoals het volgende:

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

Ik weet niet zeker of dat is wat je zoekt, maar zo doen we het.


Antwoord 8

Kortsteoplossing met fix voor sqlite4java.SQLiteException UnsatisfiedLinkErrorals het een java/kotlin-project is gebouwd met gradle (een gewijzigde $PATHis nietnodig).

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

Eenvoudig gebruik in testklassen (src/test):

private lateinit var db: AmazonDynamoDBLocal
@BeforeAll
fun runDb() { db = DynamoDBEmbedded.create() }
@AfterAll
fun shutdownDb() { db.shutdown() }

9

Er zijn een paar node.js wrappers voor DynamoDB Local. Dit maakt het mogelijk om eenvoudig eenheidstests uit te voeren die combineren met taaklopers zoals Slupp of Grunt. Probeer dynamodb-localhost ,
DynamoDB-local

Other episodes