Рубрики
Без рубрики

Тестирование вложенных структур с помощью AssertJ

В предыдущем посте я описал, почему мне нравится писать тесты с помощью AssertJ. В этом посте я хотел бы поделиться… Помеченный как java, assert, testing.

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

public class Container {

    private String name;
    private List leaves = new ArrayList<>();

    public Container(String name, List leaves) {
        this.name = name;
        this.leaves.addAll(leaves);
    }

    public String getName() {
        return name;
    }

    public List getLeaves() {
        return Collections.unmodifiableList(leaves);
    }
}

Класс Leaf состоит из имени и описания.

public class Leaf {

    private String name;
    private String description;

    public Leaf(String name, String description) {
        this.name = name;
        this.description = description;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }
}

Для обоих классов реализация AbstractAssert может быть легко реализована и может выглядеть следующим образом.

public class ContainerAssert extends AbstractAssert {

    private ContainerAssert(Container actual) {
        super(actual, ContainerAssert.class);
    }

    public static ContainerAssert assertThat(Container actual) {
        return new ContainerAssert(actual);
    }

    public ContainerAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }
}
public class LeafAssert extends AbstractAssert {

    private LeafAssert(Leaf actual) {
        super(actual, LeafAssert.class);
    }

    public static LeafAssert assertThat(Leaf actual) {
        return new LeafAssert(actual);
    }

    public LeafAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }

    public LeafAssert hasDescription(String description) {
        Assertions.assertThat(actual.getDescription()).isEqualTo(description);
        return this;
    }
}

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

public class ContainerTest {

    @Test
    public void test() throws Exception {
        Container container = classUnderTest.produceContainer();

        assertThat(container).hasName("containerName");
        assertThat(container.getLeaf(0)).hasName("leaf1").hasDescription("description1");
        assertThat(container.getLeaf(1)).hasName("leaf2").hasDescription("description2");
    }
}

Дело в том, что этот подход не выигрывает от свободного интерфейса AssertJ . Возможность перехода назад и вперед между утверждением контейнера и утверждением листа была бы приятной. Для достижения этой цели утверждение контейнера расширяется с помощью метода leaf, который возвращает утверждение Leaf для определенного листа.

public class ContainerAssert extends AbstractAssert {

    private ContainerAssert(Container actual) {
        super(actual, ContainerAssert.class);
    }

    public static ContainerAssert assertThat(Container actual) {
        return new ContainerAssert(actual);
    }

    public ContainerAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }

    public LeafAssert leaf(int index) {
        return LeafAssert.assertThat(actual.getLeaves(index), actual);
    }
}

Листовое утверждение также получает новый метод. Метод родительского контейнера переходит обратно к утверждению контейнера, которое соответствует родительскому элементу этого листа.

public class LeafAssert extends AbstractAssert {

    private Container currentContainer;

    private LeafAssert(Leaf actual, Container currentContainer) {
        super(actual, LeafAssert.class);
        this.currentContainer = currentContainer;
    }

    public static LeafAssert assertThat(Leaf actual) {
        return new LeafAssert(actual, null);
    }

    public static LeafAssert assertThat(Leaf actual, Container currentContainer) {
        return new LeafAssert(actual, currentContainer);
    }

    public LeafAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }

    public LeafAssert hasDescription(String description) {
        Assertions.assertThat(actual.getDescription()).isEqualTo(description);
        return this;
    }

    public ContainerAssert parentContainer() {
        return ContainerAssert.assertThat(currentContainer);
    }
}

Результирующий тест будет выглядеть следующим образом.

public class ContainerTest {

    @Test
    public void test() throws Exception {
        Container container = classUnderTest.produceContainer();

        assertThat(container)
            .hasName("containerName")
            .leaf(0)
            .hasName("leaf1")
            .hasDescription("description1")
            .parentContainer()
            .leaf(1)
            .hasName("leaf2")
            .hasDescription("description2");
    }
}

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

На мой взгляд, эту проблему можно очень элегантно решить, передав Consumer контейнерному методу Asserts leaf. Потребитель принимает Конечное утверждение, которое инициализируется запрошенным конечным элементом.

public class ContainerAssert extends AbstractAssert {

    private ContainerAssert(Container actual) {
        super(actual, ContainerAssert.class);
    }

    public static ContainerAssert assertThat(Container actual) {
        return new ContainerAssert(actual);
    }

    public ContainerAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }

    public ContainerAssert leaf(int index, Consumer consumer) {
        consumer.accept(LeafAssert.assertThat(actual.getLeaves(index)));
        return this;
    }
}

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

public class LeafAssert extends AbstractAssert {

    private LeafAssert(Leaf actual) {
        super(actual, LeafAssert.class);
    }

    public static LeafAssert assertThat(Leaf actual) {
        return new LeafAssert(actual);
    }

    public LeafAssert hasName(String name) {
        Assertions.assertThat(actual.getName()).isEqualTo(name);
        return this;
    }

    public LeafAssert hasDescription(String description) {
        Assertions.assertThat(actual.getDescription()).isEqualTo(description);
        return this;
    }
}

В тестовом классе утверждения для определенного листа могут быть переданы в качестве лямбда-выражения методу lead.

public class ContainerTest {

    @Test
    public void test() throws Exception {
        Container container = classUnderTest.produceContainer();

        assertThat(container)
            .hasName("containerName")
            .leaf(0, leaf -> leaf
                .hasName("leaf1")
                .hasDescription("description1"))
            .leaf(1, leaf -> leaf
                .hasName("leaf2")
                .hasDescription("description2")));
    }
}

Таким образом, в классах активов Container Assert и Leaf не требуется дополнительной обработки состояния. Отступ позволяет легко увидеть, на каком уровне вложенной структуры выполняются утверждения.

Оригинал: “https://dev.to/gossie/testing-nested-structures-with-assertj-33mn”