Quantcast
Channel: Obey the Testing Goat!
Viewing all articles
Browse latest Browse all 39

How to unit test tornado ioloop callbacks

$
0
0

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 

Right. 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 self.wait, overriding 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 tearDown or 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!


Viewing all articles
Browse latest Browse all 39

Trending Articles