在单元测试中模拟时间的流逝



我已经为客户构建了一个付费的CMS +发票系统,我需要对我的测试更加严格。

我将所有数据保存在 Django ORM 中,并有一堆以不同间隔运行的 Celery 任务,以确保在用户不支付发票时发送新发票和发票提醒并切断访问权限。

例如,我希望能够运行以下测试:

  1. 创建新用户并生成 X 天访问站点的发票

  2. 模拟 X + 1 天的流逝,并运行我在 Celery 中设置的所有任务。

  3. 检查是否已向用户开具其他 X 天的新发票。

到目前为止,我想出的KISS方法是在单独的机器上进行所有测试,并在操作系统级别实际操作日期/时间。因此,测试脚本将:

  1. 将系统日期设置为第 1 天

  2. 创建新用户并生成 X 天访问权限的第一张发票

  3. 提前然后系统日期 1 天。运行我所有的芹菜任务。重复直到 X + 1 天"过去">

  4. 检查是否已开具新发票

这有点笨拙,但我认为它可能会起作用。关于如何完成它的任何其他想法吗?

您可以使用 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)

相关内容

  • 没有找到相关文章