web-dev-qa-db-ja.com

Python関数が例外をスローすることをどうのようにテストしますか?

関数が予期される例外をスローしない場合にのみ失敗するunittestをどのように書くのでしょうか。

617
Daryl Spitzer

Unittestモジュールから TestCase.assertRaises (またはTestCase.failUnlessRaises)を使用します。例えば:

import mymod

class MyTestCase(unittest.TestCase):
    def test1(self):
        self.assertRaises(SomeCoolException, mymod.myfunc)
536
Moe

Python 2.7以降では、スローされた実際のExceptionオブジェクトを把握するためにコンテキストマネージャを使うことができます。

import unittest

def broken_function():
    raise Exception('This is broken')

class MyTestCase(unittest.TestCase):
    def test(self):
        with self.assertRaises(Exception) as context:
            broken_function()

        self.assertTrue('This is broken' in context.exception)

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

http://docs.python.org/dev/library/unittest.html#unittest.TestCase.assertRaises


Python 3.5 では、context.exceptionstrでラップする必要があります。それ以外の場合はTypeErrorが返されます。

self.assertTrue('This is broken' in str(context.exception))
382
Art

前の回答のコードは、次のように単純化できます。

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction)

もしafunctionが引数を取るのなら、単に次のようにassertRaisesに渡してください。

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction, arg1, arg2)
256
Daryl Spitzer

Python関数が例外をスローすることをどのようにテストしますか?

関数が予期される例外をスローしない場合にのみ失敗するテストをどのように書くのでしょうか。

短い答え:

コンテキストマネージャとしてself.assertRaisesメソッドを使用します。

    def test_1_cannot_add_int_and_str(self):
        with self.assertRaises(TypeError):
            1 + '1'

デモンストレーション

ベストプラクティスのアプローチは、Pythonシェルで簡単に実証できます。

unittestライブラリ

Python 2.7または3の場合:

import unittest

Python 2.6では、 unittest2 と呼ばれる2.7のunittestライブラリのバックポートをインストールすることができ、それをunittestとしてエイリアスすることができます。

import unittest2 as unittest

テスト例

それでは、次のPythonの型保証のテストをPythonシェルに貼り付けてください。

class MyTestCase(unittest.TestCase):
    def test_1_cannot_add_int_and_str(self):
        with self.assertRaises(TypeError):
            1 + '1'
    def test_2_cannot_add_int_and_str(self):
        import operator
        self.assertRaises(TypeError, operator.add, 1, '1')

テストする人はassertRaisesをコンテキストマネージャとして使います。これはエラーが記録されている間、エラーが適切にキャッチされ、クリーンアップされることを保証します。

私たちはそれを書くこともできます なしで コンテキストマネージャ、テスト2を見てください。最初の引数はあなたが上げる予定のエラータイプ、2番目の引数はテストしている関数、そして残りのargsとキーワードargsはその関数に渡されるでしょう。

コンテキストマネージャを使用するだけの方がはるかに簡単で、読みやすく、保守しやすいと思います。

テストを実行する

テストを実行するには

unittest.main(exit=False)

Python 2.6では、おそらく 以下が必要です

unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))

そしてあなたの端末は以下を出力するはずです:

..
----------------------------------------------------------------------
Ran 2 tests in 0.007s

OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>

そして予想通り、1'1'を追加しようとするとTypeErrorになります。


より詳細な出力を得るには、これを試してください。

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
111
Aaron Hall

あなたのコードはこのパターンに従う必要があります(これはunittestのモジュールスタイルのテストです):

def test_afunction_throws_exception(self):
    try:
        afunction()
    except ExpectedException:
        pass
    except Exception as e:
       self.fail('Unexpected exception raised:', e)
    else:
       self.fail('ExpectedException not raised')

Python <2.7では、この構成は期待される例外の特定の値をチェックするのに役立ちます。 unittest関数assertRaisesは、例外が発生したかどうかだけをチェックします。

40
Daryl Spitzer

から: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/

最初に、これはdum_function.pyファイル内の対応する(まだdum:p)関数です:

def square_value(a):
   """
   Returns the square value of a.
   """
   try:
       out = a*a
   except TypeError:
       raise TypeError("Input should be a string:")

   return out

これは実行されるべきテストです(このテストだけが挿入されています):

import dum_function as df # import function module
import unittest
class Test(unittest.TestCase):
   """
      The class inherits from unittest
      """
   def setUp(self):
       """
       This method is called before each test
       """
       self.false_int = "A"

   def tearDown(self):
       """
       This method is called after each test
       """
       pass
      #---
         ## TESTS
   def test_square_value(self):
       # assertRaises(excClass, callableObj) prototype
       self.assertRaises(TypeError, df.square_value(self.false_int))

   if __== "__main__":
       unittest.main()

これで機能をテストする準備が整いました。テストを実行しようとすると、次のようになります。

======================================================================
ERROR: test_square_value (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_dum_function.py", line 22, in test_square_value
    self.assertRaises(TypeError, df.square_value(self.false_int))
  File "/home/jlengrand/Desktop/function.py", line 8, in square_value
    raise TypeError("Input should be a string:")
TypeError: Input should be a string:

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)

TypeErrorは実際に発生し、テスト失敗を生成します。問題は、これがまさに私たちが望んでいた動作だということです。

このエラーを回避するには、テスト呼び出しでlambdaを使用して関数を実行します。

self.assertRaises(TypeError, lambda: df.square_value(self.false_int))

最終的な出力:

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

パーフェクト!

...そして私にとっても完璧です!

Thansk氏Julien Lengrand-Lambert氏

14
macm

例外が発生したかどうかを確認するためにあなた自身のcontextmanagerを構築することができます。

import contextlib

@contextlib.contextmanager
def raises(exception):
    try:
        yield 
    except exception as e:
        assert True
    else:
        assert False

そしてraisesを次のように使うことができます。

with raises(Exception):
    print "Hola"  # Calls assert False

with raises(Exception):
    raise Exception  # Calls assert True

pytestを使用している場合、これは既に実装されています。あなたはpytest.raises(Exception)を行うことができます:

例:

def test_div_zero():
    with pytest.raises(ZeroDivisionError):
        1/0

そしてその結果:

pigueiras@pigueiras$ py.test
================= test session starts =================
platform linux2 -- Python 2.6.6 -- py-1.4.20 -- pytest-2.5.2 -- /usr/bin/python
collected 1 items 

tests/test_div_zero.py:6: test_div_zero PASSED
10
Pigueiras

私は自分の関数を同時に文書化してテストするという事実が好きなので、ほぼどこでも doctest [1]を使います。

このコードを見てください。

def throw_up(something, gowrong=False):
    """
    >>> throw_up('Fish n Chips')
    Traceback (most recent call last):
    ...
    Exception: Fish n Chips

    >>> throw_up('Fish n Chips', gowrong=True)
    'I feel fine!'
    """
    if gowrong:
        return "I feel fine!"
    raise Exception(something)

if __== '__main__':
    import doctest
    doctest.testmod()

この例をモジュールに入れてコマンドラインから実行すると、両方のテストケースが評価され、チェックされます。

[1] Pythonのドキュメント:23.2 doctest - 対話的なPythonのテスト例

9
pi.

unittestモジュールの assertRaises メソッドを見てください。

9
Greg Hewgill

Mockライブラリ がassertRaisesWithMessage()メソッドを(そのunittest.TestCaseサブクラスに)提供していることを発見しました。

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)
6
Daryl Spitzer

あなたはunittestモジュールからassertRaisesを使うことができます

import unittest

class TestClass():
  def raises_exception(self):
    raise Exception("test")

class MyTestCase(unittest.TestCase):
  def test_if_method_raises_correct_exception(self):
    test_class = TestClass()
    # note that you dont use () when passing the method to assertRaises
    self.assertRaises(Exception, test_class.raises_exception)
2
Bruno Carvalho

ここには多くの答えがあります。このコードは、例外を作成する方法、メソッドでその例外を使用する方法、最後に、ユニットテストで正しい例外が発生することを確認する方法を示しています。

import unittest

class DeviceException(Exception):
    def __init__(self, msg, code):
        self.msg = msg
        self.code = code
    def __str__(self):
        return repr("Error {}: {}".format(self.code, self.msg))

class MyDevice(object):
    def __init__(self):
        self.name = 'DefaultName'

    def setParameter(self, param, value):
        if isinstance(value, str):
            setattr(self, param , value)
        else:
            raise DeviceException('Incorrect type of argument passed. Name expects a string', 100001)

    def getParameter(self, param):
        return getattr(self, param)

class TestMyDevice(unittest.TestCase):

    def setUp(self):
        self.dev1 = MyDevice()

    def tearDown(self):
        del self.dev1

    def test_name(self):
        """ Test for valid input for name parameter """

        self.dev1.setParameter('name', 'MyDevice')
        name = self.dev1.getParameter('name')
        self.assertEqual(name, 'MyDevice')

    def test_invalid_name(self):
        """ Test to check if error is raised if invalid type of input is provided """

        self.assertRaises(DeviceException, self.dev1.setParameter, 'name', 1234)

    def test_exception_message(self):
        """ Test to check if correct exception message and code is raised when incorrect value is passed """

        with self.assertRaises(DeviceException) as cm:
            self.dev1.setParameter('name', 1234)
        self.assertEqual(cm.exception.msg, 'Incorrect type of argument passed. Name expects a string', 'mismatch in expected error message')
        self.assertEqual(cm.exception.code, 100001, 'mismatch in expected error code')


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