使用 tornado.options 进行 Tornado 单元测试

Tornado unittesting with tornado.options

我正在尝试为 Tornado 网络应用程序编写单元测试,但我在如何正确测试使用选项模块的代码方面遇到了问题。我定义了几个选项并在应用程序的多个位置访问它们,因此,我想在单元测试中测试不同的选项组合。

一个包含所有内容的非常简单的示例是:

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()

最大的问题似乎是这些选项在所有正在 运行 的测试中都存在,我不知道我应该在哪里调用 define_options 以及如何在没有的情况下更改单个测试参数可能影响所有其他测试。下面以一段测试代码为例:

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)

这工作正常,但一旦我添加第二个测试方法:

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

我会得到这样的错误:

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

我可以将 define_options 移动到 setUpClass,但是一旦我添加第二个测试 class,我就会看到同样的问题。因此,我想知道是否有人 运行 解决了这个问题,我可以使用什么解决方案来 运行 这些测试。不仅把 define_options 放在哪里所以它只有 运行 一次,而且我还可以为不同的测试定义一组不同的选项(从 define_options 中给出的默认值开始)而不进行测试互相影响。

tornado.options有两种用法:

  • 使用全局单例。 在这种模式下,您在导入时调用 tornado.options.define ,通过 tornado.options.options,并在tornado.options模块中使用顶级函数解析命令行(或配置文件)。在此示例中,您将删除 define_options() 函数并仅在顶层定义所有选项。
  • 使用不同的 tornado.options.OptionParser 对象。 在这种模式下,您将在 define_options() 函数中创建一个 OptionParser() 对象,调用它define() 和解析方法而不是 options 模块中的函数, and return it so that other code accesses the values through this object instead oftornado.options.options`。

请注意,全局单例样式是 tornado.options 模块最初设计并打算使用的方式(它从 Google 的 C++ gflags 包中汲取灵感).在这种风格中可能有点棘手的一件事是临时更改标志的值以进行测试。 unittest.mock 包在 tornado.options 中使用的魔法对象有点麻烦,所以你必须使用辅助方法,mockable().