1. ホーム
  2. python

[解決済み] Pythonのunittestでアサーションに失敗した場合の継続について

2023-04-04 21:55:40

質問

EDIT: より良い例に変更し、なぜこれが本当の問題なのかを明確にしました。

私は、アサーションが失敗したときに実行を継続するPythonのユニットテストを書きたいと思います。たとえば

class Car(object):
  def __init__(self, make, model):
    self.make = make
    self.model = make  # Copy and paste error: should be model.
    self.has_seats = True
    self.wheel_count = 3  # Typo: should be 4.

class CarTest(unittest.TestCase):
  def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    self.assertEqual(car.make, make)
    self.assertEqual(car.model, model)  # Failure!
    self.assertTrue(car.has_seats)
    self.assertEqual(car.wheel_count, 4)  # Failure!

ここで、テストの目的は、Carの __init__ がそのフィールドを正しく設定することを確認することです。私はこれを4つのメソッドに分割することができましたが(そしてそれはしばしば素晴らしいアイデアです)、この場合、単一の概念("オブジェクトが正しく初期化されている")をテストする単一のメソッドとしてこれを維持する方が読みやすいと思います。

ここでメソッドを分割しないのがベストだと仮定すると、新たな問題が発生します。それは、すべてのエラーを一度に見ることができないということです。私が修正するとき model エラーを修正し、テストを再実行すると wheel_count エラーが表示されます。最初にテストを実行したときに両方のエラーが表示されれば、時間の節約になります。

比較のために、Google の C++ ユニットテストフレームワークである を区別しています。 と非致命的な EXPECT_* アサーションと致命的な ASSERT_* アサーションがあります。

アサーションは同じものをテストするペアで提供されますが、 現在の関数に対して異なる影響を及ぼします。ASSERT_* のバージョンは失敗したときに致命的なエラーを発生させ、現在の関数を中断させます。EXPECT_* 版は致命的でない失敗を生成し、現在の関数を中断させません。通常 EXPECT_* の方が好ましいです。しかし、もし問題のアサーションが失敗した際に処理を継続する意味がないのであれば、 ASSERT_* を使用すべきです。

を取得する方法はありますか? EXPECT_* -のような振る舞いをする方法はありますか? unittest ? にない場合は unittest でないなら、この動作をサポートする他の Python ユニットテストフレームワークはありますか?


ちなみに、非致命的アサーションから恩恵を受ける可能性のある現実のテストがどれくらいあるか興味があったので、いくつか見てみました。 コードの例 を見てみました (edited 2014-08-19 to use searchcode instead of Google Code Search, RIP)。最初のページからランダムに選ばれた10件の結果のうち、すべて同じテストメソッドで複数の独立したアサーションを行うテストが含まれていました。すべては非致命的なアサーションから恩恵を受けるでしょう。

どのように解決するのか?

致命的でないアサーションを行うもうひとつの方法は、アサーション例外をキャプチャしてリストに格納することです。そして、tearDownの一部として、そのリストが空であることをアサーションします。

import unittest

class Car(object):
  def __init__(self, make, model):
    self.make = make
    self.model = make  # Copy and paste error: should be model.
    self.has_seats = True
    self.wheel_count = 3  # Typo: should be 4.

class CarTest(unittest.TestCase):
  def setUp(self):
    self.verificationErrors = []

  def tearDown(self):
    self.assertEqual([], self.verificationErrors)

  def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    try: self.assertEqual(car.make, make)
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertEqual(car.model, model)  # Failure!
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertTrue(car.has_seats)
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertEqual(car.wheel_count, 4)  # Failure!
    except AssertionError, e: self.verificationErrors.append(str(e))

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