【问题标题】:How to stub time.sleep() in Python unit testing如何在 Python 单元测试中存根 time.sleep()
【发布时间】:2014-05-15 05:41:05
【问题描述】:

我想制作一个存根来防止 time.sleep(..) 休眠以提高单元测试的执行时间。

我拥有的是:

import time as orgtime

class time(orgtime):
    '''Stub for time.'''
    _sleep_speed_factor = 1.0

    @staticmethod
    def _set_sleep_speed_factor(sleep_speed_factor):
        '''Sets sleep speed.'''
        time._sleep_speed_factor = sleep_speed_factor


    @staticmethod
    def sleep(duration):
        '''Sleeps or not.'''
        print duration * time._sleep_speed_factor
        super.sleep(duration * time._sleep_speed_factor) 

但是,我在上面的第二个代码行(类定义)上收到以下错误:

TypeError: Error when calling the metaclass bases
module.__init__() takes at most 2 arguments (3 given).

如何修复错误?

【问题讨论】:

  • 你继承自一个模块而不是一个类

标签: python class time constructor python-unittest


【解决方案1】:

您可以在测试中使用mock 库。

import time
from mock import patch

class MyTestCase(...):


     @patch('time.sleep', return_value=None)
     def my_test(self, patched_time_sleep):
          time.sleep(666)  # Should be instant

【讨论】:

  • 我接受了答案,但遗憾的是我无法测试它,因为 mock 不是我们可以包含的组件的一部分。
  • mock 是 PyPi 上可用的开源 Python 库,适用于所有平台。通常不应该有任何实际、法律或任何其他问题,包括或嵌入这样的库。
  • 问题是我无法确定我的项目中使用了哪些库(至少不能添加库)。
  • 这是您组织内的一个社会问题。我建议你向你的团队做出这个决定,并解释使用最佳实践单元测试工具来提高开发效率的好处。如果他们不相信你,你可以告诉互联网上的一个随机家伙告诉你:) 因为替代方案是从 Mock 库中删除代码,这很丑陋。
  • 查看@Jeeppler 的回答。模拟现在是标准的
【解决方案2】:

怎么样:

import time
from time import sleep as originalsleep

def newsleep(seconds):
    sleep_speed_factor = 10.0 
    originalsleep(seconds/sleep_speed_factor)

time.sleep = newsleep

这对我有用。我在测试开始时将其包括在内我想加快速度,最后我将原来的睡眠设置为以防万一。希望对你有帮助

【讨论】:

    【解决方案3】:

    使用freezegun 包可以帮助您做到这一点。

    # fake.py
    import functools
    from datetime import datetime, timedelta
    from unittest import mock
    
    from freezegun import freeze_time
    
    
    def fake_sleep(func):
        freezegun_control = None
    
        def fake_sleep(seconds):
            nonlocal freezegun_control
            utcnow = datetime.utcnow()
            if freezegun_control is not None:
                freezegun_control.stop()
            freezegun_control = freeze_time(utcnow + timedelta(seconds=seconds))
            freezegun_control.start()
    
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            with mock.patch('time.sleep', fake_sleep):
                rv = func(*args, **kwargs)
    
                if freezegun_control is not None:
                    freezegun_control.stop()
                return rv
    
        return wrapper
    
    
    # test.py
    from fake import fake_sleep
    
    import time
    
    @fake_sleep
    def test_sleep():
        now = datetime.utcnow()
    
        for sleep_seconds in range(10):
            for i in range(1, 10):
    
                time.sleep(sleep_seconds)
    
                assert datetime.utcnow() - now >= timedelta(
                    seconds=i * sleep_seconds)
    
    1. 常见demo:请看freezegun README
    2. pytest 演示:Gist fake sleep function fixture

    【讨论】:

    • 欢迎提供解决方案链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么会出现,然后引用最相关的内容您链接到的页面的一部分,以防目标页面不可用。 Answers that are little more than a link may be deleted.
    • 这不能回答问题。问题是关于修补time.sleep()freezegun 只修补datetimetime 中的一些相关功能,而不是time.sleep()
    • 使用with freeze_time(datetime.now() + timedelta(seconds=666)):时可以去掉time.sleep
    【解决方案4】:

    接受的答案仍然有效。但是,unittest.mock 从 Python 3.3 开始就是 Python 标准库的官方部分。

    import time
    from unittest import TestCase
    from unittest.mock import patch
    
    class TestMyCase(TestCase):
    
        @patch('time.sleep', return_value=None)
        def test_my_method(self, patched_time_sleep):
            time.sleep(60)  # Should be instant
    
            # the mock should only be called once
            self.assertEqual(1, patched_time_sleep.call_count)
            # or 
            patched_time_sleep.assert_called_once()
    
        # alternative version using a context manager
        def test_my_method_alternative(self):
            with patch('time.sleep', return_value=None) as patched_time_sleep:
                time.sleep(60)  # Should be instant
    
            # the mock should only be called once
            self.assertEqual(1, patched_time_sleep.call_count)
            # or 
            patched_time_sleep.assert_called_once()
    

    【讨论】:

    • 装饰器解决方案即使在我进行的所有方法调用中都适用于time.sleep,但上下文管理器解决方案却不行。关于为什么会发生这种差异的任何想法?
    【解决方案5】:

    我正在使用pytest 并有以下固定装置到猴子补丁time.sleep

    import pytest
    
    
    @pytest.fixture
    def sleepless(monkeypatch):
    
        def sleep(seconds):
            pass
    
        monkeypatch.setattr(time, 'sleep', sleep)
    

    然后在我需要“加速”睡眠的测试中,我只使用这个夹具:

    import time
    
    def test_sleep(sleepless):
        time.sleep(60)
    

    因此,当您运行此测试时,您会看到它在更短的时间内完成:

    = 1 passed in 0.02 seconds =
    

    【讨论】:

    • 在被测模块的方法中调用 time.sleep 时如何替换它?
    • 不要在测试中调用 time.sleep,而是在使用 time.sleep 的地方调用测试模块方法。请注意,如果您在模块中执行from time import sleep,而不是修补time 模块,则需要在monkey patch.setattr.. 行中修补您的模块。
    • 我想出了一个解决方案。模块 A 有行 from time import sleep。我将其更改为 import time 然后装饰器 @patch('time.sleep', return_value=None) 在测试包含模块 A 的模块 B 时工作。
    猜你喜欢
    • 1970-01-01
    • 2012-04-12
    • 2019-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-27
    • 1970-01-01
    相关资源
    最近更新 更多