Упрощение и улучшение модульных тестов картографов типов данных - PullRequest
0 голосов
/ 21 февраля 2020

Я написал сопоставители типов с использованием Mapstruct для выполнения некоторых общих задач при отображении между сущностями и DTO, таких как переименование свойств, «выбор вишен» их путем перемещения их из вложенной структуры до уровня root и т.д. c. Начальная реализация Po C выглядит следующим образом:

@Mapper(
    componentModel = "spring",
    injectionStrategy = InjectionStrategy.CONSTRUCTOR,
    uses = {DocumentationMapper.class},
    imports = {Kind.class})
public interface ConnectorMapper {
  @Mapping(source = "metadata.extendedProperties", target = "metadata.additionalProperties")
  @Mapping(source = "metadata.documentation", target = "documentation")
  @Mapping(source = "metadata.name", target = "name")
  @Mapping(source = "spec", target = "connectorSpecYaml")
  ConnectorDto fromEntity(final ConnectorYaml connectorYaml);

  @InheritInverseConfiguration
  @Mapping(target = "kind", expression = "java(Kind.Connector)")
  ConnectorYaml fromDto(final ConnectorDto connectorDto);
}

Этот преобразователь в основном выполняет следующие действия:

  1. Переименовывает вложенное свойство metadata.extendedProperties в metadata.additionalProperties и наоборот. наоборот.
  2. Cherry - выбрать вложенное свойство metadata.documentation для DTO и применить правила отображения, определенные в DocumentationMapper (с помощью предложения uses), и наоборот.
  3. Cherry- выберите вложенное свойство metadata.name для DTO и наоборот.
  4. Переименуйте свойство spec в connectorSpecYaml и наоборот.

Для модульного тестирования При таком поведении мне пришлось написать несколько сумасшедших утверждений, которые больше не читаются:

@Test
public void connectorMapperFromEntity() {
  // GIVEN a connector entity with fixed values
  final var connector = FixtureBuilder.createConnectorYaml("connector");

  // WHEN mapping the entity to a DTO
  final var connectorDto = connectorMapper.fromEntity(connector);

  // THEN the mapping yields a result
  assertThat(connectorDto).isNotNull();
  // AND the name has been cherry-picked from metadata into the target
  assertThat(connectorDto.getName()).isEqualTo(connector.getMetadata().getName());
  // AND the metadata has been mapped correctly
  assertThat(connectorDto.getMetadata())
      .isEqualToIgnoringGivenFields(connector.getMetadata(), "additionalProperties");
  // AND the extended properties in metadata have been renamed correctly
  assertThat(connectorDto.getMetadata().getAdditionalProperties())
      .isEqualTo(connector.getMetadata().getExtendedProperties());
  // AND the documentation has been cherry-picked into the target
  assertThat(connectorDto.getDocumentation())
      .isEqualToIgnoringGivenFields(
          connector.getMetadata().getDocumentation(), "additionalProperties");
  // AND the extended properties in documentation have been renamed correctly
  assertThat(connectorDto.getDocumentation().getAdditionalProperties())
      .isEqualTo(connector.getMetadata().getDocumentation().getExtendedProperties());
  // AND the spec has been mapped correctly
  assertThat(connectorDto.getConnectorSpecYaml()).isEqualTo(connector.getSpec());
}

Другое направление одинаково грубо для чтения из-за ручной сборки DTO из данных прибора:

@Test
public void connectorMapperFromDto() {
  final var fixture = FixtureBuilder.createConnectorYaml("connector");
  final var connectorDto =
      ConnectorDto.builder()
          .connectorSpecYaml(fixture.getSpec())
          .metadata(
              MetadataDto.builder()
                  .additionalProperties(fixture.getMetadata().getExtendedProperties())
                  .labels(fixture.getMetadata().getLabels())
                  .build())
          .documentation(
              DocumentationDto.builder()
                  .additionalProperties(
                      fixture.getMetadata().getDocumentation().getExtendedProperties())
                  .exampleUsage(fixture.getMetadata().getDocumentation().getExampleUsage())
                  .longDescription(fixture.getMetadata().getDocumentation().getLongDescription())
                  .shortDescription(
                      fixture.getMetadata().getDocumentation().getShortDescription())
                  .exampleResponse(fixture.getMetadata().getDocumentation().getExampleResponse())
                  .build())
          .name(fixture.getMetadata().getName())
          .build();
  final var connector = connectorMapper.fromDto(connectorDto);
  assertThat(connector).isNotNull();
  assertThat(connector).isEqualTo(fixture);
}

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

Ответы [ 2 ]

0 голосов
/ 19 марта 2020

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

0 голосов
/ 03 марта 2020

Что вы можете сделать, это заполнить ваш DTO случайными тестовыми данными. Проверьте здесь как это сделать.

Тогда вы могли бы написать такой тестовый пример (немного псевдокод, но, надеюсь, он передает мои идеи):


@Test
public void testNonNullCases() {

   ConnectorDto controlDto = generateTestData( ConnectorDto.class );

   // map hence-and-forth
   ConnectorYaml resultEntity = connectorMapper.fromDto( controlDto );
   ConnectorDto resultDto = connectorMapper.fromEntity( resultEntity );

   // use assertj to verify them (nested and all)

   assertThat( resultDto )
      .usingRecursiveComparison()
      .ignoringFields( /* fields that you want to do by hand to cover exceptions */
      .isEqualTo( controlDto );

   // do remaining assertions.

}

@Test
public void testNullCases() {

    // I would add a testcase were you leave out attributes (null) and some nested classes in your graph.. just to see whether null cases are handled correctly. You might use jacoco (or other coverage) on the generated code to drive this test

}

public static T generateTestData(Class<T> clz) {
 // get inspiration from https://github.com/Blastman/DtoTester/pull/1
}

}
...