单元测试使用py.test时出现“RuntimeError: 超出应用上下文”
我正在尝试迁移到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, '/')
正如你所看到的,这和Flask网站上的文档非常相似:测试框架,而且,实际上,当我用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在处理模块中的文件时有什么不同吗?这让我觉得很奇怪,因为我明显是在调用app.test_client().get()时处于应用上下文中。这样的情况是正常的吗,还是我应该向py.test报告一个bug?
如果有帮助的话,我从父目录执行测试的原因是因为我无法将模块添加到site-packages,所以我从安装了Flask、py.test等的父目录启动我的所有代码。
编辑:解决了。问题出在安装上。添加了pythonpath标签,因为这就是解决方案。
3 个回答
你可以手动设置应用的上下文:
app = Flask(__name__)
ctx = app.app_context()
ctx.push()
with ctx:
pass
这不是直接回答TS问题,而是主要针对“应用上下文”错误。
在setUp和tearDown函数中添加上下文的推入和弹出应该能帮助解决这个错误:
def setUp(self):
self.app_context = app.app_context()
self.app_context.push()
def tearDown(self):
self.app_context.pop()
你可以在这里找到关于Flask上下文的更多信息:
还有这篇由Daniel Kronovet写的精彩文章:
另外,如果你打算在测试中使用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)
如果有相关的话,我之所以从父目录执行测试,是因为我不能往site-packages里添加模块,所以我从父目录启动我的所有代码,在那里我安装了Flask、py.test等。
结果发现这确实很重要。因为我的代码最终会被一个后台进程运行,我不确定能否依赖PYTHONPATH,而且我也不想在每个文件的开头都修改sys.path。所以,我安装了包的压缩文件,并在必要的地方添加了符号链接,以便能导入这些包。
为了让unittest代码找到flask,所需的结构是:
./Flask-0.10.1/
./flask -> Flask-0.10.1/flask/
./tmp1/flask -> ../Flask-0.10.1/flask/
最后一行是unittest所需要的,而正是这个符号链接导致了py.test出问题。
当我把包放在自己的目录里,并设置PYTHONPATH时,一切都正常。如果我不能控制环境来使用PYTHONPATH给后台进程,我就得硬着头皮,在每个地方添加sys.path的修改。
所以,总的来说,这是一个安装的问题。