Хорошая практика для классов сущностей Java и их модульных тестов - PullRequest
2 голосов
/ 30 июня 2019

Я просто хочу подтвердить, если вы, ребята, считаете это хорошей практикой:

  • Для написания классов сущностей в Java с некоторыми проверками с помощью аннотаций javax.validation.constraints
  • Длянаписать модульный тест, чтобы утверждать, что проверки
  • Чтобы написать модульный тест, чтобы утверждать методы получения и установки, так как это способ утверждать, что класс содержит все необходимые нам поля
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(exclude = "client")
@Data
@Entity
@Table(name = "addresses")
public class Address extends BaseEntity {

  private static final long serialVersionUID = -5966581124342250987L;

  @NotNull
  @Size(min = 2, max = 40)
  @Column(name = "line1", nullable = false, length = 40)
  private String line1;

  @Size(min = 2, max = 40)
  @Column(name = "line2", length = 40)
  private String line2;

  @NotNull
  @Size(min = 2, max = 40)
  @Column(name = "city", length = 40)
  private String city;

  @NotNull
  @Size(min = 2, max = 2)
  @Column(name = "country_code", length = 2)
  private String countryCode; //code ISO 3166 two-letter country codes

  @NotNull
  @EqualsAndHashCode.Exclude
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "client_id")
  private Client client;

}

class AddressTest {

  private static final String ADDRESS_LINE1 = "Address line 1";
  private static final String ADDRESS_LINE2 = "Address line 2";
  private static final String ADDRESS_CITY = "Address City";
  private static final String ADDRESS_COUNTRY_CODE = "IT";
  private static final Client ADDRESS_CLIENT = new Client();

  private Address address;

  @BeforeEach
  void setUp() {
    address = Address.builder()
            .line1( ADDRESS_LINE1 )
            .line2( ADDRESS_LINE2 )
            .city( ADDRESS_CITY )
            .countryCode( ADDRESS_COUNTRY_CODE )
            .client( ADDRESS_CLIENT )
            .build();
  }

  @Test
  public void CreateAddress_AssertBasicFields() {
    assertEquals(ADDRESS_LINE1, address.getLine1());

    assertEquals(ADDRESS_LINE2, address.getLine2());

    assertEquals(ADDRESS_CITY, address.getCity());

    assertEquals(ADDRESS_COUNTRY_CODE, address.getCountryCode());
  }

  @Test
  public void CreateAddress_AssertClient() {
    assertEquals(ADDRESS_CLIENT, address.getClient());
  }
}
public class AddressValidationTest {
  private static ValidatorFactory validatorFactory;
  private static Validator validator;

  private static final String ADDRESS_LINE1 = "Address line 1";
  private static final String ADDRESS_LINE2 = "Address line 2";
  private static final String ADDRESS_CITY = "Address City";
  private static final String ADDRESS_COUNTRY_CODE = "IT";
  private static final Client ADDRESS_CLIENT = new Client();

  @BeforeAll
  public static void createValidator() {
    validatorFactory = Validation.buildDefaultValidatorFactory();
    validator = validatorFactory.getValidator();
  }

  @AfterAll
  public static void close() {
    validatorFactory.close();
  }

  @Test
  public void shouldHaveNoViolations() {
    //given:
    Address address = Address.builder()
            .line1( ADDRESS_LINE1 )
            .line2( ADDRESS_LINE2 )
            .city( ADDRESS_CITY )
            .countryCode( ADDRESS_COUNTRY_CODE )
            .client( ADDRESS_CLIENT )
            .build();

    //when:
    Set<ConstraintViolation<Address>> violations
            = validator.validate(address);

    //then:
    assertTrue(violations.isEmpty());
  }

  @Test
  public void shouldDetectInvalidLine1() {
    //given too short name:
    Address address = Address.builder()
            .line1( "L" )
            .line2( ADDRESS_LINE2 )
            .city( ADDRESS_CITY )
            .countryCode( ADDRESS_COUNTRY_CODE )
            .client( ADDRESS_CLIENT )
            .build();

    //when:
    Set<ConstraintViolation<Address>> violations = validator.validate(address);

    //then:
    assertEquals(1, violations.size());

    ConstraintViolation<Address> violation = violations.iterator().next();
    assertEquals("size must be between 2 and 40", violation.getMessage());
    assertEquals("line1", violation.getPropertyPath().toString());
    assertEquals("L", violation.getInvalidValue());
  }

  @Test
  public void shouldDetectInvalidLine2() {
    //given too short name:
    Address address = Address.builder()
            .line1( ADDRESS_LINE1 )
            .line2( "L" )
            .city( ADDRESS_CITY )
            .countryCode( ADDRESS_COUNTRY_CODE )
            .client( ADDRESS_CLIENT )
            .build();

    //when:
    Set<ConstraintViolation<Address>> violations = validator.validate(address);

    //then:
    assertEquals(1, violations.size());

    ConstraintViolation<Address> violation = violations.iterator().next();
    assertEquals("size must be between 2 and 40", violation.getMessage());
    assertEquals("line2", violation.getPropertyPath().toString());
    assertEquals("L", violation.getInvalidValue());
  }

  @Test
  public void shouldDetectInvalidCity() {
    //given too short name:
    Address address = Address.builder()
            .line1( ADDRESS_LINE1 )
            .line2( ADDRESS_LINE2 )
            .city( "Aaaaaaaaaa AAAAAAAAAAA BBBBBBBBBBb dddddddddddddd eeeeeeeeeeeeee ffffffffffff ggggggggggg" )
            .countryCode( ADDRESS_COUNTRY_CODE )
            .client( ADDRESS_CLIENT )
            .build();

    //when:
    Set<ConstraintViolation<Address>> violations = validator.validate(address);

    //then:
    assertEquals( 1, violations.size());

    ConstraintViolation<Address> violation = violations.iterator().next();
    assertEquals("size must be between 2 and 40", violation.getMessage());
    assertEquals("city", violation.getPropertyPath().toString());
    assertEquals("Aaaaaaaaaa AAAAAAAAAAA BBBBBBBBBBb dddddddddddddd eeeeeeeeeeeeee ffffffffffff ggggggggggg", violation.getInvalidValue());
  }

  @Test
  public void shouldDetectInvalidCountryCode() {
    //given too short name:
    Address address = Address.builder()
            .line1( ADDRESS_LINE1 )
            .line2( ADDRESS_LINE2 )
            .city( ADDRESS_CITY )
            .countryCode( "ITA" )
            .client( ADDRESS_CLIENT )
            .build();

    //when:
    Set<ConstraintViolation<Address>> violations = validator.validate(address);

    //then:
    assertEquals( 1, violations.size());

    ConstraintViolation<Address> violation = violations.iterator().next();
    assertEquals("size must be between 2 and 2", violation.getMessage());
    assertEquals("countryCode", violation.getPropertyPath().toString());
    assertEquals("ITA", violation.getInvalidValue());
  }

  @Test
  public void shouldDetectInvalidClient() {
    //given too short name:
    Address address = Address.builder()
            .line1( ADDRESS_LINE1 )
            .line2( ADDRESS_LINE2 )
            .city( ADDRESS_CITY )
            .countryCode( ADDRESS_COUNTRY_CODE )
            .build();

    //when:
    Set<ConstraintViolation<Address>> violations = validator.validate(address);

    //then:
    assertEquals( 1, violations.size());

    ConstraintViolation<Address> violation = violations.iterator().next();
    assertEquals("must not be null", violation.getMessage());
    assertEquals("client", violation.getPropertyPath().toString());
    assertEquals(null, violation.getInvalidValue());
  }
}

Ответы [ 3 ]

0 голосов
/ 30 июня 2019

Сущности - это расширенное зеркало базы данных (экземпляр - это SSOT, класс - это SVOT), M im MVC, это бины.Если бобы должны быть проверены юнитом: Нет.

Что вы делаете, вы смешиваете M и C в сущностях.Должен ли C быть проверен модулем?Yes!!!.

Так что вы действительно должны их проверить!

0 голосов
/ 30 июня 2019

Сущности - это просто объекты POJO без логики. Стоит знать, что именно вы тестируете.

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

не private static final String ADDRESS_LINE1 = "Address line 1";, а

 Address address = Address.builder()
            .line1( randomAddress() )
            .city( randomCity() )
            .countryCode( randomCountry() )
            .build()

, где random*() - это предопределенные методы, которые возвращают некоторые допустимые возвращаемые значения.

Если вы хотите протестировать Hibernate и mappers, стоит подумать о встроенной базе данных, такой как H2 , для выполнения тестов.

Что касается геттеров и сеттеров, в большинстве случаев они генерируются автоматически, поэтому я не вижу смысла их тестировать.

0 голосов
/ 30 июня 2019

Обратите внимание, что на самом деле вы тестируете только небольшую часть того, что используете: например, все и никакие конструкторы arg, setter, hashCode() и equals() не тестируются.

Теперь это хорошая практика, чтобы проверить это? Я думаю, что это так.
Использование аннотаций Lombok создает некоторые реализации, которые вы можете проверить поведение только во время выполнения (тестирование и время выполнения приложения).
Например, аннотация может не давать ожидаемого поведения из-за конфликта с другим или из-за неправильного использования (например, ошибка переполнения стека, если в сгенерированном методе существует цикл), и вы можете обнаружить их только во время выполнения, в лучшем случае с исключением, в котором четко указано проблема или, в худшем случае, коварная ошибка, коренная причина которой не очевидна.
Точно так же, если кто-то непреднамеренно нарушает реализацию, заменяя ее (для целей отладки):

@ToString(exclude = "client")

от:

@ToString

Вы хотите, чтобы автоматизированный тест обнаружил его.
Наконец, если вы хотите по какой-либо причине удалить Lombok из своего проекта, вы хотели бы иметь регрессионные тесты, которые могут подтвердить, что новая реализация без Lombok по-прежнему верна.
Так что да, если использовать много аннотаций, значит написать много утверждений / тестов.
Но в некотором смысле это нормально, так как приведение в действие ваших классов сторонним API - это не мелочь.
Обратите внимание, что для получателей / установщиков я не думаю, что это имеет большое значение для их тестирования, поскольку тот факт, что они могут вызывать их, обычно означает, что они были правильно реализованы Lombok.

Рассуждение в этих терминах (унитарное тестирование того, что обеспечивает API вашего класса) способствует созданию полезного и качественного кода, потому что мы дважды подумаем, прежде чем добавлять аннотацию, которая генерирует некоторый код / ​​логику. И это хорошо, потому что я часто вижу злоупотребления аннотациями Lombok: «мы не уверены, что это нужно, но нет проблем, так как это легко объявить».

Я бы применил точно такую ​​же идею для аннотаций javax.validation.constraints, в то время как для проверки правильности я бы, вероятно, использовал параметризованные тесты для уменьшения кода котельной пластины.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...