Python例外テスト:クリーンで効果的な方法

Python例外テスト:クリーンで効果的な方法

Beyond Basics: PytestとUnittestのための高度なPython例外のテスト

image by chenspec on pixabay

例外のテストは形式的なもの以上のものです。それは信頼性のあるコードの記述に欠かせない要素です。このチュートリアルでは、例外を発生させるPythonコードをテストするための実践的で効果的な方法について探求します。例外メッセージの正確さを検証し、両方のpytestunittestをカバーします。また、フレームワークごとにパラメータ化テストの有無もカバーします。

このチュートリアルの最後までに、コードのためのクリーンで効率的で情報を提供する例外テストのしっかりとした理解を持つことができます。

次の例を見てみましょう:

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

上記の関数にはいくつかのフローをテストすることができます。ハッピー・フロー、ゼロの分母、非数値の入力などです。

では、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なしの状態です。

screenshot by author

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")

unittestparameterizedを使用する場合は、パッケージをインストールする必要があります。以下は、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のテストバーでよりきれいで情報量の多いビューを提供し、テストケースの理解とナビゲーションを容易にしました。これらの技術を使用することで、ユニットテストを改善し、コードが例外を適切に処理することを確認できます。

楽しいテストを!

image by jakob5200 on pixabay

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

データサイエンス

「Seerの最高データオフィサーであるDr. Serafim Batzoglouによるインタビューシリーズ」

セラフィム・バツォグルはSeerのチーフデータオフィサーですSeerに加わる前は、セラフィムはInsitroのチーフデータオフィサー...

人工知能

「シフトのCEOであるクリス・ナーゲル – インタビューシリーズ」

クリスはSiftの最高経営責任者です彼は、Ping Identityを含むベンチャー支援および公開SaaS企業のシニアリーダーシップポジシ...

人工知能

「ナレ・ヴァンダニャン、Ntropyの共同創設者兼CEO- インタビューシリーズ」

Ntropyの共同創設者兼CEOであるナレ・ヴァンダニアンは、開発者が100ミリ秒未満で超人的な精度で金融取引を解析することを可...

人工知能

ギル・ジェロン、Orca SecurityのCEO&共同創設者-インタビューシリーズ

ギル・ゲロンは、オルカ・セキュリティのCEO兼共同設立者ですギルは20年以上にわたりサイバーセキュリティ製品をリードし、提...

人工知能

「Kognitosの創設者兼CEO、ビニー・ギル- インタビューシリーズ」

ビニー・ギルは、複数の役職と企業を横断する多様で幅広い業務経験を持っていますビニーは現在、Kognitosの創設者兼CEOであり...

人工知能

「ジャスティン・マクギル、Content at Scaleの創設者兼CEO - インタビューシリーズ」

ジャスティンは2008年以来、起業家、イノベーター、マーケターとして活動しています彼は15年以上にわたりSEOマーケティングを...