Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

понедельник, 16 марта 2020 г.

SmartAssert или как проверять статус всех assertEquals в одном тесте

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

Код лежит тут. Лицензия GNU GPL v3. На здоровье.

Если тебе лень писать @Test public void shouldBlahBlahBlah_whenBlahBlahBlah () {} для каждого отдельного ассерта, то скорее всего у тебя несколько ассертов в одном тесте. Так не рекомендуют делать, но это решение встречается достаточно часто. Почему? Просто потому что блок // given в этом конкретном тесте с 3-5-7ю ассертами может быть большим. А копипастить его в каждый отдельный тест чтобы уютно разместить там 1 assertEquals еще большее зло.

Да, конечно можно выделить // given часть теста в отдельный метод и повторно использовать. Только фигня получится. Вот был лаконичный тест: в // given подготовились, в // when вызвали одну строчку тестируемого метода, и в 3-5-7ю assertEquals проверили объект, который вернулся. И что? 1 тест. И следуя рекомендации 1 assert на 1 тест мы получаем кучу операторных скобок, 8 методов и 10 шагов в сторону от рекомендации - "тест как документация". Вот все было перед глазами, а теперь иди собирай все все мысли по классу.

И если ты не следуешь бездумно всем рекомендациям, описанным в инженерных книгах, то ты, так же как и я получишь один тест с 3-5-7ю ассертами. Но тут есть другой бок - скорее всего ты, так же как и я, утомился перезапускать этот один стройный тест. Да-да, интеграционный, блин, со спрингом под капотом, от чего он ранится не 10 милисекунд, а 100 секунд! Потому что мир джава жесток и беспощаден...
@SpringBootTest(classes = CodenjoyContestApplication.class,
        properties = "spring.main.allow-bean-definition-overriding=true")
@RunWith(SpringRunner.class)
@ActiveProfiles(SQLiteProfile.NAME)
@Import(RestGameControllerTest.ContextConfiguration.class)
@WebAppConfiguration
public class RestGameControllerTest {
...в этом тесте будет несколько assertEquals. И раз за разом, натыкаясь на очередной свалившийся ассерт, скорее всего ты, так же как и я, задавался вопросом: какого фига в junit тест валится после первого же ассерта?

Решение созрело.

Да, я знаю, что писать несколько assertEquals в одном тесте (было) не ок. Но писать по новой конструкции для каждого ассерта тоже перебор.
@Test
public void shouldBlahBlah_whenBlahBlah() {
   ...
}
Вот и написал свой assertEquals который агрегирует ошибки и валидирует их только, когда ты явно этого попросишь в @After методе
@After
public void checkErrors() {
    SmartAssert.checkResult();
}
В простом вариаенте запуска потребуется еще всего лишь импортнуть класс, в котором есть статический assertEquals метод.
import static com.codenjoy.dojo.stuff.SmartAssert.*;
А старый импорт удалить
import static org.junit.Assert.*;
А что умного? Так это вывод результатов. Каждый тест отработает от начала и до конца, не важно сколько assertEquals по дороге не пройдут. При этом ты увидишь в консоли все expected: <qwe> but was: <asd> блоки и кусочек стектрейса из тестового класса, в котором были вызовы измененного assertEquals длинной до 10 строк (на это можно повлиять).

Например вот тест, я в нем заведомо поломал первые 4 assert, заменив expected с true на false
@Test
public void shouldExists() {
    assertEquals(false, service.exists("first"));
    assertEquals("false", get("/rest/game/first/exists"));
   
    assertEquals(false, service.exists("second"));
    assertEquals("false", get("/rest/game/second/exists"));

    assertEquals(false, service.exists("non-exists"));
    assertEquals("false", get("/rest/game/non-exists/exists"));
}
В оригинальной версии тест прекратил бы выполнение после первой же строчки. Слетел ассерт - давай до свидания. Но не co SmartAssert
org.junit.ComparisonFailure: expected:<[fals]e>; but was:<[tru]e>
 com.codenjoy.dojo.web.rest.RestGameControllerTest.shouldExists(RestGameControllerTest.java:135)

org.junit.ComparisonFailure: expected:<[fals]e> but was:<[tru]e>
 com.codenjoy.dojo.web.rest.RestGameControllerTest.shouldExists(RestGameControllerTest.java:136)

org.junit.ComparisonFailure: expected:<[fals]e> but was:<[tru]e>
 com.codenjoy.dojo.web.rest.RestGameControllerTest.shouldExists(RestGameControllerTest.java:138)

org.junit.ComparisonFailure: expected:<[fals]e> but was:<[tru]e>
 com.codenjoy.dojo.web.rest.RestGameControllerTest.shouldExists(RestGameControllerTest.java:139)

java.lang.AssertionError: There are errors
 at org.junit.Assert.fail(Assert.java:88)
 at com.codenjoy.dojo.stuff.SmartAssert.checkResult(SmartAssert.java:118)
 at com.codenjoy.dojo.stuff.SmartAssert.checkResult(SmartAssert.java:132)
 at com.codenjoy.dojo.web.rest.RestGameControllerTest.checkErrors(RestGameControllerTest.java:98)
Важно, что ссылки на сами ассерты в коде в idea кликабельны.
(RestGameControllerTest.java:139)
Так же я добился замены базовым проверятором Idea констукции
expected:<[fals]e> but was:<[tru]e>
На такую, которая будет подсвечивать diff каждого слетевшего ассерта отдельно. В этом мне помог deprecated MultipleFailureException.

Код лежит тут. Лицензия GNU GPL v3. На здоровье.