Pythonユニットテスト-assertRaisesの反対?


374

特定の状況で例外が発生しないことを確認するテストを記述したいと思います。

例外発生したかどうかをテストするのは簡単です...

sInvalidPath=AlwaysSuppliesAnInvalidPath()
self.assertRaises(PathIsNotAValidOne, MyObject, sInvalidPath) 

...しかし、どうすればそのができますか。

このような私は私が何をしているのか...

sValidPath=AlwaysSuppliesAValidPath()
self.assertNotRaises(PathIsNotAValidOne, MyObject, sValidPath) 

6
テストで機能するはずのことはいつでも簡単に実行できます。エラーが発生した場合は、それが表示されます(失敗ではなくエラーとしてカウントされます)。もちろん、定義されたタイプのエラーではなく、エラーが発生しないことを前提としています。それ以外は、自分で書く必要があると思います。
トーマスK


実際にassertNotRaisesassertRaises、約30行程度のコード行でコード/動作の90%を共有するメソッドを実装できることがわかります。詳細については、以下の私の回答を参照してください。
TEL

2つの関数を比較しhypothesisて、すべての種類の入力に対して同じ出力が生成されることを確認しながら、元の関数が例外を発生させるケースを無視できるようにしたいのです。 assume(func(a))出力はあいまいな真理値を持つ配列になる可能性があるため、機能しません。したがって、関数を呼び出して、True失敗しないかどうかを取得したいだけです。 assume(func(a) is not None)私が推測する作品
エンドリス

回答:


394
def run_test(self):
    try:
        myFunc()
    except ExceptionType:
        self.fail("myFunc() raised ExceptionType unexpectedly!")

32
@hiwaylon-いいえ、これは実際には正しい解決策です。user9876によって提案された解決策は、概念的には欠陥がある:発言の非調達のためのあなたのテスト場合はValueError、しかし、ValueError代わりに上げては、あなたのテストは、障害状態ではなく、エラーのいずれかで終了する必要があります。一方、同じコードを実行しているときにをKeyError発生させると、エラーではなくエラーになります。Pythonでは-他のいくつかの言語とは異なり-例外が制御フローに日常的に使用されていexcept <ExceptionName>ます。これが実際に構文がある理由です。その点で、user9876のソリューションは単に間違っています。
Mac

@mac-これも正しい解決策ですか?stackoverflow.com/a/4711722/6648326
MasterJoe2

これは、テストに対して100%未満のカバレッジ(例外は発生しません)を示すという残念な影響があります。
Shay、

3
@ Shay、IMOあなたは常にカバレッジレポートからテストファイル自体を除外する必要があります(ほとんどの場合、定義に従って100%実行されるため、レポートを人為的に増やします)
元のBBQソース

@ original-bbq-sauceを使用すると、意図せずにテストをスキップしてしまう可能性はありません。たとえば、テスト名の誤植(ttst_function)、pycharmの実行構成の誤りなど?
Shay

67

こんにちは-特定の状況で例外が発生しないことを確認するテストを記述したいと思います。

これがデフォルトの仮定です。例外は発生しません。

他に何も言わない場合は、すべてのテストでそれが想定されます。

そのためのアサーションを実際に記述する必要はありません。


7
@IndradhanushGuptaまあ受け入れられた答えは、テストをこれよりもpythonicにします。明示的は暗黙的よりも優れています。
0xc0de

17
他のコメンターはこの回答が間違っている理由を指摘していませんが、user9876の回答が間違っているのと同じ理由です。失敗とエラーはテストコードでは異なります。アサートしないテスト中に関数例外をスローする場合、テストフレームワークはアサートしないことに失敗するのではなく、エラーとして処理します
coredumperror 2016年

@CoreDumpError失敗とエラーの違いは理解していますが、これにより、すべてのテストをtry / exception構造で囲む必要がなくなりますか?または、特定の条件下で例外を明示的に発生させるテストに対してのみ行うことをお勧めします(つまり、基本的には例外が予期されることを意味します)。
federicojasson

4
@federicojasson 2番目の文であなた自身の質問にかなりよく答えました。テストのエラーと失敗は、それぞれ「予期しないクラッシュ」と「意図しない動作」として簡潔に説明できます。関数がクラッシュしたときにテストがエラーを表示するようにしたいが、特定の入力を指定した場合にそれがスローすることがわかっている例外が、別の入力を指定した場合にスローされた場合はそうではない。
coredumperror 2018年

52

関数を呼び出すだけです。例外が発生した場合、単体テストフレームワークはこれにエラーのフラグを付けます。コメントを追加したいかもしれません。例:

sValidPath=AlwaysSuppliesAValidPath()
# Check PathIsNotAValidOne not thrown
MyObject(sValidPath)

35
失敗とエラーは概念的に異なります。さらに、Pythonでは例外が制御フローに日常的に使用されているため、ロジックまたはコードが壊れていると、一目で(=テストコードを探索せずに)理解するのが非常に難しくなります...
mac

1
テストに合格するか、合格しません。うまくいかない場合は、修正する必要があります。「失敗」または「エラー」として報告されるかどうかは、ほとんど関係ありません。違いが1つあります。私の回答では、スタックトレースが表示されるので、PathIsNotAValidOneがスローされた場所を確認できます。受け入れられた回答ではその情報は得られないため、デバッグはより困難になります。(Py2を想定しています。Py3の方が優れているかどうかはわかりません)。
user9876 2014

19
@ user9876-いいえ。テストの終了条件は、誤って信じているように見える2ではなく、3(合格/不合格/エラー)です。エラーと失敗の違いはかなり大きく、それらを同じであるかのように扱うことは、プログラミングが不十分なだけです。私を信じていない場合は、テストランナーのしくみと、テストランナーが失敗やエラーを実装するためのデシジョンツリーに目を向けてください。Pythonの良い出発点はpytest xfailデコレータです。
mac

4
単体テストの使い方次第ではないでしょうか。私のチームがユニットテストを使用する方法は、すべて合格する必要があります。(すべての単体テストを実行する継続的インテグレーションマシンによるアジャイルプログラミング)。テストケースが「成功」、「失敗」、または「エラー」を報告する可能性があることを知っています。しかし、私のチームにとって重要なのは、「すべてのユニットテストに合格するか」ということです。(つまり、「ジェンキンスグリーンですか?」)。したがって、私のチームにとって、「失敗」と「エラー」の間に実際的な違いはありません。単体テストを別の方法で使用すると、要件が異なる場合があります。
user9876

1
@ user9876「失敗」と「エラー」の違いは、「私のアサートが失敗しました」と「私のテストがアサートに到達しない」の違いです。それは、私にとって、修正テストの間の有用な区別ですが、あなたが言うように、誰にとってもそうではありません。
CS

14

私は元のポスターであり、コードで最初に使用することなく、DGHによって上記の回答を受け入れました。

使用してみると、実際に必要なことを行うには少し手を加える必要があることに気づきました(DGHに公平であるために、彼または彼女は「または類似の何か」と言っていました!)。

他の人のためにここにツイークを投稿する価値があると思いました:

    try:
        a = Application("abcdef", "")
    except pySourceAidExceptions.PathIsNotAValidOne:
        pass
    except:
        self.assertTrue(False)

ここで私が試みていたのは、スペースの2番目の引数を使用してApplicationオブジェクトをインスタンス化しようとした場合に、pySourceAidExceptions.PathIsNotAValidOneが確実に発生するようにすることでした。

上記のコード(DGHの回答に大きく基づく)を使用すると、それができると思います。


2
あなたはあなたの質問を明確にし、それに答えないので、あなたはそれを編集したはずです(答えられていません)。以下の私の答えを見てください。
hiwaylon 2011

13
元の問題とは正反対のようです。self.assertRaises(PathIsNotAValidOne, MyObject, sInvalidPath)この場合、仕事をする必要があります。
アントニー・ハッチキンス

8

あなたは定義することができるassertNotRaisesの元の実装の約90%再利用することでassertRaisesunittestのモジュール。このアプローチでは、assertNotRaisesその逆の障害状態を除いて、と同じように動作するメソッドが作成されassertRaisesます。

TLDRとライブデモ

assertNotRaisesメソッドを追加するのは驚くほど簡単であることがわかりましたunittest.TestCase(この回答を書くのに、コードの場合と比べて約4倍の時間がかかりました)。ここには、assertNotRaisesメソッドの動作のライブデモです。と同様にassertRaises、callableとargsをに渡すかassertNotRaiseswithステートメントで使用できます。ライブデモには、assertNotRaises意図したとおりに機能することを示すテストケースが含まれています。

細部

assertRaisesin の実装unittestはかなり複雑ですが、少し巧妙なサブクラス化によって、その障害状態をオーバーライドして元に戻すことができます。

assertRaises基本的にunittest.case._AssertRaisesContextクラスのインスタンスを作成して返す短いメソッドです(unittest.caseモジュールでの定義を参照)。独自に定義できます_AssertNotRaisesContextメソッドをサブクラス化_AssertRaisesContextしてオーバーライドによりクラス__exit__

import traceback
from unittest.case import _AssertRaisesContext

class _AssertNotRaisesContext(_AssertRaisesContext):
    def __exit__(self, exc_type, exc_value, tb):
        if exc_type is not None:
            self.exception = exc_value.with_traceback(None)

            try:
                exc_name = self.expected.__name__
            except AttributeError:
                exc_name = str(self.expected)

            if self.obj_name:
                self._raiseFailure("{} raised by {}".format(exc_name,
                    self.obj_name))
            else:
                self._raiseFailure("{} raised".format(exc_name))

        else:
            traceback.clear_frames(tb)

        return True

通常、テストケースクラスは、クラスから継承させることで定義します。 TestCase。代わりにサブクラスから継承する場合MyTestCase

class MyTestCase(unittest.TestCase):
    def assertNotRaises(self, expected_exception, *args, **kwargs):
        context = _AssertNotRaisesContext(expected_exception, self)
        try:
            return context.handle('assertNotRaises', args, kwargs)
        finally:
            context = None

これで、すべてのテストケースでassertNotRaisesメソッドを使用できるようになります。


どこにされtraceback、あなたの中elseのステートメントは、から来ますか?
2018年

1
@NOhs欠落がありましたimport。修正済み
tel

2
def _assertNotRaises(self, exception, obj, attr):                                                                                                                              
     try:                                                                                                                                                                       
         result = getattr(obj, attr)                                                                                                                                            
         if hasattr(result, '__call__'):                                                                                                                                        
             result()                                                                                                                                                           
     except Exception as e:                                                                                                                                                     
         if isinstance(e, exception):                                                                                                                                           
            raise AssertionError('{}.{} raises {}.'.format(obj, attr, exception)) 

パラメータを受け入れる必要がある場合は変更できます。

のように呼び出す

self._assertNotRaises(IndexError, array, 'sort')

1

unittest次のように、モンキーパッチを使用すると便利です。

def assertMayRaise(self, exception, expr):
  if exception is None:
    try:
      expr()
    except:
      info = sys.exc_info()
      self.fail('%s raised' % repr(info[0]))
  else:
    self.assertRaises(exception, expr)

unittest.TestCase.assertMayRaise = assertMayRaise

これにより、例外がないことをテストする際の意図が明確になります。

self.assertMayRaise(None, does_not_raise)

これにより、ループでのテストも簡略化されます。

# ValueError is raised only for op(x,x), op(y,y) and op(z,z).
for i,(a,b) in enumerate(itertools.product([x,y,z], [x,y,z])):
  self.assertMayRaise(None if i%4 else ValueError, lambda: op(a, b))

サルパッチとは何ですか?
ScottMcC 2018年

1
en.wikipedia.org/wiki/Monkey_patchを参照してください。追加assertMayRaiseした後unittest.TestSuiteは、それがunittestライブラリの一部であるように見せかけることができます。
AndyJost 2018年

0

Exceptionクラスをに渡すassertRaises()と、コンテキストマネージャが提供されます。これにより、テストが読みやすくなります。

# raise exception if Application created with bad data
with self.assertRaises(pySourceAidExceptions.PathIsNotAValidOne):
    application = Application("abcdef", "")

これにより、コード内のエラーケースをテストできます。

この場合、PathIsNotAValidOneApplicationコンストラクタに無効なパラメータを渡したときに発生するをテストしています。


1
いいえ、これは、例外コンテキストマネージャブロック内で発生しない場合にのみ失敗します。合格した 'with self.assertRaises(TypeError):raise TypeError'で簡単にテストできます。
Matthew Trevor、

@MatthewTrevorいいね。私が覚えているように、テストコードは正しく実行される、つまり発生しないのではなく、エラーケースのテストを提案していました。それに応じて回答を編集しました。うまくいけば、赤字から抜け出すために+1を獲得できます。:)
hiwaylon '25

:注、これはまた、Pythonの2.7およびそれ以降docs.python.org/2/library/...
qneill

0

そのように試すことができます。try:self.assertRaises(None、function、arg1、arg2)を除く:コードをtryブロック内に配置しない場合は通過し、例外を通過します 'AssertionError:発生しませんでした。予想される動作であるtryブロック内に配置した場合。


0

エラーなしでオブジェクトが初期化されることを確認する簡単な方法の1つは、オブジェクトの型インスタンスをテストすることです。

次に例を示します。

p = SomeClass(param1=_param1_value)
self.assertTrue(isinstance(p, SomeClass))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.