Як показати повідомлення про помилки, виявлені assertRaises () в unittest в Python2.7?


84

Для того, щоб переконатись, що повідомлення про помилки з мого модуля є інформативними, я хотів би бачити всі повідомлення про помилки, виявлені assertRaises (). Сьогодні я роблю це для кожного assertRaises (), але оскільки їх багато в тестовому коді, це стає дуже нудно.

Як я можу надрукувати повідомлення про помилки для всіх assertRaises ()? Я вивчив документацію на http://docs.python.org/library/unittest.html, не з'ясувавши, як її вирішити. Чи можу я якось виправити мавпу методом assertRaises ()? Я вважаю за краще не змінювати всі рядки assertRaises () у тестовому коді, оскільки найчастіше використовую тестовий код стандартним способом.

Думаю, це питання пов’язане з Python unittest: як перевірити аргумент у винятках?

Ось як я це роблю сьогодні. Наприклад:

#!/usr/bin/env python

def fail():
    raise ValueError('Misspellled errrorr messageee')

І тестовий код:

#!/usr/bin/env python
import unittest
import failure   

class TestFailureModule(unittest.TestCase):

    def testFail(self):
        self.assertRaises(ValueError, failure.fail)

if __name__ == '__main__':
    unittest.main()  

Щоб перевірити повідомлення про помилку, я просто змінюю тип помилки в assertRaises () на, наприклад, IOError. Тоді я бачу повідомлення про помилку:

 E
======================================================================
ERROR: testFail (__main__.TestFailureModule)
----------------------------------------------------------------------
Traceback (most recent call last):
 File "test_failure.py", line 8, in testFail
   self.assertRaises(IOError, failure.fail)
  File "/usr/lib/python2.7/unittest/case.py", line 471, in assertRaises
    callableObj(*args, **kwargs)
 File "/home/jonas/Skrivbord/failure.py", line 4, in fail
    raise ValueError('Misspellled errrorr messageee')
ValueError: Misspellled errrorr messageee

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

Будь-які пропозиції? / Джонас

РЕДАГУВАТИ:

З натяками Роберта Росні мені вдалося вирішити проблему. Він в основному не призначений для орфографічних помилок, а для того, щоб переконатися, що повідомлення про помилки дійсно значущі для користувача модуля. Нормальна функціональність unittest (саме так я його використовую більшу частину часу) досягається встановленням SHOW_ERROR_MESSAGES = False.

Я просто перевизначаю метод assertRaises (), як показано нижче. Це працює як шарм!

SHOW_ERROR_MESSAGES = True

class NonexistantError(Exception):
    pass

class ExtendedTestCase(unittest.TestCase):
    def assertRaises(self, excClass, callableObj, *args, **kwargs):
        if SHOW_ERROR_MESSAGES:
            excClass = NonexistantError
        try:
            unittest.TestCase.assertRaises(self, excClass, callableObj, *args, **kwargs)
        except:
            print '\n    ' + repr(sys.exc_info()[1]) 

Частка отриманого результату:

testNotIntegerInput (__main__.TestCheckRegisteraddress) ... 
    TypeError('The registeraddress must be an integer. Given: 1.0',)

    TypeError("The registeraddress must be an integer. Given: '1'",)

    TypeError('The registeraddress must be an integer. Given: [1]',)

    TypeError('The registeraddress must be an integer. Given: None',)
ok
testCorrectNumberOfBytes (__main__.TestCheckResponseNumberOfBytes) ... ok
testInconsistentLimits (__main__.TestCheckNumerical) ... 
    ValueError('The maxvalue must not be smaller than minvalue. Given: 45 and 47, respectively.',)

    ValueError('The maxvalue must not be smaller than minvalue. Given: 45.0 and 47.0, respectively.',)
ok
testWrongValues (__main__.TestCheckRegisteraddress) ... 
    ValueError('The registeraddress is too small: -1, but minimum value is 0.',)

    ValueError('The registeraddress is too large: 65536, but maximum value is 65535.',)
ok
testTooShortString (__main__.TestCheckResponseWriteData) ... 
    ValueError("The payload is too short: 2, but minimum value is 4. Given: '\\x00X'",)

    ValueError("The payload is too short: 0, but minimum value is 4. Given: ''",)

    ValueError("The writedata is too short: 1, but minimum value is 2. Given: 'X'",)

    ValueError("The writedata is too short: 0, but minimum value is 2. Given: ''",)
ok
testKnownValues (__main__.TestCreateBitPattern) ... ok
testNotIntegerInput (__main__.TestCheckSlaveaddress) ... 
    TypeError('The slaveaddress must be an integer. Given: 1.0',)

    TypeError("The slaveaddress must be an integer. Given: '1'",)

    TypeError('The slaveaddress must be an integer. Given: [1]',)

    TypeError('The slaveaddress must be an integer. Given: None',)
ok

4
Навіщо продовжувати використовувати assertRaises, якщо вам потрібно перевірити аргументи? Чому б просто не зловити виняток і не вивчити його за допомогою try та except?
S.Lott

Відповіді:


56

Нестандартно unittestцього не робить. Якщо це щось, що ви хочете робити часто, ви можете спробувати щось подібне:

class ExtendedTestCase(unittest.TestCase):

  def assertRaisesWithMessage(self, msg, func, *args, **kwargs):
    try:
      func(*args, **kwargs)
      self.assertFail()
    except Exception as inst:
      self.assertEqual(inst.message, msg)

Виведіть свої модульні тестові класи ExtendedTestCaseзамість unittest.TestCase.

Але насправді, якщо ви просто стурбовані помилками з помилками та достатньо стурбовані тим, щоб навколо них створити тестові кейси, ви не повинні вбудовувати повідомлення як рядкові літерали. Ви повинні робити з ними те, що ви робите з будь-якими іншими важливими рядками: визначаючи їх як константи в модулі, який ви імпортуєте, і що хтось відповідає за коректуру. Розробник, який неправильно пише слова у своєму коді, також буде помиляти їх у своїх тестових випадках.


22
+1 для "Розробник, який неправильно пише слова у своєму коді, також буде неправильно писати їх у своїх тестових випадках".
Johnsyweb

18
Для мене набагато сильніше, коли ви тестуєте, щоб побачити, що виникає певна помилка, але тест може "пройти" через непередбачені побічні ефекти. Наприклад, помилка, яку ви очікували, не піднімалася, але той самий тип помилки виникає в іншому місці, таким чином задовольняючи тест Тест пройшов, код помилковий. Така ж угода щодо помилок, які підкласують помилку, яку ви шукаєте - якщо ваш тест занадто загальний, ви в кінцевому підсумку ловите те, чого не очікуєте.
Mark Simpson

1
Вам слід використовувати inst.args[0] замість того, inst.message щоб запускати цей код як на Python 2, так і на Python 3
oblalex

3
Це неправда, нестандартно unittest це робить.
Джонатан Хартлі,

Приємний приклад підкласифікації. Але мені важко повірити, що це необхідно або вигідно, оскільки unittestможливості "нестандартної коробки" досить великі.
Том Рассел

126

Одного разу я віддав перевагу найкращій відповіді, даній вище @Robert Rossney. В даний час я вважаю за краще використовувати assertRaises як менеджер контексту (нова можливість в unittest2) так:

with self.assertRaises(TypeError) as cm:
    failure.fail()
self.assertEqual(
    'The registeraddress must be an integer. Given: 1.0',
    str(cm.exception)
)

2
Примітка. assertRaises може використовуватися як менеджер контексту з 'unittest' у Python 2.7. Можливості модулів backtest unittest2 для попередніх версій Python. docs.python.org/2/library/…
пувло

Що робити, якщо код зазнає невдачі у частині "with" .... у моєму випадку ця частина не вдається ... тому я хочу показати повідомлення .. як ми можемо зробити для інших простих тверджень, наприклад .exception.faultCode, 101001, 'Код несправності не відповідає очікуваному коду помилки% d'% 101001)
Arindam Roychowdhury

@arindamroychowdhury, вибачте, але я досить довго не кодував жодного Python, тому не знаю відповіді на ваше запитання. Удачі. Можливо, хтось із інших людей тут міг би відповісти на ваше запитання. Удачі.
mkelley33

Я використовую Python 2.7. Мені довелося замінити str (cm.exception) на параметр cm.exception.parameter.
Чак

62

Ви шукаєте assertRaisesRegex , який доступний з Python 3.2. З документів:

self.assertRaisesRegex(ValueError, "invalid literal for.*XYZ'$",
                       int, 'XYZ')

або:

with self.assertRaisesRegex(ValueError, 'literal'):
    int('XYZ')

PS: якщо ви використовуєте Python 2.7, то правильна назва методу - assertRaisesRegexp.


так, але якщо очікувана помилка не зростає, ви ніколи не побачите повідомлення / не зможете змінити стандартне. Надзвичайно незручно при тестуванні деяких параметрів у циклі - ви не знаєте, через який параметр проходить функція без очікуваної помилки.
Mesco

2
На python 3.6 це говорить DeprecationWarning: Please use assertRaisesRegex insteadпри використанніwith self.assertRaisesRegexp( RuntimeError, '...regex...' )
користувач

33

Якщо ви хочете, щоб повідомлення про помилку точно відповідало чомусь:

with self.assertRaises(ValueError) as error:
  do_something()
self.assertEqual(error.exception.message, 'error message')

7
Мені довелося використовувати str(error.exception)замість того error.exception.message, щоб error.exceptionне мати messageатрибута у моєму випадку.
Rik Schoonbeek

6

mkelley33 дає приємну відповідь, але такий підхід може бути виявлений як проблему за допомогою деяких інструментів аналізу коду, таких як Codacy . Проблема в тому, що він не знає, що assertRaisesможна використовувати як менеджер контексту, і повідомляє, що не всі аргументи передаються assertRaises методу .

Отже, я хотів би покращити відповідь Роберса Росні:

class TestCaseMixin(object):

    def assertRaisesWithMessage(self, exception_type, message, func, *args, **kwargs):
        try:
            func(*args, **kwargs)
        except exception_type as e:
            self.assertEqual(e.args[0], message)
        else:
            self.fail('"{0}" was expected to throw "{1}" exception'
                      .format(func.__name__, exception_type.__name__))

Основні відмінності:

  1. Ми перевіряємо тип виключення.
  2. Ми можемо запустити цей код як на Python 2, так і на Python 3 (ми викликаємо, e.args[0]оскільки помилки в Py3 не мають messageатрибута).

Це дуже елегантне рішення IMO. Якщо вам не подобається e.args[0], ви також можете зателефонувати str(e).
Laryx Decidua
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.