はじめに
パッケージに制約がある中でdatetime
を含むテストを書くときに困ったのでメモがてら書いておきます
環境
Python:3.9.7
pytest:2.7.0
pytest.mock:3.10.0
テスト対象のメソッド
datetime_wrapper.py
import datetime
def return_now():
return datetime.now()
freezegun
なしでテストを書いてみよう
「おりゃ」
test_datetime_wrapper.py
import datetime
class Test_return_now:
def test_return_now(self, mocker):
mocker.patch.object(datetime, "now", return_value=datetime.datetime(1111, 12, 31, 12, 34, 56))
assert datetime_wrapper.GetFormattedCurrentTime() == "1111-12-31 12:34:56"
(そもそもこのテストをやる意義は一旦置いといて下さい...)
datetime
のnow
メソッドがモックされ、return_value
が固定された為一見問題なさそうに見えます。
が、通りません。
datetime
モジュールとモジュール内で定義されたdatetime
の名称領域を両方モックしてしまうためreturn_value
のdatetime
もモックされてしまいAttributeError
が出ます。
これは、datetime_wrapper.py(test対象のモジュール)
内のdatetime
のみをモックすることで回避可能です。
例を以下に示します。
- 良いパターン
test_datetime_wrapper.py
import datetime
class Test_return_now:
def test_return_now(self, mocker):
mock_now = mocker.patch.object(datetime_wrapper, "datetime").datetime.now
mock_now.return_value = datetime.datetime(1111, 12, 31, 12, 34, 56)
assert datetime_wrapper.return_now() == "1111-12-31 12:34:56"
以上のようにすることでピンポイントにdatetime_wrapper
内のdatetime
のみをモックすることができるためエラーを吐かずにテストを実行できます。
この例はオブジェクト単位でモックができるmocker.object
を使う上で汎用性のある考え方で、その仕組みの理解にもつながるので覚えといて損はないと思います。