Reputation: 15347
I want to make a stub to prevent time.sleep(..) to sleep to improve the unit test execution time.
What I have is:
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)
However, I get the following error on the second code line above (class definition):
TypeError: Error when calling the metaclass bases
module.__init__() takes at most 2 arguments (3 given).
How to fix the error?
Upvotes: 35
Views: 47455
Reputation: 978
Here is a slightly different variation on the aforementioned answers (allowing arguments to be logged during unit test execution while logging args passed):
I had a quick and dirty back off function like like the following (there's probably several libraries to do this better, but I didn't feel like googling - I guess I ended up here anyhow though).
def backoff(retries_left: int = 3, retries_max: int = 3) -> int:
'''
Linear 15 second backoff
'''
sleep_time = (retries_max - retries_left) * 15
if sleep_time > 0:
logging.warning('Retrying in %d seconds...', sleep_time)
time.sleep(sleep_time)
return sleep_time
Here's how I wrote the unit test around the expected values
import unittest
from unittest.mock import patch
import my_app as app
def mock_sleep(sleep_time: int = 0):
'''
Mock the sleep function.
'''
app.logging.info('time.sleep called with %d seconds', sleep_time)
class TestApp(unittest.TestCase):
'''
Test cases for the app module.
'''
@patch('time.sleep', mock_sleep)
def test_backoff(self):
'''
Test the backoff function.
'''
values = {
3: 0,
2: 15,
1: 30,
0: 45
}
for key, value in values.items():
self.assertEqual(
app.backoff(key),
value
)
self.assertEqual(
app.backoff(retries_left=1, retries_max=5),
60
)
Output for the unit tests are like:
2024-05-08 10:06:41,047 WARNING Retrying in 15 seconds...
2024-05-08 10:06:41,047 INFO time.sleep called with 15 seconds
2024-05-08 10:06:41,047 WARNING Retrying in 30 seconds...
2024-05-08 10:06:41,047 INFO time.sleep called with 30 seconds
2024-05-08 10:06:41,047 WARNING Retrying in 45 seconds...
2024-05-08 10:06:41,047 INFO time.sleep called with 45 seconds
2024-05-08 10:06:41,047 WARNING Retrying in 60 seconds...
2024-05-08 10:06:41,047 INFO time.sleep called with 60 seconds
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Taking this a step further I suppose *args
and **kwargs
could be referenced instead of a predefined value like in the above mocked function example.
Upvotes: 1
Reputation: 3253
Here's what I did to prevent the test from sleeping:
If I have a module mymodule.py
that imports and uses sleep
in a function that I want to test:
from time import sleep
def some_func()
sleep(5)
# ...do some things
I then have my test import sleep from the module that is using it, like this:
@mock.patch('mymodule.sleep')
def test_some_func(mock_sleep):
mock_sleep.return_value = None
# ...continue my test
Upvotes: 8
Reputation: 513
The accepted answer is still valid. However, unittest.mock is since Python 3.3 an official part of the Python standard library.
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()
Upvotes: 22
Reputation: 4213
I'm using pytest and have following fixture to monkey patch time.sleep
:
import pytest
@pytest.fixture
def sleepless(monkeypatch):
def sleep(seconds):
pass
monkeypatch.setattr(time, 'sleep', sleep)
Then in test which I need to "speedup" the sleep, I just use this fixture:
import time
def test_sleep(sleepless):
time.sleep(60)
So when you run this test, you will see that it completes in much shorter time:
= 1 passed in 0.02 seconds =
Upvotes: 17
Reputation: 713
What about:
import time
from time import sleep as originalsleep
def newsleep(seconds):
sleep_speed_factor = 10.0
originalsleep(seconds/sleep_speed_factor)
time.sleep = newsleep
This is working for me. I am inlcuding it at the beginning of the test I want to speed up, at the end I set back the original sleep just in case. Hope it helps
Upvotes: 3
Reputation: 188
using freezegun package can help you to do this.
# 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)
- common demo: please see the freezegun README
- pytest demo: Gist fake sleep function fixture
Upvotes: -2
Reputation: 83778
You can use mock library in your tests.
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
Upvotes: 53