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


回答:


679

unittestモジュールからTestCase.assertRaises(またはTestCase.failUnlessRaises)を使用します。次に例を示します。

import mymod

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

9
これの反対を行う方法はありますか?関数が例外をスローした場合にのみ失敗するように?
BUInvent 2017

67
引数を渡すmyfuncには、それらをassertRaises呼び出しの引数として追加する必要があることに注意してください。ダリル・スピッツァーの答えを見てください。
cbron

1
複数の例外タイプを許可する方法はありますか?
Dinesh 2018年

1
Pythonの組み込み例外を使用して、アサーションをすばやくテストできます。たとえば、上記の@Moeの回答を使用しますself.assertRaises(TypeError, mymod.myfunc)。組み込みの例外の完全なリストは、次の場所にあり
Raymond Wachaga

3
クラスのコンストラクタをテストするために同じこと:self.assertRaises(SomeCoolException, Constructor, arg1)
tschumann

476

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 __name__ == '__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))

6
私はPython 2.7.10を使用していますが、上記は機能しません。context.exceptionメッセージを出しません。タイプです。
LateCoder 2016

6
また、Python 2.7(少なくとも私の2.7.6では)を使用import unittest2する場合はstr()、を使用する必要がありますself.assertTrue('This is broken' in str(context.exception))
Sohail Si 2017

4
2つのこと:1. assertTrueの代わりにassertInを使用できます。例:self.assertIn( 'This is broken'、context.exception)2.私の場合、2.7.10を使用すると、context.exceptionは文字の配列のように見えます。strを使用しても機能しません。私はこれをやった: '' .join(context.exception)したがって、まとめて:self.assertIn( 'This is broken'、 '' .join(context.exception))
blockcipher

1
メソッドが例外のトレースバックでテストコンソールを詰まらせるのは正常ですか?これを防ぐにはどうすればよいですか?
MadPhysicist 2017年

1
後で私は例外のstrとしてメッセージを取得する別の方法を見つけました、それはerr = context.exception.messageです。そして、テストを行うために、self.assertEqual(err、 'This is broken')も使用できます。
-zhihong

326

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

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

そして、関数が引数を取る場合は、次のようにそれらをassertRaisesに渡します。

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

17
2つ目は、引数が渡されたときに何をすべきかについては、本当に役に立ちました。
Sabyasachi

私は使用してい2.7.15ます。afunctionin self.assertRaises(ExpectedException, afunction, arg1, arg2)がクラス初期化子の場合self、最初の引数として渡す必要があります。たとえば、 self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Minh Tran

128

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では、unittest2unittestと呼ばれる2.7のライブラリのバックポートをインストールし、それを次のようにエイリアスすることができます。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')

1つの用途をテストする assertRaisesコンテキストマネージャーとしてします。これにより、記録中にエラーが適切にキャッチおよびクリーンアップされることが保証されます。

なしで書くこともできますコンテキストマネージャます。テスト2を参照してください。最初の引数は、発生が予想されるエラーの種類、2番目の引数はテストする関数、残りの引数とキーワード引数はその関数に渡されます。

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

テストを実行する

テストを実行するには:

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>

そして、予想どおり、a 1'1'結果をに追加しようとしていることがわかりTypeErrorます。


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

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

56

コードは次のパターンに従う必要があります(これはユニットテストモジュールスタイルのテストです)。

def test_afunction_throws_exception(self):
    try:
        afunction()
    except ExpectedException:
        pass
    except Exception:
       self.fail('unexpected exception raised')
    else:
       self.fail('ExpectedException not raised')

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


3
メソッドself.failは引数を1つだけ取ります
mdob

3
これは、関数が例外をスローするかどうかをテストするのは非常に複雑に見えます。その例外以外の例外はテストをエラーにし、例外をスローしないとテストが失敗するため、唯一の違いは、別の例外を取得した場合assertRaises、FAILではなくERRORを取得することです。
2015年

23

から: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 __name__ == "__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は、actullayが発生し、テストエラーを生成します。問題は、これがまさに私たちが望んだ動作であるということです。

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

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

最終的な出力:

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

OK

パーフェクト!

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

たくさんありがとうジュリアン・レングラント・ランバート氏


このテストアサートは実際には検知を返します。これは、「assertRaises」内のラムダが、テストされた関数ではなく型エラーを発生させる単位であるために発生します。


10
注:ラムダは必要ありません。この行self.assertRaises(TypeError, df.square_value(self.false_int))はメソッドを呼び出し、結果を返します。何が欲しいの方法および任意の引数を渡すとunittestのは、それを呼び出すことができるようにすることです:self.assertRaises(TypeError, df.square_value, self.false_int)
ローマKutlak

どうもありがとう。完全に動作します
Chandan Kumar

14

独自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

1
unittestモジュールを必要としない回答を投稿していただきありがとうございます。
シャーウッドキャロウェイ

10

私はほとんどすべての場所で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 __name__ == '__main__':
    import doctest
    doctest.testmod()

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

[1] Pythonドキュメント:23.2 doctest-インタラクティブなPythonの例をテストする


4
私はdoctestが大好きですが、それはunittestを置き換えるのではなく、補足するものです。
TimothyAWiseman

2
doctestが自動リファクタリングでうまく機能する可能性は低いですか?Python用に設計されたリファクタリングツールは、docstringを認識する必要があると思います。誰もが彼らの経験からコメントできますか?
kdbanman 2015年

6

MockライブラリーがassertRaisesWithMessage()メソッドを(そのunittest.TestCaseサブクラスで)提供していることを発見しました。これは、予期される例外が発生するだけでなく、予期されるメッセージで発生することもチェックします。

from testcase import TestCase

import mymod

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

残念ながら、それはもう提供しません。.しかし、@ Artの上記の回答(stackoverflow.com/a/3166985/1504046)は同じ結果になります
Rmatt

6

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

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 __name__ == '__main__':
    unittest.main()

3

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)

-5

すべての答えは完璧ですが、ユニットテストフレームワークに依存せず、テストクラスを記述する必要なく、関数が例外を発生させるかどうかをテストする方法を探していました。

私は次のように書いてしまいました:

def assert_error(e, x):
    try:
        e(x)
    except:
        return
    raise AssertionError()

def failing_function(x):
    raise ValueError()

def dummy_function(x):
    return x

if __name__=="__main__":
    assert_error(failing_function, 0)
    assert_error(dummy_function, 0)

そしてそれは正しい行で失敗します:

Traceback (most recent call last):
  File "assert_error.py", line 16, in <module>
    assert_error(dummy_function, 0)
  File "assert_error.py", line 6, in assert_error
    raise AssertionError()
AssertionError
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.