ДжазТим — надежный технологический партнер

Agile разработка ПО на Java

Black box testing силами программистов

Введение

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

Black box тестирование

При постановке задачи основным требованием являлось сокрытие реализации ядра системы и предоставление простого API для создания тестов. Данный подход соответствует определению концепции Black box Testing (ссылка на англ. ресурс).

Особенности существующей системы в контексте тестирования

Тестируемое приложение построено в рамках концепции Model Driven Development с характерной особенностью: бизнес-сущности полностью отвечают за поведение на всех слоях. Транспортный слой, представление, БД, бизнес-логика — всё это находится и обрабатывается в рамках конкретных бизнес-сущностей.

Алгоритм написания тестов до разработки модуля Black box тестирования

Алгоритм написания тестов отличался в зависимости от типа самих тестов. Ниже представлены алгоритмы написания модульных и интеграционных тестов. UI тесты не описываются, т.к. не являются предметом данной статьи.

Unit-тесты

Алгоритм написания unit-тестов

Алгоритм написания unit-тестов

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

Интеграционные тесты

Написание и выполнение интеграционных тестов требует ещё больших усилий, чем создание Unit-тестов. Для создания и выполнения интеграционных тестов в первую очередь необходимо подготовить окружение:

  1. Установить MySQL и применить скрипты инициализации.
  2. Установить и настроить сервер приложений.
  3. Создать необходимые учётные записи в приложении.

Описанные ниже примеры касаются конкретного приложения, для которого создавались black box тесты, и приведены для демонстрации возможных проблем.

На первом же этапе мы сталкиваемся с проблемой: нет возможности заменить локально установленный сервер MySQL на базу данных in-memory (к примеру, H2), т. к. приложение использует определённый синтаксис и особенности конкретной версии MySQL, которые в полной мере не поддерживаются ни одной из баз данных in-memory (например, хранимые процедуры).

Также для написания интеграционных тестов необходимо выполнить сериализацию бизнес-объектов в набор http query params и при получении ответа от сервера — парсить html для десериализации полученного объекта и приведения его к необходимому типу.

Вкратце получается, что интеграционное тестирование требует приложения существенных усилий, сопряжённых с мануальным тестированием, что замедляет процесс интеграционного тестирования.

Особенности реализации модуля тестирования

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

    1. Необходимость использования локального MySQL сервера.
    2. Сериализация объекта в набор HTTP url encoded параметров.
    3. Десериализация объекта из HTML в Java объект соответствующего типа.
    4. Инициализация приложения и минимальная подготовка для проведения тестов.

Test containers

Для устранения первой проблемы (необходимость использования локального MySQL сервера) было решено использовать Test Containers (ссылка на англ. ресурс). Базовый класс, от которого наследуются тестовые классы, во время инициализации поднимает тестовый контейнер с конкретной версией MySQL и подготавливает его к дальнейшей работе. Для этого требуется наличие установленного Docker.

Данное решение позволяет разработчику избежать необходимости устанавливать и настраивать MySQL локально и является альтернативой in-memory базам данных.

Сериализация объекта в набор HTTP параметров

При выполнении запросов к серверу необходимо провести сериализацию бизнес-объекта в набор http url-encoded параметров. Для этого был создан утилитный класс, который сериализует исходный объект в набор параметров, описанных выше.

Десериализация объекта из HTML в Java объект соответствующего типа

Согласно архитектуре проекта, сервер возвращает готовое HTML представление объекта, сгенерированное на основании атрибутов объекта и набора шаблонов. Для анализа ответа требовалось представить его в виде объекта.

Первоначальная реализация содержала в себе парсинг HTML кода, полученного в качестве ответа. Парсились теги input и проверялось соответствие атрибута name формату, используемому приложением.

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

Инициализация приложения и минимальная подготовка для проведения тестов

Решив предыдущие задачи, мы реализовали возможность написания blackbox тестов для системы с предварительно «набитыми» тестовыми данными. Для того чтобы полностью избавить разработчика от ручной подготовки данных — реализовали собственно автоматизированную инициализацию приложения и наполнение базовым набором тестовых данных. Базовый класс, подняв тестовый контейнер, инициализирует приложение и создаёт учётные записи администратора и тестового пользователя. Также предоставляется возможность сконфигурировать параметры тестового аккаунта.

Реализация идеи Black box тестирования

Ниже приведён пример теста, в котором тестируется комплексная бизнес-логика — генерация заказа на поставку позиций заказа, отсутствующих на складе:

 

public class TransactionTest extends BaseBlackBoxTest {
…
    @Before
    public void setup() {
        …
        blackBoxService = new BlackBoxService(getCurrentAuthorizedUserSession());
…
    }
…
    @Test
    public void returnsPurchaseOrdersForNotAllocatedLineItemsOnSalesOrder() throws Exception {
        Transaction baseOrder = transactionBuilder. … .build();
Transaction expectedPurchaseOrder = transactionBuilder. … .build();
        
        Transaction order = blackBoxService.invokeMethod(baseOrder, "allocate");
Transaction purchaseOrder = blackBoxService.invokeMethod(order, "createPurchaseOrder");
 …
        assertTrue("The Purchase Order must have all not allocated line-items from the Order.", compareTransaction(purchaseOrder, expectedPurchaseOrder));
…
     }
}

Ряд реализованных билдеров позволяет быстро создать объекты тестирования, опустив инициализацию базовых компонентов, указывая лишь базовый набор данных, непосредственно используемых в тестировании.

Общий сервис тестирования эмитирует базовую логику вызова глобальных методов (прим.: особенностью архитектуры проекта является отсутствие набора эндпоинтов в общем понимании). Система имеет единый эндпоинт, принимающий объект и параметры ‘глобального метода’, которому передаётся в управление поток запроса.

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