Reputation: 1396
Let's suppose I am using the schedule library on a Ubuntu remote server to trigger an event every 3 days.
Code should look something similar to this:
import schedule
import time
def job():
print("I'm working...")
schedule.every(3).days.at("10:30").do(job)
while True:
schedule.run_pending()
time.sleep(1)
How can I speed up my clock or test this code?
Upvotes: 5
Views: 3984
Reputation: 2307
For reference, it is also possible to just fake the clock using the freezegun library:
from datetime import datetime, timedelta
import schedule
import freezegun
def job():
print("I'm working...")
now = datetime(2020, 1, 1, 10, 31)
with freezegun.freeze_time(now) as frozen_date:
schedule.every(3).days.at("10:30").do(job)
schedule.run_pending() # nothing happens
frozen_date.move_to(now + timedelta(days=3))
schedule.run_pending() # job has run
Upvotes: 1
Reputation: 13767
Instead of faking out the clock, you can test this code in a different way.
Obviously you can write a unit test for the job()
function (i.e. make sure it is doing what it's supposed to). This is probably obvious to you. You're probably wondering how you can test that the main script will call your function properly, but you can't ensure it is 10:30 when you run your tests.
Enter the famous "monkey patch". Since Python has first class functions, you can simply bind names to functions. I'm not going to get into the unit test framework and how to use the mock
library, but here's a quick example of what you might be looking for:
import schedule
import time
def mock_run_pending():
job()
def mock_time_sleep(num):
exit()
schedule.run_pending = mock_run_pending
time.sleep = mock_time_sleep
def job():
print("I'm working...")
schedule.every(3).days.at("10:30").do(job)
while True:
schedule.run_pending()
time.sleep(1)
So what's going on here? If you run the code snippet, you'll notice that the job
function is actually called! It is called exactly once, and then the program exits. The reason is, we are simply rebinding the names of the functions in the time
and schedule
modules to our "mock" versions (to make them more testable).
Edit: We can go even crazier and test that we are passing in the proper args to the scheduler (I couldn't help myself):
import schedule
import time
class MockEvery(object):
def __init__(self, num):
assert(num == 3)
self.days = MockDays()
class MockDays(object):
def __init__(self):
pass
def at(self, time):
assert(time == "10:30")
return MockAt()
class MockAt(object):
def __init__(self):
pass
def do(self, func):
assert(func == job)
def mock_every(num):
return MockEvery(num)
def mock_run_pending():
job()
def mock_time_sleep(num):
exit()
schedule.run_pending = mock_run_pending
time.sleep = mock_time_sleep
schedule.every = mock_every
def job():
print("I'm working...")
schedule.every(3).days.at("10:30").do(job)
while True:
schedule.run_pending()
time.sleep(1)
P.S. (this is somewhat irrelevant to your question, but you might find it useful): If you're looking to incorporate this into a testing framework, check out the mock
package, and in particular, the @patch
decorator. It allows you to monkey patch functions inside of a unit test (or any function for that matter). You'll also not want to call exit()
if you have other tests. It's easy to exit out of a particular test with pytest
, it's a bit harder to do with the built-in unittest
, but certainly possible. But, I digress.
HTH.
Upvotes: 3