{"id":4119,"date":"2015-05-01T12:15:42","date_gmt":"2015-05-01T09:15:42","guid":{"rendered":"http:\/\/www.webcodegeeks.com\/?p=4119"},"modified":"2015-04-25T15:27:28","modified_gmt":"2015-04-25T12:27:28","slug":"eventually-correct-async-testing-tornado","status":"publish","type":"post","link":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/","title":{"rendered":"Eventually Correct: Async Testing With Tornado"},"content":{"rendered":"<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/toad-vs-birdo.jpg\"><img decoding=\"async\" class=\"alignright size-medium wp-image-4124\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/toad-vs-birdo-300x144.jpg\" alt=\"toad-vs-birdo\" width=\"300\" height=\"144\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/toad-vs-birdo-300x144.jpg 300w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/toad-vs-birdo-1024x492.jpg 1024w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/toad-vs-birdo.jpg 1200w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Async frameworks like Tornado scramble our usual unittest strategies: how can you validate the outcome when you do not know when to expect it? Tornado ships with a <code>tornado.testing<\/code> module that provides two solutions: the <code>wait<\/code> \/ <code>stop<\/code> pattern, and <code>gen_test<\/code>.<br \/>\n&nbsp;<\/p>\n<ul>\n<li><a href=\"#wait-stop\">Wait \/ Stop<\/a><\/li>\n<li><a href=\"#gen_test\">gen_test<\/a><\/li>\n<li><a href=\"#further-study\">Further Study<\/a><\/li>\n<\/ul>\n<h2>Wait \/ Stop<\/h2>\n<p>To begin, let us say we are writing an async application with feature like Gmail&#8217;s <a href=\"https:\/\/support.google.com\/mail\/answer\/1284885?hl=en\">undo send<\/a>: when I click &#8220;send&#8221;, Gmail delays a few seconds before actually sending the email. It is a funny phenomenon, that during the seconds after clicking &#8220;sending&#8221; I experience a special clarity about my email. It was too angry, or I forgot an attachment, most often both. If I click the &#8220;undo&#8221; button in time, the email reverts to a draft and I can tone it down, add the attachment, and send it again.<\/p>\n<p>To write an application with this feature, we will need an asynchronous &#8220;delay&#8221; function, and we must test it. If we were testing a normal blocking delay function we could use <code>unittest.TestCase<\/code> from the standard library:<\/p>\n<pre class=\" brush:php\">import time\r\nimport unittest\r\n\r\nfrom my_application import delay\r\n\r\n\r\nclass MyTestCase(unittest.TestCase):\r\n    def test_delay(self):\r\n        start = time.time()\r\n        delay(1)\r\n        duration = time.time() - start\r\n        self.assertAlmostEqual(duration, 1, places=2)<\/pre>\n<p>When we run this, it prints:<\/p>\n<pre class=\" brush:php\">Ran 1 test in 1.000s\r\nOK<\/pre>\n<p>And if we replace <code>delay(1)<\/code> with <code>delay(2)<\/code> it fails as expected:<\/p>\n<pre class=\" brush:php\">=======================================================\r\nFAIL: test_delay (delay0.MyTestCase)\r\n-------------------------------------------------------\r\nTraceback (most recent call last):\r\nFile \"delay0.py\", line 12, in test_delay\r\n    self.assertAlmostEqual(duration, 1, places=2)\r\nAssertionError: 2.000854969024658 != 1 within 2 places\r\n\r\n-------------------------------------------------------\r\nRan 1 test in 2.002s\r\nFAILED (failures=1)<\/pre>\n<p>Great! What about testing a <code>delay_async(seconds, callback)<\/code> function?<\/p>\n<pre class=\" brush:php\">def test_delay(self):\r\n        start = time.time()\r\n        delay_async(1, callback=)  # What goes here?\r\n        duration = time.time() - start\r\n        self.assertAlmostEqual(duration, 1, places=2)<\/pre>\n<p>An asynchronous &#8220;delay&#8221; function can&#8217;t block the caller, so it must take a callback and execute it once the delay is over. (In fact we are just reimplementing Tornado&#8217;s <a href=\"http:\/\/tornado.readthedocs.org\/en\/latest\/ioloop.html#tornado.ioloop.IOLoop.call_later\"><code>call_later<\/code><\/a>, but please pretend for pedagogy&#8217;s sake this is a new function that we must test.) To test our <code>delay_async<\/code>, we will try a series of testing techniques until we have effectively built Tornado&#8217;s test framework from scratch\u2014you will see why we need special test tools for async code and how Tornado&#8217;s tools work.<\/p>\n<p>So, we define a function <code>done<\/code> to measure the delay, and pass it as the callback to <code>delay_async<\/code>:<\/p>\n<pre class=\" brush:php\">def test_delay(self):\r\n        start = time.time()\r\n\r\n        def done():\r\n            duration = time.time() - start\r\n            self.assertAlmostEqual(duration, 1, places=2)\r\n\r\n        delay_async(1, done)<\/pre>\n<p>If we run this:<\/p>\n<pre class=\" brush:php\">Ran 1 test in 0.001s\r\nOK<\/pre>\n<p>Success! &#8230;right? But why does it only take a millisecond? And what happens if we delay by two seconds instead?<\/p>\n<pre class=\" brush:php\">def test_delay(self):\r\n        start = time.time()\r\n\r\n        def done():\r\n            duration = time.time() - start\r\n            self.assertAlmostEqual(duration, 1, places=2)\r\n\r\n        delay_async(2, done)<\/pre>\n<p>Run it again:<\/p>\n<pre class=\" brush:php\">Ran 1 test in 0.001s\r\nOK<\/pre>\n<p>Something is very wrong here. The test appears to pass instantly, regardless of the argument to <code>delay_async<\/code>, because we neither start the event loop nor wait for it to complete. We have to actually pause the test until the callback has executed:<\/p>\n<pre class=\" brush:php\">def test_delay(self):\r\n        start = time.time()\r\n        io_loop = IOLoop.instance()\r\n\r\n        def done():\r\n            duration = time.time() - start\r\n            self.assertAlmostEqual(duration, 1, places=2)\r\n            io_loop.stop()\r\n\r\n        delay_async(1, done)\r\n        io_loop.start()<\/pre>\n<p>Now if we run the test with a delay of one second:<\/p>\n<pre class=\" brush:php\">Ran 1 test in 1.002s\r\nOK<\/pre>\n<p>That looks better. And if we delay for two seconds?<\/p>\n<pre class=\" brush:php\">ERROR:tornado.application:Exception in callback\r\nTraceback (most recent call last):\r\n  File \"site-packages\/tornado\/ioloop.py\", line 568, in _run_callback\r\n    ret = callback()\r\n  File \"site-packages\/tornado\/stack_context.py\", line 275, in null_wrapper\r\n    return fn(*args, **kwargs)\r\n  File \"delay3.py\", line 16, in done\r\n    self.assertAlmostEqual(duration, 1, places=2)\r\n  File \"unittest\/case.py\", line 845, in assertAlmostEqual\r\n    raise self.failureException(msg)\r\nAssertionError: 2.001540184020996 != 1 within 2 places<\/pre>\n<p>The test appears to fail, as expected, but there are a few problems. First, notice that it is not the unittest that prints the traceback: it is Tornado&#8217;s application logger. We do not get the unittest&#8217;s characteristic output. Second, the process is now hung and remains so until I type Control-C. Why?<\/p>\n<p>The bug is here:<\/p>\n<pre class=\" brush:php\">def done():\r\n            duration = time.time() - start\r\n            self.assertAlmostEqual(duration, 1, places=2)\r\n            io_loop.stop()<\/pre>\n<p>Since the failed assertion raises an exception, we never reach the call to <code>io_loop.stop()<\/code>, so the loop continues running and the process does not exit. We need to register an exception handler. Exception handling with callbacks is convoluted; we have to use a <a href=\"http:\/\/www.tornadoweb.org\/en\/branch2.3\/stack_context.html\">stack context<\/a> to install a handler with Tornado:<\/p>\n<pre class=\" brush:php\">from tornado.stack_context import ExceptionStackContext\r\n\r\nclass MyTestCase(unittest.TestCase):\r\n    def test_delay(self):\r\n        start = time.time()\r\n        io_loop = IOLoop.instance()\r\n\r\n        def done():\r\n            duration = time.time() - start\r\n            self.assertAlmostEqual(duration, 1, places=2)\r\n            io_loop.stop()\r\n\r\n        self.failure = None\r\n\r\n        def handle_exception(typ, value, tb):\r\n            io_loop.stop()\r\n            self.failure = value\r\n            return True  # Stop propagation.\r\n\r\n        with ExceptionStackContext(handle_exception):\r\n            delay_async(2, callback=done)\r\n\r\n        io_loop.start()\r\n        if self.failure:\r\n            raise self.failure<\/pre>\n<p>The loop can now be stopped two ways: if the test passes, then <code>done<\/code> stops the loop as before. If it fails, <code>handle_exception<\/code> stores the error and stops the loop. At the end, if an error was stored we re-raise it to make the test fail:<\/p>\n<pre class=\" brush:php\">=======================================================\r\nFAIL: test_delay (delay4.MyTestCase)\r\n-------------------------------------------------------\r\nTraceback (most recent call last):\r\n  File \"delay4.py\", line 31, in test_delay\r\n    raise self.failure\r\n  File \"tornado\/ioloop.py\", line 568, in _run_callback\r\n    ret = callback()\r\n  File \"tornado\/stack_context.py\", line 343, in wrapped\r\n    raise_exc_info(exc)\r\n  File \"&lt;string&gt;\", line 3, in raise_exc_info\r\n  File \"tornado\/stack_context.py\", line 314, in wrapped\r\n    ret = fn(*args, **kwargs)\r\n  File \"delay4.py\", line 17, in done\r\n    self.assertAlmostEqual(duration, 1, places=2)\r\nAssertionError: 2.0015950202941895 != 1 within 2 places\r\n-------------------------------------------------------\r\nRan 1 test in 2.004s\r\nFAILED (failures=1)<\/pre>\n<p>Now the test ends promptly, whether it succeeds or fails, with unittest&#8217;s typical output.<\/p>\n<p>This is a lot of tricky code to write just to test a trivial delay function, and it seems hard to get right each time. What does Tornado provide for us? Its <a href=\"http:\/\/www.tornadoweb.org\/en\/branch2.3\/testing.html\">AsyncTestCase<\/a> gives us <code>start<\/code> and <code>stop<\/code> methods to control the event loop. If we then move the duration-testing outside the callback we radically simplify our test:<\/p>\n<pre class=\" brush:php\">from tornado import testing\r\n\r\nclass MyTestCase(testing.AsyncTestCase):\r\n    def test_delay(self):\r\n        start = time.time()\r\n        delay_async(1, callback=self.stop)\r\n        self.wait()\r\n        duration = time.time() - start\r\n        self.assertAlmostEqual(duration, 1, places=2)<\/pre>\n<h2><code>gen_test<\/code><\/h2>\n<p>But modern async code is not primarily written with callbacks: these days we use <a href=\"http:\/\/tornado.readthedocs.org\/en\/latest\/guide\/coroutines.html\">coroutines<\/a>. Let us begin a new example test, one that uses <a href=\"https:\/\/motor.readthedocs.org\/\">Motor, my asynchronous MongoDB driver for Tornado<\/a>. Although Motor supports the old callback style, it encourages you to use coroutines and &#8220;yield&#8221; statements, so we can write some Motor code to demonstrate Tornado coroutines and unittesting.<\/p>\n<p>To begin, say we want to execute <a href=\"http:\/\/motor.readthedocs.org\/en\/latest\/api\/motor_collection.html#motor.MotorCollection.find_one\"><code>find_one<\/code><\/a> and test its return value:<\/p>\n<pre class=\" brush:php\">from motor import MotorClient\r\nfrom tornado import testing\r\n\r\nclass MyTestCase(testing.AsyncTestCase):\r\n    def setUp(self):\r\n        super().setUp()\r\n        self.client = MotorClient()\r\n\r\n    def test_find_one(self):\r\n        collection = self.client.test.collection\r\n        document = yield collection.find_one({'_id': 1})\r\n        self.assertEqual({'_id': 1, 'key': 'value'}, document)<\/pre>\n<p>Notice the &#8220;yield&#8221; statement: whenever you call a Motor method that does I\/O, you must use &#8220;yield&#8221; to pause the current function and wait for the returned Future object to be resolved to a value. Including a yield statement makes this function a generator. But now there is a problem:<\/p>\n<pre class=\" brush:php\">TypeError: Generator test methods should be decorated with tornado.testing.gen_test<\/pre>\n<p>Tornado smartly warns us that our test method is merely a generator\u2014we must decorate it with <a href=\"http:\/\/tornado.readthedocs.org\/en\/latest\/testing.html#tornado.testing.gen_test\">gen_test<\/a>. Otherwise the test method simply stops at the first yield, and never reaches the assert. It needs a coroutine <em>driver<\/em> to run it to completion:<\/p>\n<pre class=\" brush:php\">from tornado.testing import gen_test\r\n\r\nclass MyTestCase(testing.AsyncTestCase):\r\n    # ... same setup ...\r\n    @gen_test\r\n    def test_find_one(self):\r\n        collection = self.client.test.collection\r\n        document = yield collection.find_one({'_id': 1})\r\n        self.assertEqual({'_id': 1, 'key': 'value'}, document)<\/pre>\n<p>But now when I run the test, it fails:<\/p>\n<pre class=\" brush:php\">AssertionError: {'key': 'value', '_id': 1} != None<\/pre>\n<p>We need to insert some data in <code>setUp<\/code> so that <code>find_one<\/code> can find it! Since Motor is asynchronous, we cannot call its <code>insert<\/code> method directly from <code>setUp<\/code>, we must run the insertion in a coroutine as well:<\/p>\n<pre class=\" brush:php\">from tornado import gen, testing\r\n\r\nclass MyTestCase(testing.AsyncTestCase):\r\n    def setUp(self):\r\n        super().setUp()\r\n        self.client = MotorClient()\r\n        self.setup_coro()\r\n\r\n    @gen.coroutine\r\n    def setup_coro(self):\r\n        collection = self.client.test.collection\r\n\r\n        # Clean up from prior runs:\r\n        yield collection.remove()\r\n\r\n        yield collection.insert({'_id': 0})\r\n        yield collection.insert({'_id': 1, 'key': 'value'})\r\n        yield collection.insert({'_id': 2})<\/pre>\n<p>Now when I run the test:<\/p>\n<pre class=\" brush:php\">AssertionError: {'key': 'value', '_id': 1} != None<\/pre>\n<p>It still fails! When I check in the mongo shell whether my data was inserted, only two of the three expected documents are there:<\/p>\n<pre class=\" brush:php\">&gt; db.collection.find()\r\n{ \"_id\" : 0 }\r\n{ \"_id\" : 1, \"key\" : \"value\" }<\/pre>\n<p>Why is it incomplete? Furthermore, since the document I actually query <em>is<\/em> there, why did the test fail?<\/p>\n<p>When I called <code>self.setup_coro()<\/code> in <code>setUp<\/code>, I launched it as a <em>concurrent<\/em> coroutine. It began running, but I did not wait for it to complete before beginning the test, so the test may reach its <code>find_one<\/code> statement before the second document is inserted. Furthermore, <code>test_find_one<\/code> can fail quickly enough that <code>setup_coro<\/code> does not insert its third document before the whole test suite finishes, stopping the event loop and preventing the final document from ever being inserted.<\/p>\n<p>Clearly I must wait for the setup coroutine to complete before beginning the test. Tornado&#8217;s <code>run_sync<\/code> method is designed for uses like this:<\/p>\n<pre class=\" brush:php\">class MyTestCase(testing.AsyncTestCase):\r\n    def setUp(self):\r\n        super().setUp()\r\n        self.client = MotorClient()\r\n        self.io_loop.run_sync(self.setup_coro)<\/pre>\n<p>With my setup coroutine correctly executed, now <code>test_find_one<\/code> passes.<\/p>\n<h2>Further Study<\/h2>\n<p>Now we have seen two techniques that make async testing with Tornado as convenient and reliable as standard unittests. To learn more, see my page of <a href=\"http:\/\/emptysqua.re\/blog\/eventually-correct-links\/\">links related to this article<\/a>.<\/p>\n<p>Plus, stay tuned for the next book in the <a href=\"http:\/\/aosabook.org\/\">Architecture of Open Source Applications<\/a> series. It will be called &#8220;500 Lines or Less&#8221;, and my chapter is devoted to the implementation of coroutines in asyncio and Python 3.<\/p>\n<div class=\"attribution\">\n<table>\n<tbody>\n<tr>\n<td><span class=\"reference\">Reference: <\/span><\/td>\n<td><a href=\"http:\/\/emptysqua.re\/blog\/eventually-correct-async-testing-tornado\/\">Eventually Correct: Async Testing With Tornado<\/a> from our <a href=\"http:\/\/www.webcodegeeks.com\/wcg\/\">WCG partner<\/a> Jesse Davis at the <a href=\"http:\/\/emptysqua.re\/blog\/\">A. Jesse Jiryu Davis<\/a> blog.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Async frameworks like Tornado scramble our usual unittest strategies: how can you validate the outcome when you do not know when to expect it? Tornado ships with a tornado.testing module that provides two solutions: the wait \/ stop pattern, and gen_test. &nbsp; Wait \/ Stop gen_test Further Study Wait \/ Stop To begin, let us &hellip;<\/p>\n","protected":false},"author":29,"featured_media":1651,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[53],"tags":[179],"class_list":["post-4119","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-python","tag-tornado"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Eventually Correct: Async Testing With Tornado - Web Code Geeks - 2026<\/title>\n<meta name=\"description\" content=\"Async frameworks like Tornado scramble our usual unittest strategies: how can you validate the outcome when you do not know when to expect it? Tornado\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Eventually Correct: Async Testing With Tornado - Web Code Geeks - 2026\" \/>\n<meta property=\"og:description\" content=\"Async frameworks like Tornado scramble our usual unittest strategies: how can you validate the outcome when you do not know when to expect it? Tornado\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/\" \/>\n<meta property=\"og:site_name\" content=\"Web Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webcodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2015-05-01T09:15:42+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Jesse Davis\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@https:\/\/twitter.com\/jessejiryudavis\" \/>\n<meta name=\"twitter:site\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jesse Davis\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/\"},\"author\":{\"name\":\"Jesse Davis\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/294d6819cb410ef55f273ecf25426c56\"},\"headline\":\"Eventually Correct: Async Testing With Tornado\",\"datePublished\":\"2015-05-01T09:15:42+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/\"},\"wordCount\":1063,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"keywords\":[\"Tornado\"],\"articleSection\":[\"Python\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/\",\"name\":\"Eventually Correct: Async Testing With Tornado - Web Code Geeks - 2026\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"datePublished\":\"2015-05-01T09:15:42+00:00\",\"description\":\"Async frameworks like Tornado scramble our usual unittest strategies: how can you validate the outcome when you do not know when to expect it? Tornado\",\"breadcrumb\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#primaryimage\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.webcodegeeks.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Python\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/python\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Eventually Correct: Async Testing With Tornado\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"name\":\"Web Code Geeks\",\"description\":\"Web Developers Resource Center\",\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.webcodegeeks.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webcodegeeks\",\"https:\/\/x.com\/webcodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/294d6819cb410ef55f273ecf25426c56\",\"name\":\"Jesse Davis\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/2db0fdaadd8cd6b86d93e1205e30dd3b43e3c46b91826513ab618934c3db8cf9?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/2db0fdaadd8cd6b86d93e1205e30dd3b43e3c46b91826513ab618934c3db8cf9?s=96&d=mm&r=g\",\"caption\":\"Jesse Davis\"},\"description\":\"Jesse is a senior engineer at MongoDB in New York City. He specializes in Python, MongoDB drivers, and asynchronous frameworks.\",\"sameAs\":[\"http:\/\/emptysqua.re\/blog\/\",\"https:\/\/x.com\/https:\/\/twitter.com\/jessejiryudavis\"],\"url\":\"https:\/\/www.webcodegeeks.com\/author\/jesse-davis\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Eventually Correct: Async Testing With Tornado - Web Code Geeks - 2026","description":"Async frameworks like Tornado scramble our usual unittest strategies: how can you validate the outcome when you do not know when to expect it? Tornado","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/","og_locale":"en_US","og_type":"article","og_title":"Eventually Correct: Async Testing With Tornado - Web Code Geeks - 2026","og_description":"Async frameworks like Tornado scramble our usual unittest strategies: how can you validate the outcome when you do not know when to expect it? Tornado","og_url":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/","og_site_name":"Web Code Geeks","article_publisher":"https:\/\/www.facebook.com\/webcodegeeks","article_published_time":"2015-05-01T09:15:42+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","type":"image\/jpeg"}],"author":"Jesse Davis","twitter_card":"summary_large_image","twitter_creator":"@https:\/\/twitter.com\/jessejiryudavis","twitter_site":"@webcodegeeks","twitter_misc":{"Written by":"Jesse Davis","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#article","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/"},"author":{"name":"Jesse Davis","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/294d6819cb410ef55f273ecf25426c56"},"headline":"Eventually Correct: Async Testing With Tornado","datePublished":"2015-05-01T09:15:42+00:00","mainEntityOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/"},"wordCount":1063,"commentCount":0,"publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","keywords":["Tornado"],"articleSection":["Python"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/","url":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/","name":"Eventually Correct: Async Testing With Tornado - Web Code Geeks - 2026","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#primaryimage"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","datePublished":"2015-05-01T09:15:42+00:00","description":"Async frameworks like Tornado scramble our usual unittest strategies: how can you validate the outcome when you do not know when to expect it? Tornado","breadcrumb":{"@id":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#primaryimage","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2014\/11\/python-logo.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/www.webcodegeeks.com\/python\/eventually-correct-async-testing-tornado\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.webcodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"Python","item":"https:\/\/www.webcodegeeks.com\/category\/python\/"},{"@type":"ListItem","position":3,"name":"Eventually Correct: Async Testing With Tornado"}]},{"@type":"WebSite","@id":"https:\/\/www.webcodegeeks.com\/#website","url":"https:\/\/www.webcodegeeks.com\/","name":"Web Code Geeks","description":"Web Developers Resource Center","publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.webcodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.webcodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.webcodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webcodegeeks","https:\/\/x.com\/webcodegeeks"]},{"@type":"Person","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/294d6819cb410ef55f273ecf25426c56","name":"Jesse Davis","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/2db0fdaadd8cd6b86d93e1205e30dd3b43e3c46b91826513ab618934c3db8cf9?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/2db0fdaadd8cd6b86d93e1205e30dd3b43e3c46b91826513ab618934c3db8cf9?s=96&d=mm&r=g","caption":"Jesse Davis"},"description":"Jesse is a senior engineer at MongoDB in New York City. He specializes in Python, MongoDB drivers, and asynchronous frameworks.","sameAs":["http:\/\/emptysqua.re\/blog\/","https:\/\/x.com\/https:\/\/twitter.com\/jessejiryudavis"],"url":"https:\/\/www.webcodegeeks.com\/author\/jesse-davis\/"}]}},"_links":{"self":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/4119","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/users\/29"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/comments?post=4119"}],"version-history":[{"count":0,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/4119\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media\/1651"}],"wp:attachment":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media?parent=4119"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/categories?post=4119"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/tags?post=4119"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}