こんにちはsekitakaです。
今日はdatetime.now()
を使用している関数をテストする方法を紹介します。
たとえば次のようなプログラム実行時の次の日を"YYYYMMDD"の文字列を返す関数を作って、その関数をテストしたいとします。
def get_tomorrow():
now_datetime = datetime.now()
tomorrow_datetime = now_datetime + timedelta(days=+1)
tomorrow = tomorrow_datetime.strftime("%Y%m%d")
return tomorrow
テスト方法
案1
まず簡単に以下のテストコードを書いてみました。しかし見て分かる通り、テストの実行を2017/6/22に行う場合のみ成功します。
これではCIで毎日テストを実施した時にエラーになってしまいます。
class Test(TestCase):
def test_get_tomorrow(self):
self.assertEqual("20170623", get_tomorrow())
案2
次に考えたのは期待する値を動的に生成するものです。これならいつテストしてもテストをパスします。
しかし実はget_tomorrowと実質的に同じコードを書いており、テストになっていないような気もします。
class Test(TestCase):
def test_get_tomorrow(self):
expect_tomorrow = (datetime.now() + timedelta(days=+1)).strftime("%Y%m%d")
self.assertEqual(expect_tomorrow, get_tomorrow())
案3
そこでdatetime.now()
をMock化して、任意のdatetimeを返す状態にしてget_tomorrow
を実行すれば良いのではないかと考えました。そして書いたのが次のコードです。
from mock import Mock
class Test(TestCase):
def test_get_tomorrow(self):
datetime.now = Mock(return_value=datetime(year=2017, month=1, day=1))
self.assertEqual("20170102", get_tomorrow())
期待して実行したところ、ビルトインのdatetimeにattributeをセット出来ないらしく実行できませんでした。
TypeError: can't set attributes of built-in/extension type 'datetime.datetime'
案4(答え)
困っていたところfreezegunというdatetimeモジュールをモック化するライブラリを発見しました。
from freezegun import freeze_time
class Test(TestCase):
@freeze_time("2017-01-01")
def test_get_tomorrow_4(self):
self.assertEqual("20170102", get_tomorrow())
@freeze_time("2017-01-01")
の記述によって現在を2017/1/1に固定しています。
よってget_tomorrow()
は常に"20170102"
を返すことになり、いつテストを実行しても成功することになります。
freeze_timeする日を変更すれば年跨ぎやうるう年で正常にプログラムが動作するかなども確認できそうで便利ですね。
まとめ
いかがでしたでしょうか。pythonのdatetime.now()
に依存する関数のテスト方法を紹介しました。
単体テストも一度実行して終わりでなく、CIを回すならいつ実行してもいいようにテストケースを作っておく必要があるのですね。
これからもテスト手法について学んでいきたいと思います。