Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
0
Help us understand the problem. What are the problem?

posted at

updated at

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

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

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

参考

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
0
Help us understand the problem. What are the problem?