Clock
.Impatient runners
Let's test that a lit bomb explodes with a message when it should. Or in other words, that a function returns a Deferred
which fires after a time. For convenience, I'm going to put the code to be tested in the same file as the test.
We'll need to import some things:
from unittest import TestCase
from twisted.internet import reactor
from twisted.internet.defer import Deferred
Here's the test:
class BombTest(TestCase):
def test_explodes(self):
def check(result):
self.assertEqual(result, 'exploded')
d = lightBomb(2, 'exploded')
d.addCallback(check)
And here's the function:
def lightBomb(fuse, message):
defer = Deferred()
reactor.callLater(fuse, defer.callback, message)
return defer
When we run it, we see that it passes the test:
test_bomb1
BombTest
test_explodes ... [OK]
-------------------------------------------------------------------------------
Ran 1 tests in 0.001s
PASSED (successes=1)
Yay!
No, not yay. Change the test to this:
def test_explodes(self):
def check(result):
print "I was executed with %r" % result
d = lightBomb(2, 'exploded')
d.addCallback(check)
And see that check
is never called:
test_bomb2
BombTest
test_explodes ... [OK]
-------------------------------------------------------------------------------
Ran 1 tests in 0.001s
PASSED (successes=1)
What gives?
Wait up!
Does it seem a little suspicious that the bomb is supposed to go off in 2 seconds and the test runs in less than 1 second? The problem is that the test runner is not waiting for the Deferred
to be called back. To wait for the Deferred
, we'll have to use twisted.trial.unittest.TestCase
and return the Deferred
from the test method:
from twisted.trial.unittest import TestCase
from twisted.internet import reactor
from twisted.internet.defer import Deferred
def test_explodes(self):
def check(result):
self.assertEqual(result, 'exploded')
d = lightBomb(2, 'exploded')
d.addCallback(check)
return d
test_bomb3
BombTest
test_explodes ... [OK]
-------------------------------------------------------------------------------
Ran 1 tests in 2.012s
PASSED (successes=1)
See how the test took more than 2 seconds to run this time?
Timeout
What if lightBomb
looked like this instead?
def lightBomb(fuse, message):
defer = Deferred()
return defer
Run the test and it will hang. Add a timeout to fail any test that takes too long:
class BombTest(TestCase):
timeout = 3
test_timeout
BombTest
test_explodes ... [ERROR]
===============================================================================
[ERROR]
Traceback (most recent call last):
Failure: twisted.internet.defer.TimeoutError: <test_timeout.BombTest testMethod=test_explodes> (test_explodes) still running at 3.0 secs
test_timeout.BombTest.test_explodes
-------------------------------------------------------------------------------
Ran 1 tests in 3.015s
FAILED (errors=1)
Hello, Clock
If you have a lot of tests dealing with scheduled events and timing, running all of them will take a long time. To avoid that, we can simulate the passage of time with the Clock
.
Here's the same test as test_bomb3.py using the Clock
(changed lines are highlighted):
from twisted.trial.unittest import TestCase
from twisted.internet.task import Clock
from twisted.internet import reactor
from twisted.internet.defer import Deferred
class BombTest(TestCase):
def test_explodes(self):
def check(result):
self.assertEqual(result, 'exploded')
clock = Clock()
d = lightBomb(2, 'exploded', reactor=clock)
d.addCallback(check)
clock.advance(2)
return d
def lightBomb(fuse, message, reactor=reactor):
defer = Deferred()
reactor.callLater(fuse, defer.callback, message)
return defer
test_clock
BombTest
test_explodes ... [OK]
-------------------------------------------------------------------------------
Ran 1 tests in 0.005s
PASSED (successes=1)
Yes! Much faster! Here are the Clock docs if you are interested. Note in test_clock.py how we had to make lightBomb
accept an optional reactor
.
Boom tests
All of the code for the current version of boom
was test-driven. Perhaps reading some of the tests will be enlightening -- though I make no claim at being a testing expert. The test code is available on GitHub.
No comments:
Post a Comment