jan
jan

Reputation: 987

Tornado unittesting with tornado.options

I am trying to write unit tests for a Tornado web app and I have trouble on how to properly test code that uses the options module. I define several options and access them in multiple places in the application, hence, I want to test different option combinations in the unit test.

A very simple example that contains everything would be:

import tornado.web
from tornado.options import define, options
import tornado.ioloop


def define_options():
    define('myoption', type=str, default='MyValue')


class ExampleHandler(tornado.web.RequestHandler):
    def get(self):
        self.write({
            'myoption': options.myoption
        })


def make_app():
    ROUTES = [
        (r'/', ExampleHandler)
    ]
    return tornado.web.Application(ROUTES)


def run():
    define_options()
    app = make_app()
    app.listen(8080)
    tornado.ioloop.IOLoop.current().start()


if __name__ == '__main__':
    run()

The big issue seems to be that the options are persisted over all tests that are being run and I don't see where I should call define_options and how to change single test parameters without potentially affecting all other tests. Take the following example of a test code:

import tornado.testing
from demo import define_options, make_app

class Test1(tornado.testing.AsyncHTTPTestCase):
    def setUp(self):
        define_options()
        super().setUp()

    def get_app(self):
        return make_app()

    def test1(self):
        response = self.fetch('/')
        print(response)

This works fine, but as soon as I add a second test method:

def test2(self):
    response = self.fetch('/')
    print(response)

I will get an error like this:

File "/Users/jan/Documents/demo/demo/demo.py", line 7, in define_options
    define('myoption', type=str, default='MyValue')
  File "/Users/jan/anaconda/envs/py3k/lib/python3.4/site-packages/tornado/options.py", line 558, in define
    callback=callback)
  File "/Users/jan/anaconda/envs/py3k/lib/python3.4/site-packages/tornado/options.py", line 228, in define
    (name, self._options[name].file_name))
tornado.options.Error: Option 'myoption' already defined in /Users/jan/Documents/demo/demo/demo.py

I can move the define_options to setUpClass but then I will see the same problem once I add a second test class. Hence, I am wondering if anyone has run into that issue and what solution I could use to run these tests. Not only where to put define_options so it is only run once, but also how I could define a different set of options (starting from the defaults given in define_options) for different tests without tests affecting each other.

Upvotes: 0

Views: 1055

Answers (1)

Ben Darnell
Ben Darnell

Reputation: 22134

tornado.options can be used in two ways:

  • Using global singletons. In this mode, you call tornado.options.define at import time, access the results via tornado.options.options, and parse the command line (or config file) with top-level functions in the tornado.options module. In this example, you'd remove your define_options() function and just define all your options at top level.
  • Using distinct tornado.options.OptionParser objects. In this mode, you'd create an OptionParser() object in your define_options() function, call its define() and parse methods instead of the functions in the options module, and return it so that other code accesses the values through this object instead oftornado.options.options`.

Note that the global singleton style is the way that the tornado.options module was originally designed and is intended to be used (it draws inspiration from Google's C++ gflags package). One thing that can be a bit tricky in this style is to temporarily change a flag's value for a test. The unittest.mock package has a little trouble with the magic objects used in tornado.options, so you must use a helper method, mockable().

Upvotes: 1

Related Questions