Имеет ли смысл измерять условное покрытие для кода Java 8?

19

Мне интересно, не устарели ли измерения покрытия условного кода текущими инструментами для Java после появления Java 8? С Java 8 - х Optionalи Streamчасто мы можем избежать кода ветвь / петлю, что делает его легко получить очень высокое условное покрытие без проверки всех возможных путей выполнения. Давайте сравним старый код Java с кодом Java 8:

До Java 8:

public String getName(User user) {
    if (user != null) {
        if (user.getName() != null) {
            return user.getName();
        }
    }
    return "unknown";
}

В вышеуказанном методе есть 3 возможных пути выполнения. Чтобы получить 100% условного покрытия, нам нужно создать 3 модульных теста.

Java 8:

public String getName(User user) {
    return Optional.ofNullable(user)
                   .map(User::getName)
                   .orElse("unknown");
}

В этом случае ветви скрыты, и нам нужно только 1 тест, чтобы получить 100% охват, и не имеет значения, в каком случае мы будем тестировать. Хотя есть все те же 3 логические ветви, которые должны быть рассмотрены, я считаю. Я думаю, что это делает статистику условного покрытия совершенно ненадежной в наши дни.

Имеет ли смысл измерять условное покрытие для кода Java 8? Существуют ли какие-либо другие инструменты, обнаруживающие проверенный код?

Кароль Левандовски
источник
5
Метрики покрытия никогда не были хорошим способом определить, хорошо ли протестирован ваш код, а просто способом определить, что еще не было проверено. Хороший разработчик продумает различные случаи в своей голове и разработает тесты для всех из них - или, по крайней мере, всего, что она считает важным.
kdgregory
3
Конечно, высокий условный охват не означает, что у нас есть хорошие тесты, но я думаю, что ОГРОМНОЕ преимущество знать, какие пути выполнения раскрыты, и это то, о чем в основном вопрос. Без условного покрытия гораздо сложнее определить непроверенные сценарии. Относительно путей: [user: null], [user: notnull, user.name:null], [user: notnull, user.name:notnull]. Что мне не хватает?
Кароль Левандовски
6
Что такое контракт getName? Похоже, что если usernull, он должен вернуть «unknown». Если userне ноль и user.getName()нуль, он должен вернуть «неизвестно». Если userне ноль и user.getName()не нуль, он должен вернуть это. Таким образом, вы бы протестировали эти три случая, потому что это контракт getName. Вы, кажется, делаете это задом наперед. Вы не хотите видеть ветки и писать тесты в соответствии с ними, вы хотите писать свои тесты в соответствии с вашим контрактом и гарантировать, что контракт выполнен. Это когда у вас есть хорошее освещение.
Винсент Савард
1
Опять же, я не говорю, что покрытие доказывает, что мой код отлично протестирован, но это был ЧРЕЗВЫЧАЙНО ценный инструмент, показывающий мне то, что я не проверял наверняка. Я думаю, что контракты тестирования неотделимы от путей выполнения тестирования (ваш пример исключителен, поскольку в нем задействован неявный языковой механизм). Если вы не проверили путь, значит, вы не полностью протестировали контракт или контракт не полностью определен.
Кароль Левандовски
2
Я повторю свою предыдущую мысль: это всегда так, если вы не ограничиваете себя только базовыми языковыми функциями и никогда не вызываете функцию, которая не была инструментирована. Что означает отсутствие сторонних библиотек и отсутствие использования SDK.
kdgregory

Ответы:

4

Существуют ли инструменты для измерения логических ветвей, которые можно создать в Java 8?

Я не знаю ни о чем. Я попытался запустить код, который вы имеете через JaCoCo (он же EclEmma), просто чтобы быть уверенным, но он показывает 0 ветвей в Optionalверсии. Я не знаю ни одного способа его настройки, чтобы сказать иначе. Если вы сконфигурируете его также для включения файлов JDK, теоретически в нем будут отображаться ветви Optional, но я думаю, что начинать проверку кода JDK было бы глупо. Вы просто должны предположить, что это правильно.

Тем не менее, я думаю, что основная проблема заключается в понимании того, что дополнительные ветви, которые у вас были до Java 8, были, в некотором смысле, искусственно созданными ветвями. То, что они больше не существуют в Java 8, означает, что теперь у вас есть подходящий инструмент для работы (в данном случае, Optional). В коде до Java 8 вам приходилось писать дополнительные модульные тесты, чтобы вы могли быть уверены, что каждая ветвь кода ведет себя приемлемым образом - и это становится немного более важным в разделах кода, которые не являются тривиальными, как User/ getNameпример.

В коде Java 8 вы вместо этого доверяете JDK, что код работает правильно. Таким образом, вы должны обрабатывать эту Optionalстроку так же, как это делают инструменты покрытия кода: 3 строки с 0 ветвями. То, что в приведенном ниже коде есть другие строки и ветви, - это то, на что вы просто не обращали внимания прежде, но существовали каждый раз, когда вы использовали что-то вроде ArrayListили HashMap.

Shaz
источник
2
«То, что они больше не существуют в Java 8 ...» - я не могу с этим согласиться, Java 8 обратно совместима ifи nullпо-прежнему является частью языка ;-) Все еще возможно писать код по-старому и передавать nullпользователь или пользователь с nullименем. Ваши тесты должны просто доказать, что контракт выполняется независимо от того, как метод реализован. Дело в том, что нет никакого инструмента, чтобы сказать вам, полностью ли вы протестировали контракт.
Кароль Левандовски
1
@KarolLewandowski Я думаю, что Shaz говорит, что если вы доверяете тому, как Optional(и связанные методы) работают, вам больше не нужно их проверять. Не так, как вы проверяли if-else: каждый ifбыл потенциальным минным полем. Optionalи подобные функциональные идиомы уже закодированы и гарантированно не сбивают вас с толку, так что по сути есть «ветвь», которая исчезла.
Андрес Ф.
1
@AndresF. Я не думаю, что Кароль предлагает нам проверить Optional. Как он сказал, логически мы все равно должны тестировать, который getName()обрабатывает различные возможные входные данные так, как мы намереваемся, независимо от его реализации. Труднее определить это без помощи инструментов покрытия кода, как это было бы до JDK8.
Майк Партридж
1
@MikePartridge Да, но дело в том, что это не делается через покрытие филиалов. Покрытие ветвлений необходимо при написании, if-elseпотому что каждая из этих конструкций является полностью произвольной. В отличие от этого , Optional, orElse, mapи т.д., все уже протестировали. По сути, ветви «исчезают», когда вы используете более мощные идиомы.
Андрес Ф.