Pull to refresh

Comments 3

Очень хорошая статья, спасибо. Неявно, Вы подсветили важный недостаток Java Streams, а именно - они накручивают покрытие тестами.

Давайте попробуем переписать Ваш код ниже:

Stream.of(50, 100, 150)
       .findAny()
       .map(TopCitiesCount::findByValue)
       .map(this::getTopCityLocation)
       .map(this::getCurrentConditionByLocation)
       .ifPresent(eventService::sendEvent)

Без Stream'ов (и Optional'ов) будет что-то вроде:

final var values = List.of(50, 100, 150);

if(values.isEmpty()) {
   return
}
final var firstElement = valies.get(0);
if(firstElement == null) {
   return;
}
final var topCity = TopCitiesCount.findByValue(firstElement);
if(topCity == null) {
   return;
}
final var location = topCity.getTopCityLocation();
if(location == null) {
   return
}
final var currentCondition = location.getCurrentConditionByLocation();
if(currentCondition == null) {
  return;
}
eventService.sendEvent(currentCondition);

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

Более того, вот этот код покажет 100% покрытия если просто его вызвать:

Stream.of()
       .findAny()
       .map(TopCitiesCount::findByValue)
       .map(this::getTopCityLocation)
       .map(this::getCurrentConditionByLocation)
       .ifPresent(eventService::sendEvent)

Тогда как мой код выше будет весь "красным". И это важный момент, который я хотел подсветить: .ifPresent(eventService::sendEvent) отмечается как "вызванный" вне зависимости того, вызывали ли Вы sendEvent или нет, так как здесь мы просто передаем ссылку на функцию (а она передается всегда).

Если честно, я не знаю, как исправлять эту ситуацию в Java, так как это приведет к диссертации в каждом классе (по объему). В Kotlin, например, код был бы таким:

val values = listOf(50, 100, 150);
val condition = values
            .firstOrNull()
            ?.findByValue()
            ?.topCityLocation
            ?.currentConditionByLocation
if(condition != null) {
    eventService.sendEvent(condition)
}

или:

val values = listOf(50, 100, 150);
values
    .firstOrNull()
    ?.findByValue()
    ?.topCityLocation
    ?.currentConditionByLocation
    ?.let {
       eventService.sendEvent(it)
    }

И вот тут уже отсутствие вызова eventService.sendEvent(it) будет четко показано в JaCoCo.

Спасибо ко комментарий! Из вашего кода в примере видятся лишние if'ы

//Первый, который всегда будет false
if(values.isEmpty()) {
   return
}
//Второй, который всегда будет false
if(firstElement == null) {
   return;
}

Остальные альтернативные сценарии возможны. Их нужно тестировать отдельно: мокать вызов клиента и выкидывать ошибку. Имеется ввиду сценарии orElseThrow . Почему jacoco считает их покрытыми - большой вопрос :)

И конечно, еще нужен альтернативный сценарий, когда метод eventService.sendEvent не вызовется по бизнес логике. Данные сценарии я специально не отражал в туториале, он и так получился довольно большой(

Вы правы на счет того, что jacoco не отслеживает покрытие ветки .ifPresent(eventService::sendEvent). По идее, здесь по бранчам покрыто 50%, хотя при генерации отчета index.html отображается 100%

Не думаю, что эти if'ы лишние, так как, ну как мне кажется, values должно прийти извне, ну или иначе лучше сразу использовать значение из первого элемента.

В Stream могут быть nullы, но Optional прячет это.

Почему jacoco считает их покрытыми - большой вопрос :)

Нет, с этим всё просто. В коде a.b() мы проверяем, что b() было вызвано (то есть будет физический вызов функции), а в коде a::b мы проверяем, что мы получили указатель на функцию. И Jacoco честно говорит, что указатель был получен.

Sign up to leave a comment.