Python assert - улучшенный самоанализ неудачи? - PullRequest
12 голосов
/ 21 августа 2009

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

$ python -c "assert 6-(3*2)"
[...]
AssertionError

Есть ли лучшая реализация assert в Python, которая более привлекательна? Он не должен вводить дополнительные накладные расходы на выполнение (кроме случаев, когда assert не выполняется) .. и должен отключаться, если используется флаг -O.

Редактировать : Я знаю о втором аргументе assert в виде строки. Я не хочу писать один ... как это закодировано в выражении, которое утверждается. СУХОЙ (не повторяйся).

Ответы [ 7 ]

10 голосов
/ 21 августа 2009

Установите вашу функцию как sys.excepthook - см. документы . Ваша функция, если вторым аргументом является AssertionError, может самоанализом вашего сердца; в частности, с помощью третьего аргумента, трассировки, он может получить кадр и точное место, в котором произошел сбой подтверждения, получить сбойное исключение через источник или байт-код, значение всех соответствующих переменных и т. д. Модуль inspect помогает.

Выполнение этого в полной общности - это довольно трудоемкий процесс, но в зависимости от того, какие ограничения вы готовы принять при написании ваших assert s, его можно существенно облегчить (например, ограничив их только локальными или глобальными переменными). делает интроспекцию легче, чем если бы в нее могли быть вовлечены нелокальные переменные замыкания и т. д.).

7 голосов
/ 21 августа 2009

Как сказал @ Марк Рушаков, nose может оценить ошибочные утверждения. Работает по стандарту assert тоже.

# test_error_reporting.py
def test():
    a,b,c = 6, 2, 3
    assert a - b*c

nosetests 'help:

$ nosetests --help|grep -B2 assert
  -d, --detailed-errors, --failure-detail
                        Add detail to error output by attempting to evaluate
                        failed asserts [NOSE_DETAILED_ERRORS]

Пример:

$ nosetests -d
F
======================================================================
FAIL: test_error_reporting.test
----------------------------------------------------------------------
Traceback (most recent call last):
  File "..snip../site-packages/nose/case.py", line 183, in runTest
    self.test(*self.arg)
  File "..snip../test_error_reporting.py", line 3, in test
    assert a - b*c
AssertionError:
    6,2,3 = 6, 2, 3
>>  assert 6 - 2*3


----------------------------------------------------------------------
Ran 1 test in 0.089s

FAILED (failures=1)
7 голосов
/ 21 августа 2009

Вы можете прикрепить сообщение к assert:

assert 6-(3*2), "always fails"

Сообщение также может быть построено динамически:

assert x != 0, "x is not equal to zero (%d)" % x

См. Оператор assert в документации Python для получения дополнительной информации.

4 голосов
/ 21 августа 2009

Пакет проверки носа применяет самоанализ к утверждениям .

Однако, AFAICT, вы должны позвонить их , чтобы получить самоанализ:

import nose
def test1():
    nose.tools.assert_equal(6, 5+2)

Результаты в

C:\temp\py>C:\Python26\Scripts\nosetests.exe -d test.py
F
======================================================================
FAIL: test.test1
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Python26\lib\site-packages\nose-0.11.1-py2.6.egg\nose\case.py", line
183, in runTest
    self.test(*self.arg)
  File "C:\temp\py\test.py", line 3, in test1
    nose.tools.assert_equal(6, 5+2)
AssertionError: 6 != 7
>>  raise self.failureException, \
          (None or '%r != %r' % (6, 7))

Обратите внимание на ошибку AssertionError. Когда моя линия была просто assert 6 == 5+2, я бы получил:

C:\temp\py>C:\Python26\Scripts\nosetests.exe -d test.py
F
======================================================================
FAIL: test.test1
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Python26\lib\site-packages\nose-0.11.1-py2.6.egg\nose\case.py", line
183, in runTest
    self.test(*self.arg)
  File "C:\temp\py\test.py", line 2, in test1
    assert 6 == 5 + 2
AssertionError:
>>  assert 6 == 5 + 2

Кроме того, я не уверен, что их утверждения пропущены с помощью -O, но это будет очень быстрая проверка.

0 голосов
/ 08 марта 2016

Я кодировал замену для sys.excepthook (которая вызывается для любого необработанного исключения), которая немного более причудлива, чем стандартная. Он проанализирует строку, где произошло исключение, и напечатает все переменные, на которые ссылается эта строка (он не напечатает все локальные переменные, потому что это может быть слишком много шума - также, возможно, важный переменный является глобальным или около того). *

Я назвал это py_better_exchook (идеальное имя), и это здесь .

Файл примера:

a = 6

def test():
    unrelated_var = 43
    b,c = 2, 3
    assert a - b*c

import better_exchook
better_exchook.install()

test()

Выход:

$ python test_error_reporting.py 
EXCEPTION
Traceback (most recent call last):
  File "test_error_reporting.py", line 12, in <module>
    line: test()
    locals:
      test = <local> <function test at 0x7fd91b1a05f0>
  File "test_error_reporting.py", line 7, in test
    line: assert a - b*c
    locals:
      a = <global> 6
      b = <local> 2
      c = <local> 3
AssertionError

Есть несколько других альтернатив:

0 голосов
/ 22 августа 2009

Похоже, что вы действительно хотите сделать, это установить точку останова отладчика непосредственно перед assert и проверять в своем любимом отладчике столько, сколько хотите.

0 голосов
/ 21 августа 2009

Добавьте сообщение к вашему утверждению, которое будет отображаться при сбое подтверждения:

$ python -c "assert 6-(3*2), '6-(3*2)'"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AssertionError: 6-(3*2)

Единственный способ представить это автоматически - это сохранить утверждение в вызове процедуры, а затем проверить стек, чтобы получить исходный код для этой строки. К сожалению, дополнительный вызов привнесет в тест служебную информацию и не будет отключен с помощью -O.

...