" RuntimeError:在应用程序上下文之外工作"用py.test进行单元测试时

时间:2014-07-22 00:30:13

标签: python unit-testing flask pytest pythonpath

我尝试迁移到py.test以便于使用和自动发现测试。当我使用unittest运行测试时,测试工作正常。当我在py.test下运行测试时,我得到RuntimeError: working outside of application context

这是测试代码(test_app.py):

import unittest

from app import app

class TestAPILocally(unittest.TestCase):
    def setUp(self):
        self.client = app.test_client()

    def testRoot(self):
        retval = self.client.get('/').data
        self.assertTrue('v1' in retval)

if __name__ == '__main__':
    unittest.main()

这是我正在测试的精简文件(app.py):

from flask import Flask
from flask.ext.restful import Api, Resource

class APIListAPI(Resource):
    def get(self):
        return ['v1']

app = Flask(__name__)
api = Api(app)
api.add_resource(APIListAPI, '/')

正如您所看到的,这与烧瓶网站上的文档非常相似:the testing skeleton,实际上,当我使用unittest运行它时,它会成功:

$ python tmp1/test_app.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.115s

OK
$ 

但是,当我用py.test测试时,它失败了:

$ ./py.test tmp1/test_app.py
=================== test session starts =========================
platform sunos5 -- Python 2.7.5 -- py-1.4.22 -- pytest-2.6.0
collected 1 items

tmp1/test_app.py F

========================= FAILURES ==============================
_________________ TestAPILocally.testRoot _______________________

self = <tmp1.test_app.TestAPILocally testMethod=testRoot>

    def testRoot(self):
>       retval = self.client.get('/').data

tmp1/test_app.py:10:
 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
werkzeug/test.py:762: in get
    return self.open(*args, **kw)
flask/testing.py:108: in open
    follow_redirects=follow_redirects)
werkzeug/test.py:736: in open
    response = self.run_wsgi_app(environ, buffered=buffered)
werkzeug/test.py:659: in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
werkzeug/test.py:855: in run_wsgi_app
    app_iter = app(environ, start_response)
tmp1/flask/app.py:1836: in __call__
    return self.wsgi_app(environ, start_response)
tmp1/flask/app.py:1820: in wsgi_app
    response = self.make_response(self.handle_exception(e))
flask_restful/__init__.py:256: in error_router
    if self._has_fr_route():
flask_restful/__init__.py:237: in _has_fr_route
    if self._should_use_fr_error_handler():
flask_restful/__init__.py:218: in _should_use_fr_error_handler
    adapter = current_app.create_url_adapter(request)
werkzeug/local.py:338: in __getattr__ 
    return getattr(self._get_current_object(), name)
werkzeug/local.py:297: in _get_current_object
    return self.__local()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def _find_app():
        top = _app_ctx_stack.top
        if top is None:
>           raise RuntimeError('working outside of application context')
E           RuntimeError: working outside of application context

flask/globals.py:34: RuntimeError
================ 1 failed in 1.02 seconds ======================

现在,事实证明,我可以通过这样做来完成这个测试:

$ rm tmp1/__init__.py

通过这样做让它再次失败:

$ touch tmp1/__init__.py

那么,unittest和py.test处理模块中文件的方式有什么区别吗?看起来非常奇怪,它足以让Flask抱怨,因为我在app上下文中明确 am ,调用app.test_client()。get()。这是预期的行为,还是应该针对py.test提交错误?

如果它是相关的,我从父目录执行测试的原因是因为我没有能力将模块添加到网站包,所以我&#39;从父目录启动我的所有代码,我在那里安装了Flask,py.test等。

编辑:已解决。这是一个安装问题。添加pythonpath标记,因为那是解决方案。

3 个答案:

答案 0 :(得分:7)

不直接回答TS问题,但主要是针对“应用程序上下文”错误。

在setUp和tearDown函数中添加推送和弹出上下文应该有助于解决此错误:

def setUp(self):
    self.app_context = app.app_context()
    self.app_context.push()

def tearDown(self):
    self.app_context.pop()

您可以在那里找到有关瓶子背景的更多信息:

还有 Daniel Kronovet

这篇精彩文章

PS如果您打算在测试中使用 url_for ,则需要进行其他配置:

@classmethod
def setUpClass(cls)
    app.config['SERVER_NAME'] = 'localhost:5000'

实施例

class ViewsTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        app.config['SERVER_NAME'] = 'localhost:5000'
        cls.client = app.test_client()

    def setUp(self):
        self.app_context = app.app_context()
        self.app_context.push()

    def tearDown(self):
        self.app_context.pop()

    def test_view_should_respond(self):
        r = self.client.get(url_for("index"))
        self.assertEqual(r.status_code, 200)

答案 1 :(得分:5)

您可以手动设置应用上下文:

app = Flask(__name__)
ctx = app.app_context()
ctx.push()

with ctx:
    pass

答案 2 :(得分:4)

  

如果它是相关的,我从父目录执行测试的原因是因为我没有能力将模块添加到网站包,所以我&#39;从父目录启动我的所有代码,我在那里安装了Flask,py.test等。

事实证明这非常重要。因为我的代码最终会由守护进程运行,所以我不确定我是否可以依赖PYTHONPATH,而且我不想在每个文件的顶部更改sys.path。所以,我正在安装包tarball,并在必要时添加符号链接以使包可导入。

事实证明,使单元测试代码找到烧瓶所需的结构是:

./Flask-0.10.1/
./flask -> Flask-0.10.1/flask/
./tmp1/flask -> ../Flask-0.10.1/flask/

unittest需要最后一行,而那个符号链接破坏了py.test。

当我将软件包放在他们自己的目录中并设置PYTHONPATH时,一切正常。如果我无法控制环境以便为守护程序使用PYTHONPATH,我会咬紧牙关,并在任何地方添加sys.path修改。

所以,最重要的是,这是一个安装问题。