Python で時間を扱うコードのテストを書いてハマったので対策を紹介します.
結論
結論としては,何も考えずにtime-machineを使うのがおすすめです.
検証
同一の時刻操作を,time-machine と freezegun それぞれで行う下記のようなテストコードを準備します.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import datetime                                                                                                                                               import logging                                                                                                                                                 timezone_offset = datetime.timezone(datetime.timedelta(hours=9))                                                                                               def move_to(handle, hour):                                                                                                                                         target_time = datetime.datetime.now(tz=timezone_offset).replace(hour=hour, minute=0, second=0)                                                                 logging.info("Move to %s", target_time)                                                                                                                        handle.move_to(target_time)                                                                                                                                    logging.info("Now (native) is %s", datetime.datetime.now())                                                                                                    logging.info("Now (aware ) is %s", datetime.datetime.now(timezone_offset))                                                                                 def test_freezegun(freezer):                                                                                                                                       logging.info("Current timezone is %s", datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo)                                                       move_to(freezer, 0)                                                                                                                                            move_to(freezer, 1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                    def test_time_machine(time_machine):                                                                                                                               logging.info("Current timezone is %s", datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo)                                                       move_to(time_machine, 0)                                                                                                                                       move_to(time_machine, 1)                                         | 
Pytest で実行した結果がこちら.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | tests/a.py::test_freezegun  -------------------------------- live log call --------------------------------- 22:42:13 INFO [a.py:18 test_freezegun] Current timezone is tzlocal() 22:42:13 INFO [a.py:10 move_to] Move to 2024-08-19 00:00:00.984044+09:00 00:00:00 INFO [a.py:13 move_to] Now (native) is 2024-08-18 15:00:00.984044 00:00:00 INFO [a.py:14 move_to] Now (aware ) is 2024-08-19 00:00:00.984044+09:00 00:00:00 INFO [a.py:10 move_to] Move to 2024-08-19 01:00:00.984044+09:00 01:00:00 INFO [a.py:13 move_to] Now (native) is 2024-08-18 16:00:00.984044 01:00:00 INFO [a.py:14 move_to] Now (aware ) is 2024-08-19 01:00:00.984044+09:00 PASSED                                                                   [ 50%] tests/a.py::test_time_machine  -------------------------------- live log call --------------------------------- 22:42:14 INFO [a.py:25 test_time_machine] Current timezone is JST 22:42:14 INFO [a.py:10 move_to] Move to 2024-08-19 00:00:00.030326+09:00 00:00:00 INFO [a.py:13 move_to] Now (native) is 2024-08-19 00:00:00.030326 00:00:00 INFO [a.py:14 move_to] Now (aware ) is 2024-08-19 00:00:00.030600+09:00 00:00:00 INFO [a.py:10 move_to] Move to 2024-08-19 01:00:00.030847+09:00 01:00:00 INFO [a.py:13 move_to] Now (native) is 2024-08-19 01:00:00.030847 01:00:00 INFO [a.py:14 move_to] Now (aware ) is 2024-08-19 01:00:00.031089+09:00 PASSED                                                                   [100%] | 
「Now (native)」の結果が,違っていることがわかります. test_time_machine の方は期待値と一致しますが,test_freezegun の方は, JST タイムゾーンなのに UTC の値が取れてしまっています.この挙動を理解せずに freezegun を使うと,テスト対象のコードがおかしいのか,テストコードがおかしいのか混乱してしまうことになります.
注意点
time-machine の紹介ページでは,freezegun や python-libfaketime と比較した欠点として CPython でしか使えないことが挙げられています.それに該当するケースの場合はまた別の対応必要になります.
 
  
  
  
  

コメント