我已经为客户构建了一个付费的CMS +发票系统,我需要对我的测试更加严格。
我将所有数据保存在 Django ORM 中,并有一堆以不同间隔运行的 Celery 任务,以确保在用户不支付发票时发送新发票和发票提醒并切断访问权限。
例如,我希望能够运行以下测试:
-
创建新用户并生成 X 天访问站点的发票
-
模拟 X + 1 天的流逝,并运行我在 Celery 中设置的所有任务。
-
检查是否已向用户开具其他 X 天的新发票。
到目前为止,我想出的KISS方法是在单独的机器上进行所有测试,并在操作系统级别实际操作日期/时间。因此,测试脚本将:
-
将系统日期设置为第 1 天
-
创建新用户并生成 X 天访问权限的第一张发票
-
提前然后系统日期 1 天。运行我所有的芹菜任务。重复直到 X + 1 天"过去">
-
检查是否已开具新发票
这有点笨拙,但我认为它可能会起作用。关于如何完成它的任何其他想法吗?
您可以使用 mock 更改用于获取时间的函数的返回值(例如datetime.datetime.now
(。
有多种方法可以做到这一点(请参阅模拟文档(,但这里有一种:
import unittest
import datetime
from mock import patch
class SomeTestCase(unittest.TestCase):
def setUp(self):
self.time = datetime.datetime(2012, 5, 18)
class fakedatetime(datetime.datetime):
@classmethod
def now(cls):
return self.time
patcher = patch('datetime.datetime', fakedatetime)
self.addCleanup(patcher.stop)
patcher.start()
def test_something(self):
self.assertEqual(datetime.datetime.now(), datetime.datetime(2012, 5, 18))
self.time = datetime.datetime(2012, 5, 20)
self.assertEqual(datetime.datetime.now(), datetime.datetime(2012, 5, 20))
因为我们不能直接替换datetime.datetime.now
,所以我们创建了一个假的日期时间类,它以相同的方式做所有事情,除了在调用现在时返回一个常量值。
在不使用特殊模拟库的情况下,我建议准备处于模拟模式的代码(可能通过全局变量(。 在模拟模式下,您可以调用模型时间函数,而不是调用正常的时间函数(如time.time((或其他函数(,该函数返回特殊情况下所需的任何内容。
我会投票反对更改系统时间。 这似乎不像是单元测试,而更像是功能测试,因为它不能与该机器上的任何其他内容并行完成。
你也可以看看freezegun
模块。Github - https://github.com/spulec/freezegun
从他们的文档
from freezegun import freeze_time
import datetime
@freeze_time("2012-01-14")
def test():
assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
这是@madjar作为可重用上下文管理器的技术,它同时模拟time
模块,以防您的东西使用该评分器而不是datetime.now()
:
import datetime
import time
import mock
from contextlib import contextmanager
@contextmanager
def mock_passage_of_time(
hours: int = 0,
minutes: int = 0,
seconds: int = 0
):
now = datetime.datetime.now()
mock_now = now + datetime.timedelta(hours=hours, minutes=minutes, seconds=seconds)
class MockDateTime(datetime.datetime):
@classmethod
def now(cls):
return mock_now
with mock.patch('datetime.datetime', MockDateTime),
mock.patch('time.time', mock.MagicMock(return_value=mock_now.timestamp())):
yield
用法:
class SomeTestCase(unittest.TestCase):
def test_something(self):
orig_now = datetime.datetime.now()
five_mins_in_the_future = orig_now + timedelta(minutes=5)
with mock_passage_of_time(minutes=5):
delta = datetime.datetime.now() - five_mins_in_the_future
self.assertEqual(delta.seconds, 0)