Skip to content

Instantly share code, notes, and snippets.

@podhmo
Forked from tomoh1r/testutil.py
Last active August 29, 2015 14:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save podhmo/066af0861f435c5788a0 to your computer and use it in GitHub Desktop.
Save podhmo/066af0861f435c5788a0 to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
from datetime import datetime
import contextlib
import unittest
@contextlib.contextmanager
def mock_datetime_now(module_path, return_value):
"""
適当なモジュールの datetime.now() を mock し、適当な日付を返すようにする。
>>> from datetime import datetime
>>> with mock_datetime_now('some_app.some_module.datetime', datetime(2015, 1, 1)):
... datetime.now()
datetime(2015, 1, 1, 0, 0, 0)
のように利用する。
"""
class DummyDatetime(datetime):
@classmethod
def now(cls):
return return_value
# dateime を持つモジュールを取得する
module_names = module_path.rstrip('.datetime').split('.')
module = __import__('.'.join(module_names))
for name in module_names[1:]:
module = getattr(module, name)
# 置き換えて yield
module.datetime = DummyDatetime
yield
# 置き換えたのを戻す
module.datetime = datetime
class DateTimeTests(unittest.TestCase):
def _callFUT(self, *args, **kwargs):
return datetime.now()
def test_patch(self):
target = 1
with mock_datetime_now("__main__.datetime", target):
result = self._callFUT()
self.assertEqual(result, target)
def test_must_fail(self):
target = 1
with mock_datetime_now("__main__.datetime", target):
raise Exception("oops")
def test_must_success(self):
self.assertNotEqual(datetime.now(), 1)
if __name__ == "__main__":
unittest.main()
"""
EF.
======================================================================
ERROR: test_must_fail (__main__.DateTimeTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "hmm.py", line 51, in test_must_fail
raise Exception("oops")
Exception: oops
======================================================================
FAIL: test_must_success (__main__.DateTimeTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "hmm.py", line 54, in test_must_success
self.assertNotEqual(self._callFUT(), 1)
AssertionError: 1 == 1
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1, errors=1)
"""
@podhmo
Copy link
Author

podhmo commented Apr 9, 2015

素直にmock使えば良いと思います。

import mock


class DummyDatetime(object):
    def __init__(self, dt):
        self.dt = dt

    def now(self):
        return self.dt


with mock.patch("__main__.datetime", new=DummyDatetime(1)):
    print(datetime.now())  # => 1

# とかふつうに
with mock.patch("__main__.datetime") as m:
    m.now.return_value = 1
    print(datetime.now())  # => 1

@podhmo
Copy link
Author

podhmo commented Apr 9, 2015

あとversionにもよりますが__import__よりimportlib.import_moduleの方がめんどうなこと少ないです。

@podhmo
Copy link
Author

podhmo commented Apr 9, 2015

ちなみにですが。なぜrstripされる".datetime"を必ず付ける仕様にするのかよく分かんないです。
どうせ除去されるのだからと、".datetime"を抜かすとrstripの処理でmodule名が変なことになります。
"hmm".rstrip(".datetime") # => h

@tomoh1r
Copy link

tomoh1r commented Apr 10, 2015

素直にmock使えば良いと思います。

ユニットテストのテスト範囲が大きくて、テストが落ちた気がします。ちょっともう少し追ってみます。

あとversionにもよりますがimportよりimportlib.import_moduleの方がめんどうなこと少ないです。

ver2.7 です。今の案件のあのプロジェクトです。

ちなみにですが。なぜrstripされる".datetime"を必ず付ける仕様にするのかよく分かんないです。
どうせ除去されるのだからと、".datetime"を抜かすとrstripの処理でmodule名が変なことになります。
"hmm".rstrip(".datetime") # => h

.datetime つけたほうが明示的で良いかと思いました。

まー module_path に .datetime つけるのは意味わかりませんね。 module なのに。

なので .datetime 不要にします。

@tomoh1r
Copy link

tomoh1r commented Apr 10, 2015

素直にmock使えば良いと思います。

ユニットテストのテスト範囲が大きくて、テストが落ちた気がします。ちょっともう少し追ってみます。

これ問題なくて、教わったのの感じの以下のクラスを作って利用するようにします。

class DummyDatetime(object):
    """
    適当なモジュールの datetime.now() を mock し、適当な日付を返すようにする。

    >>> import mock
    >>> from datetime import datetime
    >>> with mock.patch('app.some_app.some_module.datetime',
    ...                 new=DummyDatetime(datetime(2015, 1, 1))):
    ...     datetime.now()
    datetime(2015, 1, 1, 0, 0, 0)

    のように利用する。
    """
    # XXX: メソッドがないと言われたら、随時テストに合うように追加願います。
    def __init__(self, dummy_current):
        self.now = lambda: dummy_current

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment