2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Pythonのテスト patch()関数のautospec指定でパッチ対象の関数/メソッドの仕様を保証する

Last updated at Posted at 2021-11-25

Pythonでテストコードを書いていると, ときどきこんな問題に遭遇します.

  • テスト対象の関数のなかで呼び出されている関数をパッチしてテストを成功させる
  • 実はテスト対象の関数のなかでパッチした関数の呼び出し方が間違っていて, アプリケーションとして動かすまでそのことに気づけない

パッチするときに unittest.mock にある patch() 関数を使いますが, このとき autospec引数にTrueを渡しておくと, この問題を回避できそうなことに気づきました.

問題のコード

実際のコードで説明します.
はじめに実装コードを示します.

app.py
def items_impl(type_):
    item_table = {
        "fruits": ["apple"],
        "meets": ["beef"],
    }
    return item_table.get(type_, [])


def items():
    return items_impl()

if __name__ == "__main__":
    print(items())

このアプリケーションは実行時エラーが発生します.
items() 関数のなかで items_impl()関数を呼び出していますが, ここで必要な引数 type_ が指定されていないためです.

TypeError: items_impl() missing 1 required positional argument: 'type_'

次にテストコードを示します.

test_app.py
import unittest
from unittest import mock
import app


class AppTest(unittest.TestCase):

    @mock.patch("app.items_impl", mock.MagicMock(return_value=["baseball"]))
    def test_items(self):
        self.assertListEqual(app.items(), ["baseball"])

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

このテストは成功します.
items_impl() 関数をMagicMockオブジェクトでパッチしているためです.

この程度のシンプルなコードでパッチが必要になることはありませんが, それぞれの関数で評価/テストしたいときや, items_impl() 関数が未実装であったりするときに, パッチしてテストする必要があるケースはそれなりにあります.

で, 実装コードの誤りを見落としました.

patch() の autospec=True 指定で解決する

パッチするときに使う patch() 関数で autospec=Trueと引数を加えると, パッチした関数の仕様にあわない呼び出し方をすると, エラーにすることができます.

@mock.patch("app.items_impl", autospec=True)
def test_items_2(self, mock_items_impl):
    mock_items_impl.return_value = ["baseball"]
    self.assertListEqual(app.items(), ["baseball"])

TypeError: missing a required argument: 'type_'

(失敗/FAILEDではなくエラー. 実行時エラーなので)

次のように create_autospec() 関数と組み合わせることもできます.

@mock.patch(
    "app.items_impl",
    mock.create_autospec(app.items_impl, return_value=["baseball"]),
)
def test_items_3(self):
    self.assertListEqual(app.items(), ["baseball"])

これで実装コードであげた問題をテストコードで検出できそうです.

参考

ドキュメントながめてて知りました.

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?