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

Сделайте ваши тесты более удобочитаемыми с помощью AssertJ и Синтаксис BDD

Я люблю писать тесты! Я получаю повышение дофамина каждый раз, когда вижу эти зеленые тесты в своих отчетах. Каждый… Помеченный java, тестирование.

Я люблю писать тесты! Я получаю повышение дофамина каждый раз, когда вижу эти зеленые тесты в своих отчетах. Каждый провал теста – это крошечная головоломка, которую нужно решить, и вы получите зеленый результат, как только найдете решение. Суперзеленый!

Но я знаю, что написание и чтение тестов может быть рутиной для некоторых разработчиков. Я считаю, что это можно улучшить, упростив написание тестов и сделав эти длинные и скучные последовательности assertEquals более содержательными и удобными для чтения.

Помните, что разработчики тратят больше времени на чтение кода, чем на его написание !

С этими советами чтение ваших тестов будет легким делом, и ваши товарищи по команде поблагодарят вас за это!

Используйте Assert вместо Junit утверждения

Если вы используете утверждения Junit в своих тестах, скорее всего, вы в основном используете следующие утверждения:

assertEquals(expected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(actual);

Большую часть времени я вижу тесты, использующие assertEquals() для чего-либо подобного assertEquals(true, someList.size()); 😱

Распространенная ошибка использования Junit assertEquals() и это 2 параметра, которые вы можете легко инвертировать ожидаемое значение с фактическим значением, если вы не обращаете внимания. Пока тест зеленый, вы не увидите ошибку, но если тест не пройдет, сообщение об ошибке будет вводить в заблуждение!

Пример: давайте предположим, что наш метод сгенерировать хэш()

    @Test
    void testHashMethod() {
        String actual = underTest.generateHash("some string");
        assertEquals(actual.length(), 35); // Wrong order!
    }

Сообщение об ошибке будет вводить в заблуждение, так как ожидаемое значение является результатом метода:

org.opentest4j.AssertionFailedError: 
Expected :32
Actual   :35

Если вы используете IntelliJ, у вас могут быть подсказки о том, какой параметр какой: но у вас не будет этих подсказок, например, во время просмотра кода на github. 🙈

Введите AssertJ

AssertJ поставляется с различными утверждениями, которые могут быть объединены в цепочку и специфичны для типа вашей “фактической” переменной.

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

// actual is a String
assertThat(actual).isEqualTo("MD55AC749FBEEC93607FC28D666BE85E73A");
assertThat(actual).hasSize(35)
    .contains("A") // You can chain assertions if you want
    .startsWith("MD5")
    .isUpperCase()
    .isNotBlank();

// actual and expected are collections of String
assertThat(actual).hasSize(2);
assertThat(actual).contains("first value");
assertThat(actual).first().isEqualTo("first value"); // Get the first element
assertThat(actual).containsExactly("first value", "second value");
assertThat(actual).containsExactlyInAnyOrder("second value", "first value");
assertThat(actual).containsExactlyInAnyOrderElementsOf(expected);
assertThat(actual).doesNotContain("forbidden value", "another value");
// Make an assertion on all elements of the collection :
assertThat(actual).allSatisfy(s -> assertThat(s).doesNotStartWith("foo"));

// actual is an int
assertThat(actual).isEqualTo(42);
assertThat(actual).isGreaterThan(40);
assertThat(actual).isPositive();
assertThat(actual).isIn(0, 2, 42, 1337);
assertThat(actual).isBetween(30, 50);

// actual is an Instant
assertThat(actual).isAfterOrEqualTo(Instant.now())
    .isBetween(pastInstant, futureInstant)
    .isCloseTo(Instant.now(), within(1, ChronoUnit.MINUTES));

// Test an exception
Throwable thrown = catchThrowable(() -> List.of(1, 2, 3).get(4));
assertThat(thrown).isInstanceOf(ArrayIndexOutOfBoundsException.class);

Как вы можете видеть, утверждение всегда начинается с assertThat(фактическое) , которое существует для любого типа. Это как 3 преимущества:

  1. Вы не можете инвертировать фактические и ожидаемые значения
  2. Утверждение читается как обычное предложение на английском языке, что позволяет вам сделать свой тест очень четким в отношении того, что вы намерены проверить
  3. Вы можете легко узнать, какое утверждение доступно для вашего типа данных, с помощью автоматического завершения IDE! Для всего остального в документации есть множество примеров .

Последнее замечание об AssertJ: сообщения об ошибках часто являются более явными, чем утверждения Junit. Например, если ваш список содержит на один элемент больше, чем ожидалось:

// assertJ (using assertThat(actual).containsExactly("first value", "second value"); )
java.lang.AssertionError: 
Expecting:
  ["first value", "second value", "third value"]
to contain exactly (and in same order):
  ["first value", "second value"]
but some elements were not expected:
  ["third value"]

// junit assertion (using assertLinesMatch(expected, actual); )
org.opentest4j.AssertionFailedError: more actual lines than expected: 1

Если вы используете Spring Boot, AssertJ уже входит в состав spring-boot-starter-test вместе с Mockito и другими библиотеками тестирования. Хорошие новости!

Используйте один и тот же шаблон для всех ваших тестов

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

Я склонен использовать следующий шаблон, предложенный Робертом К. Мартином в Чистый код :

    @Test
    void someTest() {
        // Given
            // prepare your expected values, program mocks
        // When
            // call the method under test
        // Then
            // put all you assertions here, verify your mocks
    }

Что прекрасно подводит меня к следующему пункту….

Используйте синтаксис BDD

Знаете ли вы, что и Mockito, и AssertJ предлагают альтернативный синтаксис?

Для AssertJ вы можете использовать BDDAssertions.затем заменить ваш утверждать, что() :

import static org.assertj.core.api.BDDAssertions.then;
(...)
then(actual).isEqualTo(expected);// Everything else is the same

Для Mockito вы можете использовать BDDMockito.данный вместо когда() :

import static org.mockito.BDDMockito.given;
(...)
// instead of :
when(myMock.someMethod(any())).thenReturn("Some mocked value");
// use this :
given(myMock.someMethod(any())).willReturn("Some mocked value");

Это превратит что-то вроде этого:

@Test
    void someTest() {
        when(myMock.someMethod(any())).thenReturn("some string");
        var expected = "MD55AC749FBEEC93607FC28D666BE85E73A";

        var actual = underTest.generateHash(10);

        assertThat(actual).isEqualTo(expected);
        verify(myMock).someMethod(10);
    }

в это:

@Test
    void someTest() {
        // Given
        given(myMock.someMethod(any())).willReturn("some string");
        var expected = "MD55AC749FBEEC93607FC28D666BE85E73A";

        // When
        var actual = underTest.generateHash(10);

        // Then
        then(actual).isEqualTo(expected);
        verify(myMock).someMethod(10);
    }

Имейте в виду, что у BDDMockito также есть метод then() , я не рекомендую использовать оба в одном классе.

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

Используйте мягкие утверждения

Если ваш метод тестирования содержит несколько утверждений, но одно из них завершается неудачей, тест останавливается при первом сбое, но не выполняет другие утверждения. Если у вас 2 утверждения не работают, вы не узнаете об этом, пока не исправите первое утверждение, снова запустите тест и получите второй сбой. Такая пустая трата времени!

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

Мягкие утверждения позволяют выполнять многократные утверждения, не останавливаясь при первом сбое. Если что-то не удастся, у вас будет отчет о том, какие утверждения не удались.

    @Test
    void testList() {
        var actual = List.of("first value", "second value", "third value");
        var expected = List.of("first value", "second value");

        var softly = new BDDSoftAssertions();
        softly.then(actual).contains("first value");
        softly.then(actual).first().isEqualTo("first value");
        softly.then(actual).containsExactly("first value", "second value"); // fails
        softly.then(actual).containsExactlyInAnyOrderElementsOf(expected); // fails
        softly.then(actual).doesNotContain("forbidden value", "another value");
        softly.then(actual).allSatisfy(s -> assertThat(s).doesNotStartWith("foo"));
        softly.assertAll();
    }

Вот результат:

org.assertj.core.error.AssertJMultipleFailuresError: 
Multiple Failures (2 failures)
-- failure 1 --
Expecting:
  ["first value", "second value", "third value"]
to contain exactly (and in same order):
  ["first value", "second value"]
but some elements were not expected:
  ["third value"]

at SimpleTest.testList(SimpleTest.java:49)
-- failure 2 --
Expecting:
  ["first value", "second value", "third value"]
to contain exactly in any order:
  ["first value", "second value"]
but the following elements were unexpected:
  ["third value"]

at SimpleTest.testList(SimpleTest.java:50)

⚠️ Внимание: не забудьте строку с утверждать все() в противном случае ваш тест будет зеленым, но на самом деле никаких утверждений не выполняется!

Существуют и другие способы использования мягких утверждений в документации , которые позволяют вам опустить утверждать все() , если хотите.

Вот и все на сегодня! У меня все еще есть много советов относительно тестов, но они будут для другого поста.

Ты узнал сегодня что-то новое? У вас есть предложения? Не стесняйтесь оставлять комментарии ниже!

Оригинал: “https://dev.to/daviddasilva/make-your-tests-more-readable-using-assertj-and-bdd-syntax-27f”