JUnit избегать дублирующих утверждений - PullRequest
0 голосов
/ 20 марта 2019

Я пишу простые тестовые случаи для преобразования Entity в DTO и наоборот. Вопрос больше в дизайне. Допустимо ли оставлять дубликаты, как в коде ниже, или лучше создать внешний метод для этого утверждения? Так как я новичок на Java, может кто-нибудь дать мне подсказку о хм? какой-нибудь общий метод? Я не хочу использовать и наследование, или любую другую абстракцию для такого простого Entity и его DTO, потому что кода будет намного больше, чем несколько повторяющихся строк кода. Вот как это выглядит сейчас:

@Test
void addressToAddressDTO() {
    Address address = getAddress();

    AddressDTO addressDTO = addressMapper.addressToAddressDTO(address);

    assertAll("Check if values were properly bound",
            () -> {
                assertEquals(address.getCity(), addressDTO.getCity());
                assertEquals(address.getUserDetails().getFirstName(), addressDTO.getUserDetails().getFirstName());
                assertEquals(address.getUserDetails().getUser().getUsername(), addressDTO.getUserDetails().getUser().getUsername());
                assertEquals(address.getUserDetails().getContact().getEmail(), addressDTO.getUserDetails().getContact().getEmail());
                assertEquals(address.getUserDetails().getProfileImage().getImageUrl(), addressDTO.getUserDetails().getProfileImage().getImageUrl());
            });
}

@Test
void addressDTOtoAddress() {
    AddressDTO addressDTO = getAddressDTO();

    Address address = addressMapper.addressDTOtoAddress(addressDTO);

    assertAll("Check if values were properly bound",
            () -> {
                assertEquals(addressDTO.getCity(), address.getCity());
                assertEquals(addressDTO.getUserDetails().getFirstName(), address.getUserDetails().getFirstName());
                assertEquals(addressDTO.getUserDetails().getUser().getUsername(), address.getUserDetails().getUser().getUsername());
                assertEquals(addressDTO.getUserDetails().getContact().getEmail(), address.getUserDetails().getContact().getEmail());
                assertEquals(addressDTO.getUserDetails().getProfileImage().getImageUrl(), address.getUserDetails().getProfileImage().getImageUrl());
            });
}

Моя идея состояла в том, чтобы создать что-то более общее, например:

private<T, S> void assertObject(T expected, S actual) {
        assertAll("Check if values were properly bound",
                () -> {
                    assertEquals(expected.getCity(), actual.getCity());
                    assertEquals(expected.getUserDetails().getFirstName(), actual.getUserDetails().getFirstName());
                    assertEquals(expected.getUserDetails().getUser().getUsername(), actual.getUserDetails().getUser().getUsername());
                    assertEquals(expected.getUserDetails().getContact().getEmail(), actual.getUserDetails().getContact().getEmail());
                    assertEquals(expected.getUserDetails().getProfileImage().getImageUrl(), actual.getUserDetails().getProfileImage().getImageUrl());
                });
    }

но так как даже они являются одинаковыми объектами, у них нет ничего общего. Как добиться чего-то взаимозаменяемого, что Address и AddressDTO могут быть как актуальными, так и ожидаемыми?

EDIT

Согласно ответу Аарона Дигуллы, я внес некоторые изменения, надеюсь, это поможет кому-то с такими же сомнениями. Если кто-то знает какой-либо другой вариант, пожалуйста, оставьте сообщение в разделе комментариев.

@Test
void addressToAddressDTO() {
    Address expected = getAddress();

    AddressDTO actual = addressMapper.addressToAddressDTO(expected);

    assertEquals(
            mergeAddressDataToString(expected),
            actual.getCity() + "," +
                    actual.getUserDetails().getFirstName() + "," +
                    actual.getUserDetails().getUser().getUsername() + "," +
                    actual.getUserDetails().getContact().getEmail() + "," +
                    actual.getUserDetails().getProfileImage().getImageUrl()

    );
}

@Test
void addressDTOtoAddress() {
    AddressDTO expected = getAddressDTO();

    Address actual = addressMapper.addressDTOtoAddress(expected);

    assertEquals(
            expected.getCity() + "," +
                    expected.getUserDetails().getFirstName() + "," +
                    expected.getUserDetails().getUser().getUsername() + "," +
                    expected.getUserDetails().getContact().getEmail() + "," +
                    expected.getUserDetails().getProfileImage().getImageUrl(),
            mergeAddressDataToString(actual)
    );
}

private String mergeAddressDataToString(Address address) {
    StringJoiner stringJoiner = new StringJoiner(",");
    stringJoiner.add(address.getCity());
    stringJoiner.add(address.getUserDetails().getFirstName());
    stringJoiner.add(address.getUserDetails().getUser().getUsername());
    stringJoiner.add(address.getUserDetails().getContact().getEmail());
    stringJoiner.add(address.getUserDetails().getProfileImage().getImageUrl());

    return stringJoiner.toString();
}

Ответы [ 2 ]

1 голос
/ 20 марта 2019

Обычно я пишу пользовательский метод toString() для объекта в тесте или класса служебной программы, когда несколько тестов должны совместно использовать его. Таким образом, я могу проверить все значения с помощью одного assertEquals().

Я могу использовать новые строки, чтобы сделать утверждение более читабельным в IDE:

assertEquals(
    "city=Foo\n" + 
    "firstName=John\n" + 
    "user=doe\n" + 
    ....
    , toString(actual));

, что также хорошо работает, когда вам нужно проверить список значений:

    ...
    , list.stream().map(this::toString).collect(Collectors.joining("\n---\n"));

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

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

Когда тест прерывается из-за изменения выходных данных, я могу просто выбрать всю строку и заменить ее новым выходным. IDE сделает для меня форматирование.

0 голосов
/ 23 марта 2019

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

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

Address address = getAddress();
AddressDTO addressDTO = addressMapper.addressToAddressDTO(address);
Address actual = addressMapper.addressDTOtoAddress(addressDTO);
assertEquals(address, actual);

Это исключает сравнение отдельных элементов. Может быть даже выгодно, если представление сущности и DTO изменяются таким образом, что атрибуты больше не являются строго равными, а вместо этого должны быть только конвертируемыми назад и вперед.

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

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

С подходом строк или без него: Если у вас есть больше утверждений, где нужно сравнивать полные объекты и DTO, я бы рекомендовал написать ваши собственные вспомогательные методы утверждений, такие как assertEntityMatchesDto и assertDtoMatchesEntity.

Последнее замечание: могут быть причины не сравнивать все атрибуты объектов в одном тесте. Таким образом, тесты могут быть недостаточно сфокусированными. Возьмем пример изменения кодировки снова. Представьте, что вам придется изменить представление адреса электронной почты в DTO в какой-то момент времени. Затем, если вы всегда просматриваете все атрибуты в своих тестах, все ваши тесты не пройдут. Если вместо этого вы сфокусировали тесты для отдельных атрибутов, такое изменение, если оно выполнено правильно, повлияет только на тесты с фокусом на атрибут электронной почты, но оставит другие тесты без изменений.

...