WARNING: this is not battle-tested wisdom of a massively experienced tornado tester. Today was the first time we ever tried to test something that actually uses the ioloop, and we've probably got it all totally backwards. Still, in case it helps...
Async. It's always hard to wrap your head around, so perhaps it's not surprising that it took us a few goes at work today before we got the hang of it.
Here's a bit of code that adds a callback to the tornado ioloop:
def sort_that_out(mess): IOLoop.instance().add_callback(mess.sort)
How might one naively write a test for it?
class TestSortingStuffOut(unittest.TestCase): def test_stuff_get_sorted(self): stuff = [3,2,1] sort_that_out(mess) self.assertEqual(mess, [1, 2, 3])
Well, that doesn't work:
AssertionError: Lists differ: [3, 2, 1] != [1, 2, 3]
A little head-scratching will get you to the fact that it's because the tornado IOLoop hasn't actually been started, so our callback never gets run. So, let's fix that:
def test_stuff_get_sorted(self): stuff = [3,2,1] sort_that_out(stuff) IOLoop.instance().start() self.assertEqual(stuff, [1, 2, 3])
What about now? The test hangs, and a little Ctrl-C based profiling tells us where the busy loop is:
File "/tmp/t.py", line 13, in test_stuff_get_sorted IOLoop.instance().start() File "/usr/local/lib/python2.7/site-packages/tornado/ioloop.py", line 627, in start event_pairs = self._impl.poll(poll_timeout) KeyboardInterrupt
start() on the IOLoop is a blocking call, and just assumes the loop should
be run forever. At this point we ventured over to the official tornado
testing docs but they seem
to suggest a lot of overcomplicated things: using a
get_new_ioloop to return the singleton...
Actually, all you really need to do is this:
def test_stuff_get_sorted(self): stuff = [3,2,1] sort_that_out(stuff) IOLoop.instance().add_callback(IOLoop.instance().stop) IOLoop.instance().start() self.assertEqual(stuff, [1, 2, 3])
We just add our own callback, telling the loop to shut itself down, making sure that it's the last callback added before we start the loop. Voila!
. ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
Well, that was our first foray into writing a test for tornado that actually used the IOLoop
(all our other tests have just mocked everything). No doubt the tornado tools come in useful
for other use cases. And you'd probably want to use a
addCleanup that made
sure the IOLoop got shut down even when your test doesn't behave as expected....
But I though I'd post this in case anybody else has a simple requirement to test a tornado async callback, and finds the docs a little hard-going. Hope it helps!
I'm writing a book all about TDD and Web programming. Read the draft and let me know what you think!
A selection of links and videos about TDD, not necessarily all mine, eg this tutorial at PyCon 2013, how to motivate coworkers to write unit tests, thoughts on Django's test tools, London-style TDD and more.
This is my old TDD tutorial, which follows along with the official Django tutorial, but with full TDD. It badly needs updating. Read the book instead!
The campaign page, preserved for history, which led to the glorious presence of the Testing Goat on the front of the book.