Фиктивные данные и стратегии тестирования модулей в модульном стеке приложений

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

У нас есть стек приложений с несколькими модулями в зависимости от другого с каждым содержащим объекты. Каждый модуль имеет свои собственные тесты и нуждается в фиктивных данных для работы.

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

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

Есть ли лучший выход из этого или все решения компрометируют?


Подробнее

Наш стек выглядит примерно так:

Один модуль:

src/main/java --> gets jared (.../entities/*.java contains the entities)
src/main/resources --> gets jared
src/test/java --> contains dummy object setup, will NOT get jared
src/test/resources --> not jared

Мы используем Maven для обработки зависимостей.

Пример модуля

:

  • Модуль A содержит несколько фиктивных объектов
  • Модуль B нуждается в собственных объектах И так же, как и модуль A

Вариант a)

Модуль тестирования T может содержать все фиктивные объекты и предоставлять их в тестовой области (так что загруженные зависимости не получают jared) ко всем тестам во всех модулях. Будет ли это работать? Значение: если я загружаю T в A и запускаю установку на A, он НЕ содержит ссылки, введенные T, особенно не B? Затем, однако, A будет знать о B datamodel.

Вариант b)

Модуль A предоставляет фиктивные объекты где-то в src/main/java../entities/dummy, позволяя B получать их, а A не знает о B фиктивных данных

Вариант c)

Каждый модуль содержит внешние ресурсы, которые представляют собой сериализованные фиктивные объекты. Они могут быть десериализованы тестовой средой, которая им нужна, поскольку она имеет зависимость от модуля, к которому они принадлежат. Это потребует, чтобы каждый модуль создавал и сериализовал свои фиктивные объекты, и как это сделать? Если с другим unit test он вводит зависимости между модульными тестами, которые никогда не должны происходить или с script, будет сложно отлаживать и не гибко.

Вариант d)

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

Что мы не хотим

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


Мои мысли идут в правильном направлении? Какая наилучшая практика для тестирования тестов, требующих большого количества данных? У нас будет несколько взаимозависимых модулей, для которых потребуются объекты, заполненные некоторыми данными из нескольких других модулей.


ИЗМЕНИТЬ

Дополнительная информация о том, как мы это делаем сейчас, в ответ на второй ответ:

Итак, для простоты мы имеем три модуля: Person, Product, Order. Person проверит некоторые методы менеджера, используя объект MockPerson:

(in person/src/test/java:)

public class MockPerson {

    public Person mockPerson(parameters...) {
        return mockedPerson;
    }
}

public class TestPerson() {
    @Inject
    private MockPerson mockPerson;
    public testCreate() {
        Person person = mockPerson.mockPerson(...);
        // Asserts...
    }
}

Класс MockPerson не будет упакован.

То же самое относится к тестированию продукта:

(в файле /src/test/java:)

public class MockProduct() { ... }
public class TestProduct {
    @Inject
    private MockProduct mockProduct;
    // ...
}

MockProduct необходим, но не будет упакован.

Теперь для тестов заказа потребуются MockPerson и MockProduct, поэтому теперь нам нужно создать как теги, так и MockOrder для тестирования Order.

(в порядке /src/test/java:)

Это дубликаты, и их нужно будет менять каждый раз, когда Person или Product изменяет

public class MockProduct() { ... }
public class MockPerson() { ... }

Это единственный класс, который должен быть здесь:

public class MockOrder() { ... }

public class TestOrder() {
    @Inject
    private order.MockPerson mockPerson;
    @Inject
    private order.MockProduct mockProduct;
    @Inject
    private order.MockOrder mockOrder;
    public testCreate() {

        Order order = mockOrder.mockOrder(mockPerson.mockPerson(), mockProduct.mockProduct());
        // Asserts...
    }
}

Проблема заключается в том, что теперь мы должны обновлять person.MockPerson и order.MockPerson при изменении Person.

Разве не лучше просто публиковать Mocks с банкой, чтобы каждый другой тест, который имеет зависимость в любом случае, может просто вызвать Mock.mock и получить красиво настроенный объект? Или это темная сторона - простой способ?

Ответ 1

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

То, что я выполнил (и AFAIKT в Java - это лучшая практика), пытается использовать шаблон Data Data Builder, описанный Nat Pryce в его публикация тестовых данных.

Если вы считаете, что это несколько актуально, проверьте:

Ответ 2

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

  • Настройка (статическая) тестовая база данных;
  • Каждый тест имеет собственные настройки данных, которые создают (динамические) тестовые данные до запуска модульных тестов;
  • Используйте фиктивный или макет объекта. Все модули знают все фиктивные объекты, поэтому дубликатов нет;
  • Уменьшить область действия unit test;

Первый вариант довольно прямолинейный и имеет множество недостатков, кто-то должен воспроизводить его один раз в то время, когда модульные тесты "испортили", если есть изменения в модуле данных, кто-то должен внести соответствующие изменения в тестовые данные, много накладных расходов на обслуживание. Не сказать, что генерация этих данных, с одной стороны, может быть сложной. См. Также второй вариант.

Второй вариант, вы пишете свой тестовый код, который перед тестированием вызывает некоторые из ваших "основных" бизнес-методов, которые создают вашу сущность. В идеале ваш тестовый код должен быть независимым от производственного кода, но в этом случае вы получите дубликат кода, который вы должны поддерживать дважды. Иногда полезно разделить свой производственный бизнес-метод, чтобы иметь точку входа для вашего unit test (я делаю такие методы приватными и использую Reflection для их вызова, также требуется некоторое замечание о методе, рефакторинг теперь немного сложный). Главный недостаток, заключающийся в том, что, если вы должны изменить свои "основные" бизнес-методы, это внезапно повлияет на все ваши unit test, и вы не можете проверить. Таким образом, разработчики должны знать об этом, а не делать частичные обязательства для "основных" бизнес-методов, если они не работают. Кроме того, при любых изменениях в этой области вы должны держать в уме "как это повлияет на мой unit test". Иногда также невозможно воспроизвести все требуемые данные динамически (обычно это связано с API сторонних разработчиков, например, вы вызываете другое приложение с его собственной БД, из которой вы должны использовать некоторые ключи. Эти ключи (с связанные данные) создается вручную через стороннее приложение. В таком случае эти данные и только эти данные должны быть созданы статически. Например, созданные 10000 ключей, начиная с 300000.

Третий вариант должен быть хорошим. Варианты a) и d) звучат для меня довольно хорошо. Для вашего фиктивного объекта вы можете использовать фреймворк или вы не можете его использовать. Mock Framework здесь только для вас. Я не вижу проблемы, что все ваше подразделение знает все ваши сущности.

Четвертый параметр означает, что вы переопределяете, что такое "единица" в unit test. Когда у вас есть пара модулей с взаимозависимостью, может быть трудно протестировать каждый модуль отдельно. Этот подход говорит, что первоначально мы тестировали интеграционный тест, а не unit test. Итак, мы разделили наши методы, извлекли небольшие "единицы работ", которые получают все его взаимозависимости в отношении других модулей в качестве параметров. Эти параметры могут (надеюсь) легко высмеиваться. Основным недостатком этого подхода является то, что вы не проверяете весь свой код, а только так называете "координационные центры". Вы должны сделать интеграционный тест отдельно (обычно командой QA).

Ответ 3

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

Модуль Тестирование модуля, который зависит от других модулей и, из-за этого, от тестовых данных других модулей не является реальным unit test!

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

Если вы представляете себе пирамиду, тогда база будет вашим модульным тестом, выше того, что у вас есть функциональные тесты, а наверху у вас есть некоторые сценарийные тесты (или как их называют Google, малые, средние и большие тесты).

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

Это означает, что ваши тестовые данные модуля не используются всеми вашими тестами, но только несколькими группами, сгруппированными вместе.

Шаблон Builder, упомянутый cwash, определенно поможет в ваших функциональных тестах. Мы используем .NET Builder, который настроен на создание полного дерева объектов и генерирует значения по умолчанию для каждого свойства, поэтому, когда мы сохраняем его в базе данных, все необходимые данные присутствуют.