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