20

これは、複数の出力ストリームがあるために、複数の独立した障害モードを持つ単一のテストを実行したときに発生した問題です。また、どのモードが最初に失敗したかに関係なく、これらすべてのモードでデータをアサートした結果も示したいと思いました。Python の unittest には、Suite を使用して単一のテストを表す以外にそのような機能はありません。物事の性質を捉えていないだけです。

実際の例は、ログも生成するオブジェクトのテストです。そのメソッドの出力をアサートしたいが、ログ出力もアサートしたい。2 つの出力には異なるテストが必要です。これは、2 つのストック アサーション式として適切に表現できますが、テスト内で一方の失敗が他方の失敗の可能性を隠してしまうことも望ましくありません。したがって、両方を同時にテストする必要があります。

この便利な小さなウィジェットを組み合わせて問題を解決しました。

def logFailures(fnList):
    failurelog = []
    for fn in fnList:
        try:
            fn()
        except AssertionError as e:
            failurelog.append("\nFailure %d: %s" % (len(failurelog)+1,str(e)))

    if len(failurelog) != 0:
        raise AssertionError(
            "%d failures within test.\n %s" % (len(failurelog),"\n".join(failurelog))
        )

次のように使用されます。

def test__myTest():
    # do some work here
    logFailures([
        lambda: assert_(False,"This test failed."),
        lambda: assert_(False,"This test also failed."),
    ])

その結果、logFailures() は、リスト内のメソッドで発生したすべてのアサーションのログを含む例外を発生させます。

質問:これでうまくいきますが、ネストされた一連のテストなどを作成する必要がある以外に、これを処理するためのより良い方法があるかどうか疑問に思っています。

4

3 に答える 3

25

サブテストを使用すると、最初の失敗後に実行が停止しません https://docs.python.org/3/library/unittest.html#subtests

2 つの失敗アサートの例を次に示します。

class TestMultipleAsserts(unittest.TestCase):

    def test_multipleasserts(self):
        with self.subTest():
            self.assertEqual(1, 0)
        with self.subTest():
            self.assertEqual(2, 0)

出力は次のようになります。

======================================================================
FAIL: test_multipleasserts (__main__.TestMultipleAsserts) (<subtest>)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test.py", line 9, in test_multipleasserts
    self.assertEqual(1, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_multipleasserts (__main__.TestMultipleAsserts) (<subtest>)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test.py", line 11, in test_multipleasserts
    self.assertEqual(2, 0)
AssertionError: 2 != 0

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

FAILED (failures=2)

次のようにサブテストを簡単にラップできます

class MyTestCase(unittest.TestCase):
    def expectEqual(self, first, second, msg=None):
        with self.subTest():
            self.assertEqual(first, second, msg)

class TestMA(MyTestCase):
    def test_ma(self):
        self.expectEqual(3, 0)
        self.expectEqual(4, 0)
于 2017-01-25T14:16:55.930 に答える
16

私は、アサーションごとにテスト メソッドを作成する必要があるという支配的な意見には同意しません。1 つのテスト メソッドで複数のことを確認したい場合があります。それを行う方法についての私の答えは次のとおりです。

# Works with unittest in Python 2.7
class ExpectingTestCase(unittest.TestCase):
    def run(self, result=None):
        self._result = result
        self._num_expectations = 0
        super(ExpectingTestCase, self).run(result)

    def _fail(self, failure):
        try:
            raise failure
        except failure.__class__:
            self._result.addFailure(self, sys.exc_info())

    def expect_true(self, a, msg):
        if not a:
            self._fail(self.failureException(msg))
        self._num_expectations += 1

    def expect_equal(self, a, b, msg=''):
        if a != b:
            msg = '({}) Expected {} to equal {}. '.format(self._num_expectations, a, b) + msg
            self._fail(self.failureException(msg))
        self._num_expectations += 1

そして、これが便利でリスクがないと私が考えるいくつかの状況を次に示します。

1) 異なるデータ セットのコードをテストする場合。ここに add() 関数があり、いくつかの入力例でテストしたいと思います。3 つのデータ セットに対して 3 つのテスト メソッドを記述することは、自分自身を繰り返すことを意味し、これは悪いことです。特に呼び出しがより複雑な場合:

class MyTest(ExpectingTestCase):
    def test_multiple_inputs(self):
        for a, b, expect in ([1,1,2], [0,0,0], [2,2,4]):
            self.expect_equal(expect, add(a,b), 'inputs: {} {}'.format(a,b))

2) 関数の複数の出力を確認したい場合。各出力をチェックしたいのですが、最初の失敗で他の 2 つがマスクされないようにします。

class MyTest(ExpectingTestCase):
    def test_things_with_no_side_effects(self):
        a, b, c = myfunc()
        self.expect_equal('first value', a)
        self.expect_equal('second value', b)
        self.expect_equal('third value', c)

3) 設定コストが高いものをテストする。テストは迅速に実行する必要があります。一部のテストでは、テストが非常に遅くなる 1 秒かかるデータベースまたはネットワーク接続が必要です。データベース接続自体をテストしている場合は、おそらく速度を落とす必要があります。しかし、関係のないものをテストしている場合は、一連のチェック全体に対して低速セットアップを 1 回実行したいと考えています。

于 2013-01-01T01:43:45.957 に答える
12

これは私にはオーバーエンジニアリングのように感じます。また:

  • 1 つのテスト ケースで 2 つのアサートを使用します。最初のアサートが失敗した場合、それは本当です。2 番目のアサートが成功したかどうかはわかりません。しかし、とにかくコードを修正するつもりなので、修正してください。そうすれば、2 番目のアサートが成功したかどうかがわかります。

  • 各条件をチェックするために 1 つずつ、合計 2 つのテストを記述します。テストでコードが重複する恐れがある場合は、テストから呼び出すヘルパー メソッドにコードの大部分を配置します。

于 2013-01-01T03:25:57.913 に答える