为什么我的Flask应用程序的全局对象在app拆卸时被删除了?

时间:2016-08-11 17:29:11

标签: unit-testing python-3.x flask contextmanager flask-testing

我正在编写一个带有全局对象的应用程序,只要应用程序处于活动状态,它就应该存在。不同的端点应该改变全局对象。

下面是我的服务器,其中有一个示例模型对象,可以在虚拟端点调用上进行变异。

server.py

#!/usr/bin/env python3

from flask import Flask, g, request

class Foo(object):
    def __init__(self):
        self.bar = None

    def add_bar(self, bar):
        if self.bar is not None:
            raise Exception("You blew it!")
        self.bar = bar

def create_app():
    app = Flask(__name__)
    return app

app = create_app()

def get_foo():
    foo = getattr(g, '_foo', None)
    if foo is None:
        print("foo is None. Creating a foo")
        foo = g._foo = Foo()
    return foo

@app.teardown_appcontext
def teardown_foo(exception):
    foo = getattr(g, '_foo', None)
    if foo is not None:
        print("Deleting foo")
        del foo

@app.route("/add_bar", methods=['POST'])
def bar():
    bar = request.form.get("bar")
    foo.add_bar(bar)
    return "Success"    

if __name__ == "__main__":
    app = create_app()
    app.run('localhost', port=8080)

就像一个优秀的软件开发人员,我想测试一下。这是我的测试套件:

test.py

#!/usr/bin/env python3

from server import *
import unittest

class FlaskTestCase(unittest.TestCase):
    def setUp(self):
        app.config['TESTING'] = True
        print("Creating an app...")
        self.app = create_app()
        print("Created an app...")
        with self.app.app_context():
            self.foo = get_foo()

    def tearDown(self):
        del self.foo

    def test_add_bar(self):
        with self.app.test_client() as client:
            client.post("/add_bar", data={'bar': "12345"})
            assert self.foo.bar == "12345"

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

当我运行测试时,我注意到我的foo对象永远不会被删除(通过打印)。这是我运行测试套件时的输出:

13:35 $ ./test.py
Creating an app...
Created an app...
foo is None. Creating a foo
F
======================================================================
FAIL: test_add_bar (__main__.FlaskTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test.py", line 21, in test_add_bar
    assert self.foo.bar == "12345"
AssertionError

----------------------------------------------------------------------
Ran 1 test in 0.009s

FAILED (failures=1)

对于应用程序上下文,我必须对我的全局对象做错误的操作。我对testing documents进行了仔细研究,但似乎找不到我需要的东西。

当应用程序上下文超出范围时(如在我的测试中),如何确保我的foo对象被销毁?为什么我的单元测试套件不会改变在foo上创建的setUp对象?

我认为可以修改app中的全局server.py对象,但是当我打印foo时,我发现它并未在我的测试范围内定义套件。

2 个答案:

答案 0 :(得分:0)

因为del foo只取消绑定变量foo。如果您有foo = g._foo,则调用del foo不会取消绑定g._foo,而只取消绑定foo

del不用于删除对象。它从范围中释放变量名称。

答案 1 :(得分:0)

这是一个奇怪的问题。

问题是app中的全局server.py对象与原始test.py内创建的应用之间存在干扰。具体来说,由于应用程序是导入的,因此它的生命周期就是测试套件的生命周期。

我使用app删除了全局flask.Blueprint对象。这是最后的server.py

#!/usr/bin/env python3

from flask import Flask, g, request, Blueprint

main = Blueprint('main', __name__)

def get_foo():
    foo = getattr(g, 'foo', None)
    if foo is None:
        foo = g.foo = Foo()
    return foo

@main.before_request
def before_request():
    foo = get_foo()

@main.route("/add_bar", methods=['POST'])
def bar():
    bar = request.form.get("bar")
    g.foo.add_bar(bar)
    return "Success"

class Foo(object):
    def __init__(self):
        self.bar = None

    def add_bar(self, bar):
        if self.bar is not None:
            raise Exception("You blew it!")
        self.bar = bar

def create_app():
    app = Flask(__name__)
    app.register_blueprint(main)
    @app.teardown_appcontext
    def teardown_foo(exception):
        if g.foo is not None:
            del g.foo
    return app

if __name__ == "__main__":
    app = create_app()
    with app.app_context():
        foo = get_foo()
        app.run('localhost', port=8080)

这是最后的test.py

#!/usr/bin/env python3

from server import create_app, get_foo
import unittest

class FlaskTestCase(unittest.TestCase):
    def setUp(self):
        self.app = create_app()
        self.app_context = self.app.app_context()
        self.app_context.push()
        self.client = self.app.test_client()
        self.foo = get_foo()

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

    def test_add_bar_success(self):
        assert self.foo.bar is None
        self.client.post("/add_bar", data={'bar': "12345"})
        assert self.foo.bar == "12345"

    def test_foo_reset_on_new_test(self):
        assert self.foo.bar is None

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

test_foo_reset_on_new_test说明了每个测试都会重置与测试套件关联的foo对象。

所有测试都通过。