Как найти код, который никогда не выполнялся в cover.py, несмотря на отчет о 100%-м покрытии

Рассмотрим следующий код:

import math

def dumb_sqrt(x):
    result = math.sqrt(x) if x >= 0 else math.sqrt(-x)*j
    return result


def test_dumb_sqrt():
    assert dumb_sqrt(9.) == 3.

Тест можно выполнить так:

$ pip install pytest pytest-cov
$ pytest test_thing.py --cov=test_thing --cov-report=html --cov-branch

В отчете о покрытии будут учтены все линии, покрытые на 100%, даже при включенном покрытии филиалов:

inline

Однако в этом коде есть ошибка, и те из вас, у кого острый глаз, возможно, уже заметили ее. Если он когда-либо попадет в ветку "else", будет исключение:

NameError: global name 'j' is not defined

Ошибку легко исправить: изменить неопределенное имя j на буквальное 1j. Также легко добавить еще один тест, который выявит ошибку: assert dumb_sqrt(-9.) == 3j. Ни то, о чем этот вопрос спрашивает. Я хочу знать, как найти участки кода, которые фактически никогда не выполнялись, несмотря на отчет о 100% покрытии кода.

Использование условных выражений является одним из таких виновников, но везде есть подобные случаи, когда Python может сократить вычисление (другие примеры — x or y, x and y).

Предпочтительно, строка 4 выше может быть окрашена в отчете желтым цветом, подобно тому, как отображалась бы строка «если», если бы в ней изначально не использовалось условное выражение:

long

Поддерживает ли coverage.py такую ​​функцию? Если да, то как вы можете включить «встроенное покрытие ветвей» в своих отчетах по cov? Если нет, существуют ли какие-либо другие подходы для выявления «скрытого» кода, который никогда не выполнялся вашим набором тестов?


person wim    schedule 27.02.2020    source источник
comment
Я не знаю о поддержке converage.py, но тестирование мутаций может охватывать некоторые проблемы, о которых вы думаете. Пакет mutmut реализует тестирование мутаций, что может помочь выявить те сценарии, которых нет в ваших тестах. Надеюсь, поможет   -  person Josh Zwiebel    schedule 28.02.2020
comment
Я знаю, что Нед Бэтчелдер говорил об этом раньше, пытаясь найти ссылку. Проблема в том, что вам придется отслеживать выполнение на уровне байт-кода, а в настоящее время Cover.py отслеживает на уровне логической строки.   -  person Martijn Pieters    schedule 28.02.2020
comment
Да, и Нед активен на SO, я надеюсь, что он появится и расскажет нам, как обстоят дела здесь..   -  person wim    schedule 28.02.2020
comment
Итак, начнем: github.com/nedbat/coveragepy/issues/660   -  person Martijn Pieters    schedule 28.02.2020
comment
Кроме того, nedbatchelder.com/blog/200710/ и nedbatchelder.com/blog/200804/. С трассировкой 'opcode' в Python 3.7 самое большое препятствие устранено.   -  person Martijn Pieters    schedule 28.02.2020


Ответы (2)


Нет, Cover.py не обрабатывает условное ветвление в выражении. Это влияет не только на условное выражение Python, но и на использование and или or:

# pretending, for the sake of illustration, that x will never be 0
result = x >= 0 and math.sqrt(x) or math.sqrt(-x)*j

Нед Бэтчелдер, сопровождающий coverage.py, называет это скрытым условным выражением в статья 2007 года, посвященная этому и другим случаям, с которыми coverage.py не справляется.

Эта проблема распространяется и на операторы if! Возьмем, например:

if condition_a and (condition_b or condtion_c):
    do_foo()
else:
    do_bar()

Если condition_b всегда истинно, когда condition_a истинно, вы никогда не найдете опечатку в condtion_c, если вы полагаетесь исключительно на converage.py, потому что нет поддержки условного покрытия (не говоря уже о более сложных концепциях, таких как измененное покрытие условий/решений и множественное покрытие условий.

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

Не то, чтобы это помешало другому проекту, instrumental предлагать покрытие условий/решений в любом случае . В этом проекте использовалась переписывание AST и ловушка импорта для добавления дополнительного байт-кода, который позволял отслеживать результаты отдельных условий и, таким образом, давал вам обзор «таблицы истинности» выражений. У этого подхода есть огромный недостаток: он очень хрупок и часто требует обновления для новых выпусков Python. В результате проект сломался с Python 3.4 и не был исправлен.

Однако в Python 3.7 добавлена ​​поддержка трассировки на уровне кода операции., что позволяет трассировщику анализировать влияние каждого отдельного байт-кода, не прибегая к взлому AST. А поскольку Coverage.py 5.0 достиг стабильного состояния, похоже, что проект рассматривает возможность добавления поддержки покрытие условий с возможными спонсорами для поддержки разработки.

Итак, ваши варианты прямо сейчас:

  • Запустите свой код на Python 3.3 и используйте инструментальные
  • Инструмент восстановления для работы в более поздних версиях Python
  • Подождите, пока Cover.py добавит покрытие условий
  • Помогите написать функцию для cover.py
  • Создайте свою собственную версию инструментального подхода, используя режим трассировки Python 3.7 или новее 'opcode'.
person Martijn Pieters    schedule 28.02.2020

Не используйте res1, если cond else res2 - это должно рассматриваться как один оператор. Если вы напишете это как if/else, я думаю, Coverage.py сделает свою работу лучше.

И рассмотрите возможность использования чего-то вроде pylint или, по крайней мере, pyflakes. Я считаю, что они автоматически обнаружили бы проблему.

person dstromberg    schedule 27.02.2020
comment
Никакие pyflakes и pylint не могут обнаружить этот случай. ОП понимает, что res1 if cond else res2 уже является отдельным выражением (а не оператором), в этом суть. - person Martijn Pieters; 28.02.2020