Как создать масштабируемые тесты интеграции без побочных эффектов?

14

В моем текущем проекте мне трудно найти хорошее решение для создания масштабируемых интеграционных тестов, которые не имеют побочных эффектов. Небольшое уточнение о свободном от побочных эффектов свойстве: в основном это касается базы данных; после завершения тестов в базе данных не должно быть никаких изменений (состояние должно быть сохранено). Возможно, масштабируемость и сохранение состояния не объединяются, но я действительно хочу стремиться к лучшему решению.

Вот типичный интеграционный тест (эти тесты касаются уровня базы данных):

public class OrderTests {

    List<Order> ordersToDelete = new ArrayList<Order>(); 

    public testOrderCreation() {
        Order order = new Order();
        assertTrue(order.save());
        orderToDelete.add(order);
    }

    public testOrderComparison() {
        Order order = new Order();
        Order order2 = new Order();
        assertFalse(order.isEqual(order2);
        orderToDelete.add(order);
        orderToDelete.add(order2);
    }
    // More tests

    public teardown() {
         for(Order order : ordersToDelete)
             order.delete();
    }
}

Как можно себе представить, этот подход дает очень медленные тесты. А применительно ко всем интеграционным тестам требуется около 5 секунд, чтобы протестировать только небольшую часть системы. Я могу представить, что это число будет расти, когда охват увеличится.

Каков был бы другой подход для написания таких тестов? Одна альтернатива, о которой я могу подумать, - это иметь вид глобальных переменных (внутри класса), и все методы тестирования разделяют эту переменную. В результате только несколько заказов создаются и удаляются; в результате чего более быстрые тесты. Тем не менее, я думаю, что это представляет большую проблему; тесты больше не изолированы, и их становится все труднее понимать и анализировать.

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

Guven
источник

Ответы:

6

Рассмотрите возможность использования Hypersonic или другой базы данных в памяти для модульного тестирования. Мало того, что тесты будут выполняться быстрее, но побочные эффекты не имеют значения. (Откат транзакций после каждого теста также позволяет запускать много тестов на одном экземпляре)

Это также заставит вас создавать макеты данных, что хорошо для IMO, так как это означает, что то, что происходит в производственной базе данных, не может необъяснимым образом начать проваливать ваши тесты, И это дает вам отправную точку для «чистой установки базы данных». будет выглядеть так, что помогает, если вам нужно внезапно развернуть новый экземпляр приложения без подключения к существующему производственному сайту.

Да, я сам использую этот метод, и да, это был PITA, чтобы установить ПЕРВЫЙ раз, но это более чем окупило себя за время существования проекта, который я собираюсь завершить. Есть также ряд инструментов, которые помогают с макетами данных.

SplinterReality
источник
Звучит как отличная идея. Мне особенно нравится его полная изоляция; начиная с новой установки базы данных. Кажется, что это потребует некоторых усилий, но после настройки это будет очень полезно. Благодарю.
Гювен
3

Это вечная проблема, с которой сталкиваются все при написании интеграционных тестов.

Идеальное решение, особенно если вы тестируете на производстве, - это открытие транзакции при настройке и откат ее до завершения. Я думаю, что это должно удовлетворить ваши потребности.

Там, где это невозможно, например, когда вы тестируете приложение на уровне клиента, другое решение состоит в том, чтобы использовать БД на виртуальной машине, сделать моментальный снимок при настройке и вернуться к нему в состоянии разрыва (это не так. займет столько времени, сколько вы можете ожидать).

прецизионный самописец
источник
Я не могу поверить, что я не думал об откате транзакции. Я буду использовать эту идею как быстрое решение проблемы, но в конечном итоге БД в памяти звучит очень многообещающе.
Гювен
3

Интеграционные тесты всегда должны выполняться в соответствии с настройками производства. В вашем случае это означает, что вы должны иметь одну и ту же базу данных и сервер приложений. Конечно, ради производительности вы можете использовать БД в памяти.

Однако никогда не следует расширять объем транзакций. Некоторые люди предложили взять контроль над транзакцией и откатить ее после тестов. Когда вы сделаете это, все юридические лица (если вы используете JPA) будут оставаться прикреплены к контексту персистенции на протяжении выполнения теста. Это может привести к некоторым очень неприятным ошибкам, которые очень трудно найти .

Вместо этого вам следует очищать базу данных вручную после каждого теста с помощью библиотеки, такой как JPAUnit или аналогичной этому подходу (очистить все таблицы с использованием JDBC).

Что касается проблем с производительностью, вам не следует выполнять интеграционные тесты для каждой сборки. Пусть ваш сервер непрерывной интеграции сделает это. Если вы используете Maven, вам может понравиться подключаемый модуль отказоустойчивости, который позволяет разделить ваши тесты на модульные и интеграционные тесты.

Кроме того, вы не должны ничего издеваться. Помните, что вы проводите интеграционное тестирование, т.е. тестирование поведения в среде выполнения во время выполнения.

BenR
источник
Хороший ответ. Одно замечание: может быть допустимо издеваться над некоторыми внешними зависимостями (например, отправка электронной почты). Но вы, скорее всего, будете издеваться, настроив полный фиктивный сервис / сервер, а не издеваться над ним в коде Java.
Слеське
Слеське, я с тобой согласен. Особенно, когда вы используете JPA, EJB, JMS или реализуете по другой спецификации. Вы можете поменять сервер приложений, поставщик сохраняемости или базу данных. Например, вы можете использовать встроенные Glassfish и HSQLDB для простой настройки и повышения скорости (вы, конечно, можете выбрать другую сертифицированную реализацию).
BenR
2

По масштабируемости

Я уже сталкивался с этой проблемой несколько раз раньше, когда интеграционные тесты выполнялись слишком долго и не практично для одного разработчика постоянно работать в тесной обратной связи с изменениями. Некоторые стратегии, чтобы справиться с этим:

  • Пишите меньше интеграционных тестов - если у вас есть хорошее покрытие модульными тестами в системе, интеграционные тесты должны быть более сфокусированы на (гм) проблемах интеграции, на том, как различные компоненты работают вместе. Они больше похожи на тесты дыма, которые просто пытаются увидеть, работает ли система в целом. Таким образом, в идеале, ваши модульные тесты охватывают большую часть функциональных частей приложения, а интеграционные тесты просто проверяют, как эти части взаимодействуют друг с другом. Вам не нужен обширный охват логических путей здесь, только некоторые критические пути через систему.
  • Одновременно запускайте только подмножество тестов - как один разработчик, работающий над некоторыми функциями, вы обычно понимаете, какие тесты необходимы для охвата этих функций. Запускайте только те тесты, которые имеют смысл охватить ваши изменения. Фреймворки, такие как JUnit, позволяют группировать приборы в категории . Создайте группы, которые обеспечивают лучший охват для каждой функции, которая у вас есть. Конечно, вы все равно хотите запустить все интеграционные тесты в какой-то момент, может быть, прежде чем переходить в систему контроля версий и, конечно, на сервер непрерывной интеграции.
  • Оптимизация системы. Интеграционные тесты в сочетании с некоторыми тестами производительности могут дать вам необходимые данные для настройки медленных частей системы, чтобы позже тесты выполнялись быстрее. Это может помочь обнаружить необходимые индексы в базе данных, болтливые интерфейсы между подсистемами или другие узкие места производительности, которые требуют устранения.
  • Запускайте ваши тесты параллельно. Если у вас есть хорошая группа ортогональных тестов, вы можете попробовать запустить их параллельно . Будьте внимательны к ортогональному требованию, которое я упомянул, хотя вы не хотите, чтобы ваши тесты наступали друг другу на ноги.

Попробуйте объединить эти методы для большего эффекта.

Jordão
источник
1
Действительно хорошие рекомендации; Написание меньшего количества интеграционных тестов, безусловно, лучший совет. Кроме того, запуск их параллельно был бы очень хорошей альтернативой; идеальный «тест», чтобы проверить, правильно ли я получил свои тесты с точки зрения изоляции.
Гювен
2

В целях тестирования мы использовали развертывание базы данных SQLite на основе файлов (просто скопируйте ресурс). Это было сделано для того, чтобы мы могли также проверить миграцию схемы. Насколько мне известно, изменения схемы не являются транзакционными, поэтому не будут откатываться после отмены транзакции. Также отсутствие необходимости полагаться на поддержку транзакций для настройки теста позволяет правильно проверить поведение транзакций из вашего приложения.

Карло Куйп
источник