Статьи

Новый способ извлечения значений в утверждениях коллекций AssertJ

Мы уже использовали  AssertJ   в  Young Digital Planet  в нескольких проектах Java, и нам это нравится. Это облегчает написание и чтение наших тестов, в основном благодаря простоте написания пользовательских утверждений. Автоматически сгенерированные утверждения послужили основой для тестирования классов нашего домена, за исключением одной проблемы. Часто возникает необходимость утвердить список наших сущностей по одному из их полей, а затем перейти к другим утверждениям. Поле обычно является перечисляемым значением и выглядит так:

public enum Gender {
    FEMALE,
    MALE,
}

public class Person {
    private final String name;
    private final Gender gender;

    // the constructor and (g|s)etters, etc.
}

Теперь,  когда вы хотели сделать некоторые утверждения на  Person экземплярах, вам пришлось извлечь значения, используя  extracting метод. Пример будет выглядеть так:

Person wilma, betty, pebbles, fred, barney, bambam;
List<Person> persons = Lists.newArrayList(wilma, betty, pebbles, fred, barney, bambam; // guava-style

assertThat(persons).extracting("gender").containsExactly(FEMALE, FEMALE, FEMALE, MALE, MALE, MALE);

Несмотря на то, что извлечение по имени свойства весьма удобно, это плохо для объектно-ориентированного проектирования. Основная причина заключается в том, что любое изменение в классе  Person приведет к сбою всех тестовых случаев из-за свойства недопустимого имени. Хотя это не требует много изменений, когда у вас есть пара тестовых случаев, введение изменений в несколько сотен — ад.

Вы всегда можете написать свои собственные утверждения для каждого поля, но это немного излишне. Вместо этого я предложил изменить AssertJ, чтобы он появился в версии 1.7.0. Изменение представило интерфейс Single Abstract Method (SAM)  Extractor (вы могли бы назвать его функциональным, если вы использовали Java 8), который обрабатывает извлечение требуемого свойства, но также может выполнять любые другие необходимые преобразования. Благодаря этому больше не нужно писать целый набор утверждений, только небольшой класс, который извлекает проверенное свойство. Теперь предыдущий пример будет выглядеть так:

public class GenderExtractor implements Extractor<Person, Gender> {

    // do yourself some good and write a factory method
    private GenderExtractor() { }

    public static Extractor<Person, Gender> gender() {
        return new GenderExtractor();
    }

    @Override
    public Gender extract(Person input) {
        return input.getGender();
    }
}

assertThat(persons).extracting(gender()).containsExactly(FEMALE, FEMALE, FEMALE, MALE, MALE, MALE);

Намного красивее, не правда ли? Но подождите, это еще не все! Поскольку я много играл с функциональными вещами (а именно с Scala), я добавил еще один метод, который дополняет  extracting, и он называется flatExtracting. Если вы видите сходство между  extracting и функциональным  map, то вам  flatExtracting должно быть ясно. Если вы не видите соответствия:  flatExtracting извлекает списки с использованием  Extractor реализаций, а затем объединяет списки. Просто посмотрите на пример:

public class ChildrenExtractor implements Extractor<Person, List<Person>> {
    
    // with a factory method    

    @Override
    public List<Person> extract(Person input) {
        return input.getChildren();
    }
}

assertThat(Lists.newArrayList(fred, barney)).flatExtracting(children()).contains(pebbles, bambam);

Если вы использовали старый  extracting, не бойтесь! Старые методы были оставлены для обеспечения обратной совместимости. Изменение было объединено на прошлой неделе, так что не стесняйтесь получить снимок версии AssertJ и протестировать  extracting. Кроме того , есть некоторые примеры в репозитории  здесь .