Python例外テスト:クリーンで効果的な方法
Python例外テスト:クリーンで効果的な方法
Beyond Basics: PytestとUnittestのための高度なPython例外のテスト
例外のテストは形式的なもの以上のものです。それは信頼性のあるコードの記述に欠かせない要素です。このチュートリアルでは、例外を発生させるPythonコードをテストするための実践的で効果的な方法について探求します。例外メッセージの正確さを検証し、両方のpytest
とunittest
をカバーします。また、フレームワークごとにパラメータ化テストの有無もカバーします。
このチュートリアルの最後までに、コードのためのクリーンで効率的で情報を提供する例外テストのしっかりとした理解を持つことができます。
次の例を見てみましょう:
def divide(num_1: float, num_2: float) -> float: if not isinstance(num_1, (int, float)) \ or not isinstance(num_2, (int, float)): raise TypeError("少なくとも1つの入力が数字ではありません: {num_1}, {num_2}") return num_1 / num_2
上記の関数にはいくつかのフローをテストすることができます。ハッピー・フロー、ゼロの分母、非数値の入力などです。
- Agents.jsをご紹介します:JavaScriptを使用して、あなたのLLMにツールを提供します
- 「ジェネレーティブAIを活用してグローバルでアジャイルかつ効果的なゴートゥーマーケット戦略を開発する方法」
- 「ChatGPTの新しいカスタム指示がリリースされました使い方は以下の通りです」
では、pytest
を使用した場合のテストはどのようになるでしょうか:
pytest
from contextlib import nullcontext as does_not_raiseimport pytestfrom operations import dividedef test_happy_flow(): with does_not_raise(): assert divide(30, 2.5) is not None assert divide(30, 2.5) == 12.0def test_division_by_zero(): with pytest.raises(ZeroDivisionError) as exc_info: divide(10.5, 0) assert exc_info.value.args[0] == "float division by zero"def test_not_a_digit(): with pytest.raises(TypeError) as exc_info: divide("a", 10.5) assert exc_info.value.args[0] == \ "少なくとも1つの入力が数字ではありません: a, 10.5"
また、無効なフローに対して間違った例外タイプをテストした場合や、ハッピー・フローで発生した例外を確認しようとした場合にどのような結果が得られるかを確認するためのサニティチェックも行うことができます。これらの場合、テストは失敗します:
# 以下の2つのテストはともに失敗するdef test_wrong_exception(): with pytest.raises(TypeError) as exc_info: divide(10.5, 0) assert exc_info.value.args[0] == "float division by zero"def test_unexpected_exception_in_happy_flow(): with pytest.raises(Exception): assert divide(30, 2.5) is not None
では、上記のテストが失敗した理由はなんでしょうか?with
コンテキストは要求された特定の例外タイプをキャッチし、例外タイプが実際に要求したものであることを検証します。
テストwrong_exception_check
では、例外(ZeroDivisionError
)がスローされましたが、それがTypeError
によってキャッチされませんでした。したがって、スタックトレースではZeroDivisionError
がスローされ、TypeError
コンテキストによってキャッチされなかったことが確認できます。
テストredundant_exception_context
では、with pytest.raises
コンテキストが要求した例外タイプ(この場合はException
を指定しました)を検証しようとしましたが、例外がスローされなかったため、テストはFailed: DID NOT RAISE <class ‘Exception’>
というメッセージで失敗しました。
では、次のステージに移りましょう。パラメータ化を使用することで、テストをより簡潔でクリーンにする方法を探求してみましょう。
Parametrize
from contextlib import nullcontext as does_not_raiseimport pytestfrom operations import [email protected]( "num_1, num_2, expected_result, exception, message", [ (30, 2.5, 12.0, does_not_raise(), None), (10.5, 0, None, pytest.raises(ZeroDivisionError), "float division by zero"), ("a", 10.5, None, pytest.raises(TypeError), "少なくとも1つの入力が数字ではありません: a, 10.5") ], ids=["正しい入力", "ゼロで割る", "数値ではない入力"])def test_division(num_1, num_2, expected_result, exception, message): with exception as e: result = divide(num_1, num_2) assert message is None or message in str(e) if expected_result is not None: assert result == expected_result
ids
パラメータは、IDEのテストバービューに表示されるテストケース名を変更します。以下のスクリーンショットで、左側がids
あり、右側がids
なしの状態です。
pytest
フレームワークについて説明しましたので、unittest
を使用して同じテストをどのように書くか見てみましょう。
unittest
from unittest import TestCasefrom operations import divideclass TestDivide(TestCase): def test_happy_flow(self): result = divide(0, 10.5) self.assertEqual(result, 0) def test_division_by_zero(self): with self.assertRaises(ZeroDivisionError) as context: divide(10, 0) self.assertEqual(context.exception.args[0], "division by zero") def test_not_a_digit(self): with self.assertRaises(TypeError) as context: divide(10, "c") self.assertEqual(context.exception.args[0], "at least one of the inputs " "is not a number: 10, c")
unittest
でparameterized
を使用する場合は、パッケージをインストールする必要があります。以下は、unittest
でパラメータ化されたテストがどのように見えるかの例です:
Parametrized
import unittestfrom parameterized import parameterized # インストールが必要ですfrom operations import dividedef get_test_case_name(testcase_func, _, param): test_name = param.args[-1] return f"{testcase_func.__name__}_{test_name}"class TestDivision(unittest.TestCase): @parameterized.expand([ (30, 2.5, 12.0, None, None, "valid inputs"), (10.5, 0, None, ZeroDivisionError, "float division by zero", "divide by zero"), ("a", 10.5, None, TypeError, "at least one of the inputs is not a number: a, 10.5", "not a number input") ], name_func=get_test_case_name) def test_division(self, num_1, num_2, expected_result, exception_type, exception_message, test_name): with self.subTest(num_1=num_1, num_2=num_2): if exception_type is not None: with self.assertRaises(exception_type) as e: divide(num_1, num_2) self.assertEqual(str(e.exception), exception_message) else: result = divide(num_1, num_2) self.assertIsNotNone(result) self.assertEqual(result, expected_result)
unittest
では、pytest
の例と同様にテストケース名を変更しました。ただし、これを実現するために、カスタム関数とname_func
パラメータを使用しました。
今日は、Pythonの例外をテストするための効果的な方法について探求しました。例外が予想どおりにスローされたかどうかを認識し、例外メッセージが私たちの期待と一致することを検証する方法について学びました。pytest
を使用した伝統的なアプローチや、parametrize
を使用したよりきれいなアプローチを含むdivide
関数のテストのさまざまな方法を検討しました。また、ライブラリのインストールが必要なparameterized
を使用したunittest
の相当も調査しました。また、ids
とカスタムテスト名を使用することで、IDEのテストバーでよりきれいで情報量の多いビューを提供し、テストケースの理解とナビゲーションを容易にしました。これらの技術を使用することで、ユニットテストを改善し、コードが例外を適切に処理することを確認できます。
楽しいテストを!
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles