Подтвердить что ты не робот

Простое локальное тестирование DynamoDB

Я использую DynamoDB local для модульного тестирования. Это неплохо, но имеет некоторые недостатки. В частности:

  • Вам нужно как-то запустить сервер до запуска ваших тестов.
  • Сервер не запускается и не останавливается перед каждым тестом, поэтому тесты становятся взаимозависимыми, если вы не добавите код для удаления всех таблиц и т.д. после каждого теста.
  • Все разработчики должны установить его

То, что я хочу сделать, это что-то вроде установки локального банка DynamoDB и других банок, от которых это зависит, в моем каталоге test/resources (я пишу Java). Затем перед каждым тестом я запускал его, работая с -inMemory, и после теста я бы остановил его. Таким образом, любой, кто вытаскивает репозиторий git, получает копию всего, что им нужно для запуска тестов, и каждый тест не зависит от других.

Я нашел способ сделать эту работу, но это уродливо, поэтому я ищу альтернативы. Решение, которое я имею, это поместить .zip файл локального материала DynamoDB в test/resources, а затем в метод @Before, я извлечу его в какой-нибудь временный каталог и запустим новый Java-процесс для его выполнения. Это работает, но это уродливо и имеет некоторые недостатки:

  • Каждому нужен исполняемый файл java на $PATH
  • Мне нужно распаковать zip на локальный диск. Использование локального диска часто бывает рискованным для тестирования, особенно с непрерывными сборками и т.д.
  • Мне нужно запустить процесс и дождаться его начала для каждого unit test, а затем убить этот процесс после каждого теста. Помимо медленного, потенциал для процессов с переходом кажется уродливым.

Кажется, должен быть более простой способ. В конце концов, DynamoDB Local - это просто код Java. Не могу ли я каким-то образом спросить JVM о том, чтобы развить себя и заглянуть в ресурсы для создания classpath? Или, что еще лучше, я не могу просто вызвать метод main DynamoDb Local из какого-то другого потока, так что все это происходит в одном процессе? Любые идеи?

PS: Я знаю об альтернаторе, но, похоже, имеет другие недостатки, поэтому я склонен придерживаться решения, поддерживаемого Amazon, если я могу заставить его работать.

4b9b3361

Ответ 1

Чтобы использовать DynamoDBLocal, вам необходимо выполнить следующие шаги.

  • Получить Direct DynamoDBLocal Dependency
  • Получить родные зависимости SQLite4Java
  • Установите sqlite4java.library.path для отображения собственных библиотек

1. Получить Direct DynamoDBLocal Dependency

Этот простой. Вам нужен этот репозиторий, как описано в разделе Форумы AWS.

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

Если вы не добавляете эти зависимости, ваши тесты не будут выполнены с внутренней внутренней ошибкой.

Сначала добавьте эти зависимости:

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

Ответ 2

Вы можете использовать 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();

Ответ 3

Это повторение ответа bhdrkn для пользователей Gradle (его основано на Maven). Это все те же три шага:

  1. Получить прямую зависимость от DynamoDBLocal
  2. Получить собственные зависимости SQLite4Java
  3. Установите sqlite4java.library.path для отображения родных библиотек

1. Получите прямую зависимость от DynamoDBLocal

Добавьте в раздел зависимостей файл build.gradle...

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

Ответ 4

Я завернул ответы выше в два правила JUnit, которые не требуют изменений в скрипте сборки, поскольку правила обрабатывают материал родной библиотеки. Я сделал это, когда обнаружил, что Idea не понравилась решения Gradle/Maven, поскольку она просто ушла и сделала свое дело anyhoos.

Это означает, что следующие шаги:

  • Получить версию 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());
  }
}

Ответ 5

Для модульного тестирования на работе я использую Mockito, а затем просто издеваюсь над AmazonDynamoDBClient. затем выкрикивайте возвращаемые данные, используя когда. например:

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

не уверен, что это то, что вы ищете, но как мы это делаем.

Ответ 6

Есть несколько оберток node.js для DynamoDB Local. Они позволяют легко выполнять модульные тесты, сочетающиеся с бегунами задач, такими как gulp или grunt. Попробуйте dynamodb-localhost, dynamodb-local

Ответ 8

Я обнаружил, что репозиторий amazon как индексный файл, поэтому, похоже, не работает таким образом, чтобы он приводил его так:

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

Единственный способ получить зависимости для загрузки - загрузить DynamoDbLocal в качестве банки и привести его в мой скрипт сборки следующим образом:

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

Конечно, это означает, что все зависимости SQLite и Jetty нужно вводить вручную - я все еще пытаюсь понять это правильно. Если кто-то знает о надежном репо для DynamoDbLocal, я бы очень хотел узнать.

Ответ 9

В августе 2018 года Amazon анонсировала новый образ 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:

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, а о всей услуге, а не о единственном компоненте.

После того, как вы запустили контейнер, вы можете создать для него клиент, указывающий на него:

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 и позволить разработчикам запускать контейнер, насколько это удобно для них.

Сервер не запускается и не останавливается перед каждым тестом, поэтому тесты становятся взаимозависимыми, если вы не добавите код для удаления всех таблиц и т.д. После каждого теста

Это правда, но... Вы не должны начинать и останавливать такие тяжеловесные вещи до и после каждого теста. Тесты БД почти всегда взаимозависимы, и это нормально для них. Просто используйте уникальные значения для каждого тестового примера (например, установите хэш-ключ элемента в идентификатор билета/конкретный идентификатор тестового случая, над которым вы работаете). Что касается данных о семени, я бы рекомендовал также переместить его из инструмента построения и тестового кода. Либо создайте собственное изображение со всеми необходимыми данными, либо используйте AWS CLI для создания таблиц и вставки данных. Следуйте принципам единой ответственности и принципам зависимости: ваш тестовый код не должен делать ничего, кроме тестов. Вся окружающая среда (таблицы и данные в этом случае должны быть предоставлены для них). Создание таблицы в тесте неверно, потому что в реальной жизни эта таблица уже существует (конечно, если вы не тестируете метод, который фактически создает таблицу).

Все разработчики должны установить его

Докер должен быть обязательным для каждого разработчика в 2018 году, так что это не проблема.


И если вы используете JUnit 5, может быть хорошей идеей использовать расширение DynamoDB Local, которое будет вводить клиента в ваши тесты (да, я делаю саморекламу):

  1. Добавьте репозиторий 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()
    }
    
  2. Добавьте зависимость от 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. Используйте расширение в своих тестах:

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