Как мы должны проверять исключения с носом? - PullRequest
31 голосов
/ 18 октября 2011

Я проверяю исключения с носом.Вот пример:

def testDeleteUserUserNotFound(self):
    "Test exception is raised when trying to delete non-existent users"
    try:
        self.client.deleteUser('10000001-0000-0000-1000-100000000000')
        # make nose fail here
    except UserNotFoundException:
        assert True

Утверждение выполняется, если возбуждается исключение, но если не возникает исключение, оно не будет выполнено.

Могу ли я надеть что-либопрокомментированная строка выше, чтобы, если не было исключений, нос сообщал об ошибке?

Ответы [ 6 ]

46 голосов
/ 23 ноября 2011

nose предоставляет инструменты для тестирования исключений (как это делает unittest). Попробуйте этот пример (и прочитайте о других инструментах в Nose Testing Tools

from nose.tools import *

l = []
d = dict()

@raises(Exception)
def test_Exception1():
    '''this test should pass'''
    l.pop()

@raises(KeyError)
def test_Exception2():
    '''this test should pass'''
    d[1]

@raises(KeyError)
def test_Exception3():
    '''this test should fail (IndexError raised but KeyError was expected)'''
    l.pop()

def test_Exception4():
    '''this test should fail with KeyError'''
    d[1]

Я бы подумал, что это правильный способ , который вы искали, потому что он позволяет вам быть точным в отношении исключений, которые вы ожидаете или хотите. Таким образом, вы фактически провоцируете ошибку, чтобы увидеть, что она вызывает правильное исключение. И тогда вы позволите носу оценить результат. (Добавьте как можно меньше логики в юнит-тесты!)

9 голосов
/ 16 ноября 2016

Я настоятельно рекомендую использовать assert_raises и assert_raises_regexp из nose.tools, которые дублируют поведение assertRaises и assertRaisesRegexp из unittest.TestCase. Это позволяет использовать те же функциональные возможности, которые предоставляет unittest.TestCase в тестовых наборах, которые фактически не используют класс unittest.TestCase.

Я считаю, что @raises слишком тупой инструмент. Вот код, иллюстрирующий проблему:

from nose.tools import *

something = ["aaa", "bbb"]

def foo(x, source=None):
    if source is None:
        source = something
    return source[x]

# This is fine
@raises(IndexError)
def test1():
    foo(3)

# This is fine. The expected error does not happen because we made 
# a mistake in the test or in the code. The failure indicates we made
# a mistake.
@raises(IndexError)
def test2():
    foo(1)

# This passes for the wrong reasons.
@raises(IndexError)
def test3():
    source = something[2]  # This is the line that raises the exception.
    foo(10, source)  # This is not tested.

# When we use assert_raises, we can isolate the line where we expect
# the failure. This causes an error due to the exception raised in 
# the first line of the function.
def test4():
    source = something[2]
    with assert_raises(IndexError):
        foo(10, source)

test3 проходит, но не потому, что foo вызвал исключение, которое мы ожидали, а потому, что код, который устанавливает данные, которые будут использоваться foo, завершается с ошибкой с тем же исключением. test4 показывает, как можно написать тест, используя assert_raises, чтобы фактически проверить, что мы подразумеваем под тестированием. Проблема в первой строке приведет к тому, что Nose сообщит об ошибке, а затем мы сможем переписать тест, чтобы эта строка позволила нам, наконец, протестировать то, что мы собирались проверить.

@raises не позволяет проверить сообщение, связанное с исключением. Когда я поднимаю ValueError, просто для примера, я обычно хочу поднять его с информативным сообщением. Вот пример:

def bar(arg):
    if arg:  # This is incorrect code.
        raise ValueError("arg should be higher than 3")

    if arg >= 10:
        raise ValueError("arg should be less than 10")

# We don't know which of the possible `raise` statements was reached.
@raises(ValueError)
def test5():
    bar(10)

# Yes, we're getting an exception but with the wrong value: bug found!
def test6():
    with assert_raises_regexp(ValueError, "arg should be less than 10"):
        bar(10)

test5, который использует @raises, пройдет, но пройдет по неправильной причине. test6 выполняет более тонкий тест, который показывает, что поднятый ValueError не тот, который мы хотели.

9 голосов
/ 18 октября 2011
def testDeleteUserUserNotFound(self):
    "Test exception is raised when trying to delete non-existent users"
    try:
        self.client.deleteUser('10000001-0000-0000-1000-100000000000')
        assert False # <---
    except UserNotFoundException:
        assert True

Семантика try / except подразумевает, что поток выполнения покидает блок try при исключении, поэтому assert False не будет выполняться, если возникнет исключение.Кроме того, выполнение не будет повторно входить в блок try после завершения работы блока except, поэтому у вас не должно быть проблем.

     ↓
(statements)
     ↓    exception
   (try) ↚──────────→ (except)
     ↓                   │
(statements) ←───────────┘
     ↓
7 голосов
/ 31 июля 2015

Я не знаю, почему его еще нет, но существует еще один способ:

import unittest

class TestCase(unittest.TestCase):

    def testKeyError(self):
        d = dict()
        with self.assertRaises(KeyError):
            d[1]
3 голосов
/ 14 апреля 2016

Использование assert_raises:

from nose.tools import assert_raises

our_method         = self.client.deleteUser
arg1               = '10000001-0000-0000-1000-100000000000'
expected_exception = UserNotFoundException

assert_raises(expected_exception, our_method, arg1)

Использование try и catch в ваших тестах кажется плохой практикой (в большинстве случаев).

В носу нет специальной документации, потому что это просто оболочка вокруг unittest.TestCase.assertRaises (ref. Как использовать assert_raises носа? )

1 голос
/ 18 октября 2011

Я не знаю, что это за нос, но вы пытались использовать 'else' после предложения кроме? То есть

else:
    assert False
...