使用py.test进行单元测试时出现“RuntimeError:working outside of application context”的错误。

8
我希望使用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在处理模块中的文件方面是否有区别呢?很奇怪它会出现足以使Flask感到不满的故障,因为很明显我正在应用上下文中调用app.test_client().get()。这是预期行为还是我应该对py.test提出错误报告呢?
如果相关的话,我从父目录执行测试的原因是因为我没有将模块添加到site-packages的能力,所以我从安装了Flask、py.test等的父目录启动了所有代码。
编辑:问题已解决。这是一个安装问题。添加pythonpath标记,因为那是解决方案。

1
无法重现。您能否添加使用的版本、文件结构和运行测试的命令? - tbicr
@tbicr - 谢谢。我用虚拟机进一步研究了一下,如果我使用pip安装flask就不会出现这种情况。我马上会发布一个答案,解释出了什么问题。 - John Hazen
3个回答

11

虽然不是直接回答 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)

我需要从我的应用程序中删除 .test_client() 才能使其正常工作。 - matthewlent

8

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

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

with ctx:
    pass

4
如果相关的话,我从父级目录执行测试的原因是因为我没有添加模块到site-packages的能力,所以我从父级目录启动所有的代码,在那里我安装了Flask、py.test等。事实证明这非常重要。因为我的代码最终将由守护进程运行,我不确定是否可以依赖PYTHONPATH,并且我不想在每个文件的顶部更改sys.path。因此,我正在安装包tarballs,并在必要时添加符号链接使包可导入。结果证明,使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修改。

因此,底线是,这是一个安装问题。


您可以使用virtualenv和virtualenvwrapper来全局添加文件夹到sys.path,从而避免在代码中进行此操作。 - varela

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接