Фиктивные данные и стратегии модульного тестирования в модульном стеке приложений - PullRequest
10 голосов
/ 12 января 2012

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

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

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

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

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


Подробнее

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

Один модуль:

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)

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

Опция b)

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

Опция c)

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

Опция d)

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

Что мы не делаемwant

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


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


EDIT

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

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

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 не будет упакован.

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

product / 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 с помощью jar, чтобы любой другой тест, имеющий зависимость, мог просто вызвать Mock.mock и получить красивый объект установки? Или это темная сторона - легкий путь?

Ответы [ 3 ]

3 голосов
/ 20 января 2012

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

  1. Настройка (статической) базы данных теста;
  2. Каждый тест имеет свои собственные данные настройки, которые создают (динамические) данные теста до запускамодульные тесты;
  3. Используйте фиктивный или фиктивный объект.Все модули знают все фиктивные объекты, таким образом, нет дубликатов;
  4. Уменьшите объем модульного теста;

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

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

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

Четвертый вариант означает, что вы переопределяете, что такое «юнит» в вашем юнит-тесте.Если у вас есть пара модулей с взаимозависимостью, то может быть трудно протестировать каждый модуль изолированно.Этот подход говорит о том, что первоначально мы тестировали интеграционный тест , а не модульный тест .Итак, мы разделяем наши методы, извлекаем небольшие «единицы работ», которые получают все свои взаимозависимости с другими модулями в качестве параметров.Эти параметры могут быть (надеюсь) легко смоделированы.Главный недостаток этого подхода в том, что вы тестируете не весь код, а лишь так называемые «фокусные точки».Вам нужно сделать интеграционный тест отдельно (обычно командой QA).

3 голосов
/ 14 января 2012

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

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

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

1 голос
/ 20 января 2012

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

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

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

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

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

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

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

...