Как избежать множественных утверждений в тесте JUnit?

У меня есть DTO, который я заполняю из объекта запроса, а объект запроса имеет много полей. Я хочу написать тест, чтобы проверить, помещает ли метод populateDTO() значения в нужные места или нет. Если я буду следовать правилу одного утверждения за тест, мне придется написать большое количество тестов, чтобы проверить каждое поле. Другим подходом было бы написать несколько утверждений в одном тесте. Действительно ли рекомендуется следовать одному утверждению в правиле теста или мы можем расслабиться в этих случаях. Как мне подойти к этой проблеме?

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

    Действительно ли рекомендуется иметь только одно утверждение на единичный тест ? Да, есть люди, которые делают эту рекомендацию. Правильно ли они? Я так не думаю. Мне трудно поверить, что такие люди на самом деле давно работали над реальным кодом.

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

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

    Единственный безопасный способ проверить долгоживущий код – проверить, что мутаторы не имеют неожиданных побочных эффектов. Но как вы можете протестировать их? У большинства classов есть некоторые инварианты : вещи, которые никакой мутатор не может изменить. Например, метод size контейнера никогда не будет возвращать отрицательное значение. Каждый инвариант, по сути, является пост-условием для каждого мутатора (а также конструктора). Каждый мутатор также обычно имеет набор инвариантов, которые описывают, какой вид, если изменения его не производят. Например, метод sort не изменяет длину контейнера. Инварианты classа и мутанта являются, по сути, пост-условиями для вызова мутатора. Добавление утверждений для них является единственным способом проверки неожиданных побочных эффектов.

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

    У вас может быть параметризованный тест, где 1-й параметр является именем свойства, а второй – ожидаемым.

    Это правило распространяется на то, чтобы быть в цикле? Учти это

     Collection expectedValues = // populate expected values populateDTO(); for(DTO dto : myDtoContainer) assert_equal(dto, expectedValues.get(someIndexRelatedToDto)) 

    Теперь я не настолько силен в точном синтаксисе, но это только понятие, на которое я смотрю.

    EDIT: после комментариев …

    Ответ … Нет!

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

    Таким образом, вы можете сделать это одним из двух способов:

    1. Один метод, менее шаблонный код.
    2. Многие методы, лучшие отчеты о пробном запуске

    Это зависит от вас, у обоих есть взлеты и падения. 3. Элемент списка

    [caveat: Я очень «несчастлив» в Java / JUnit, поэтому остерегайтесь ошибок в деталях ниже]

    Есть несколько способов сделать это:

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

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

    3) Внедрите равные для объекта, которые проверяют каждое свойство и реализуют toString, чтобы вы, по крайней мере, могли вручную проверить результат утверждения, чтобы узнать, какая часть неверна.

    4) Для каждого сценария, в котором создается DTO, создайте отдельный тестовый прибор, который генерирует DTO и инициализирует ожидаемые свойства в методе setUp. Создайте отдельный тест для тестирования каждого из свойств. Это также приводит к множеству тестов, но они, по крайней мере, будут только однострочными. Пример в псевдокоде:

     public class WithDtoGeneratedFromXxx : TestFixture { DTO dto = null; public void setUp() { dto = GenerateDtoFromXxx(); expectedProp1 = ""; ... } void testProp1IsGeneratedCorrectly() { assertEqual(expectedProp1, dto.prop1); } ... } 

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

     abstract class AbstractDtoTest : TestFixture { DTO dto; SomeType expectedProp1; abstract DTO createDto(); abstract SomeType getExpectedProp1(); void setUp() { dto = createDto(); ... } void testProp1IsGeneratedCorrectly() { assertEqual(getExpectedProp1(), dto.prop1); } ... } class WithDtoGeneratedFromXxx : AbstractDtoTest { DTO createDto() { return GenerateDtoFromXxx(); } abstract SomeType getExpectedProp1() { return new SomeType(); } ... } 

    эта конструкция поможет вам иметь 1 большое утверждение (с небольшими утверждениями внутри)

     import static org.junit.jupiter.api.Assertions.assertAll; assertAll( () -> assertThat(actual1, is(expected)), () -> assertThat(actual2, is(expected)) ); 

    Или вы можете сделать некоторое обходное решение.

     import junit.framework.Assert; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; public class NewEmptyJUnitTest { public NewEmptyJUnitTest() { } @BeforeClass public static void setUpClass() throws Exception { } @AfterClass public static void tearDownClass() throws Exception { } @Before public void setUp() { } @After public void tearDown() { } @Test public void checkMultipleValues() { String errMessages = new String(); try{ this.checkProperty1("someActualResult", "someExpectedResult"); } catch (Exception e){ errMessages += e.getMessage(); } try{ this.checkProperty2("someActualResult", "someExpectedResult"); } catch (Exception e){ errMessages += e.getMessage(); } try{ this.checkProperty3("someActualResult", "someExpectedResult"); } catch (Exception e){ errMessages += e.getMessage(); } Assert.assertTrue(errMessages, errMessages.isEmpty()); } private boolean checkProperty1(String propertyValue, String expectedvalue) throws Exception{ if(propertyValue == expectedvalue){ return true; }else { throw new Exception("Property1 has value: " + propertyValue + ", expected: " + expectedvalue); } } private boolean checkProperty2(String propertyValue, String expectedvalue) throws Exception{ if(propertyValue == expectedvalue){ return true; }else { throw new Exception("Property2 has value: " + propertyValue + ", expected: " + expectedvalue); } } private boolean checkProperty3(String propertyValue, String expectedvalue) throws Exception{ if(propertyValue == expectedvalue){ return true; }else { throw new Exception("Property3 has value: " + propertyValue + ", expected: " + expectedvalue); } } } 

    Может быть, это не лучший подход, и если он чрезмерен, чем может запутать … но это возможность.

    Interesting Posts

    Список внешних ключей и таблиц, которые они ссылаются

    Не удалось resize массива в переменной Inspector в Unity?

    Пользовательский раздел app.config с простым списком элементов «добавить»

    Управление объемом приложения: по идентификатору процесса

    Выберите несколько изображений из фотоальбома на Android с помощью намерений

    Что делать, когда я замечаю симптомы механических отказов принтера?

    Как удалить вредоносные программы-шпионы, вредоносное ПО, рекламное ПО, вирусы, трояны или руткиты с моего ПК?

    Прикрепите исходный код Java

    Аппаратное ускорение VT-x / AMD-V недоступно в вашей системе

    Разрешение на копирование в VBS

    Как удалить .htaccess защиту паролем из подкаталога

    Порядок Mysql по определенным значениям идентификатора

    Могу ли я прочитать hash-часть URL-адреса моего серверного приложения (PHP, Ruby, Python и т. Д.)?

    EntityFramework – содержит запрос составного ключа

    Где предложение IN в LINQ

    Давайте будем гением компьютера.