Flask-Testing

Flask-Testing 扩展为 Flask 提供了单元测试的工具。

安装 Flask-Testing

使用 pip 或者 easy_install 安装:

pip install Flask-Testing

或者从版本控制系统(github)中下载最新的版本:

git clone https://github.com/jarus/flask-testing.git
cd flask-testing
python setup.py develop

如果你正在使用 virtualenv,假设你会安装 Flask-Testing 在运行你的 Flask 应用程序的同一个 virtualenv 上。

编写测试用例

简单地继承 TestCase 的 MyTest:

from flask.ext.testing import TestCase

class MyTest(TestCase):

    pass

你必须定义 create_app 方法,该方法返回一个 Flask 实例:

from flask import Flask
from flask.ext.testing import TestCase

class MyTest(TestCase):

    def create_app(self):

        app = Flask(__name__)
        app.config['TESTING'] = True
        return app

如果不定义 create_appNotImplementedError 异常将会抛出。

使用 LiveServer 测试

如果你想要你的测试通过 Selenium 或者 无头浏览器(无头浏览器的意思就是无外设的意思,可以在命令行下运行的浏览器)运行,你可以使用 LiveServerTestCase:

import urllib2
from flask import Flask
from flask.ext.testing import LiveServerTestCase

class MyTest(LiveServerTestCase):

    def create_app(self):
        app = Flask(__name__)
        app.config['TESTING'] = True
        # Default port is 5000
        app.config['LIVESERVER_PORT'] = 8943
        return app

    def test_server_is_up_and_running(self):
        response = urllib2.urlopen(self.get_server_url())
        self.assertEqual(response.code, 200)

在这个例子中 get_server_url 方法将会返回 http://localhost:8943

测试 JSON 响应

如果你正在测试一个返回 JSON 的视图函数的话,你可以使用 Response 对象的特殊的属性 json 来测试输出:

@app.route("/ajax/")
def some_json():
    return jsonify(success=True)

class TestViews(TestCase):
    def test_some_json(self):
        response = self.client.get("/ajax/")
        self.assertEquals(response.json, dict(success=True))

选择不渲染模板

当测试需要处理模板渲染的时候可能是一个大问题。如果在测试中你不想要渲染模板的话可以设置 render_templates 属性:

class TestNotRenderTemplates(TestCase):

    render_templates = False

    def test_assert_not_process_the_template(self):
        response = self.client.get("/template/")

        assert "" == response.data

尽管可以设置不想渲染模板,但是渲染模板的信号在任何时候都会发送,你也可以使用 assert_template_used 方法来检查模板是否被渲染:

class TestNotRenderTemplates(TestCase):

    render_templates = False

    def test_assert_mytemplate_used(self):
        response = self.client.get("/template/")

        self.assert_template_used('mytemplate.html')

当渲染模板被关闭的时候,测试执行起来会更加的快速并且视图函数的逻辑将会孤立地被测试。

使用 Twill

Twill 是一个用来通过使用命令行界面浏览网页的简单的语言。

Note

请注意 Twill 只支持 Python 2.x,不能在 Python 3 或者以上版本上使用。

Flask-Testing 拥有一个辅助类用来创建使用 Twill 的功能测试用例:

def test_something_with_twill(self):

    with Twill(self.app, port=3000) as t:
        t.browser.go(t.url("/"))

旧的 TwillTestCase 类已经被弃用。

测试 SQLAlchemy

这部分将会涉及使用 Flask-Testing 测试 SQLAlchemy 的一部分内容。这里假设你使用的是 Flask-SQLAlchemy 扩展,并且这里的例子也不是太难,可以适用于用户自己的配置。

首先,先确保数据库的 URI 是设置成开发环境而不是生产环境!其次,一个好的测试习惯就是在每一次测试执行的时候先创建表,在结束的时候删除表,这样保证干净的测试环境:

from flask.ext.testing import TestCase

from myapp import create_app, db

class MyTest(TestCase):

    SQLALCHEMY_DATABASE_URI = "sqlite://"
    TESTING = True

    def create_app(self):

        # pass in test configuration
        return create_app(self)

    def setUp(self):

        db.create_all()

    def tearDown(self):

        db.session.remove()
        db.drop_all()

同样需要注意地是每一个新的 SQLAlchemy 会话在测试用例运行的时候就被创建, db.session.remove() 在每一个测试用例的结尾被调用(这是为了确保 SQLAlchemy 会话及时被删除) - 这是一种常见的 “陷阱”。

另外一个 “陷阱” 就是 Flask-SQLAlchemy 会在每一个请求结束的时候删除 SQLAlchemy 会话(session)。因此每次调用 client.get() 或者其它客户端方法的后,SQLAlchemy 会话(session)连同添加到它的任何对象都会被删除。

例如:

class SomeTest(MyTest):

    def test_something(self):

        user = User()
        db.session.add(user)
        db.session.commit()

        # this works
        assert user in db.session

        response = self.client.get("/")

        # this raises an AssertionError
        assert user in db.session

你现在必须重新添加 “user” 实例回 SQLAlchemy 会话(session)使用 db.session.add(user),如果你想要在数据库上做进一步的操作。

同样需要注意地是在这个例子中内存数据库 SQLite 是被使用:尽管它是十分的快,但是你要是使用其它类型的数据库(例如 MySQL 或者 PostgreSQL),可能上述代码就不适用。

你也可能想要在 setUp() 里为你的数据库增加一组实例一旦你的数据库的表已经创建。如果你想要使用数据集的话,请参看 Fixture,它包含了对 SQLAlchemy 的支持。

运行测试用例

使用 unittest

一开始我建议把所有的测试放在一个文件里面,这样你可以使用 unittest.main() 函数。这个函数将会发现在你的 TestCase 类里面的所有的测试方法。请记住,所有的测试方法和类请以 test 开头(不区分大小写),这样才能被自动识别出来。

一个测试用例的文件可以看起来像这样:

import unittest
import flask.ext.testing

# your test cases

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

现在你可以用 python tests.py 命令执行你的测试。

使用 nose

同样 nose 也与 Flask-Testing 能够很好的融合在一起。

更新历史

0.4.2 (24.07.2014)

> >

  • Improved teardown to be more graceful.
  • Add message argument to assertStatus respectively all assertion methods with fixed status like assert404.

0.4.1 (27.02.2014)

This release is dedicated to every contributer who made this release possible. Thank you very much.

> >

  • Python 3 compatibility (without twill)
  • Add LiveServerTestCase
  • Use unittest2 backports if available in python 2.6
  • Install multiprocessing for python versions earlier than 2.6

0.4 (06.07.2012)

> >

  • Use of the new introduced import way for flask extensions. Use import flask.ext.testing instead of import flaskext.testing.
  • Replace all assert with self.assert* methods for better output with unittest.
  • Improved Python 2.5 support.
  • Use Flask’s preferred JSON module.

API

class flask.ext.testing.TestCase(methodName='runTest')

assert200(response)

Checks if response status code is 200

Parameters: response – Flask response

assert400(response)

Checks if response status code is 400

Versionadded: 0.2.5

Parameters: response – Flask response

assert401(response)

Checks if response status code is 401

Versionadded: 0.2.1

Parameters: response – Flask response

assert403(response)

Checks if response status code is 403

Versionadded: 0.2

Parameters: response – Flask response

assert404(response)

Checks if response status code is 404

Parameters: response – Flask response

assert405(response)

Checks if response status code is 405

Versionadded: 0.2

Parameters: response – Flask response

assert500(response)

Checks if response status code is 500

Versionadded: 0.4.1

Parameters: response – Flask response

assertContext(name, value)

Checks if given name exists in the template context and equals the given value.

Versionadded:

0.2

Parameters:

  • name – name of context variable
  • value – value to check against

assertRedirects(response, location)

Checks if response is an HTTP redirect to the given location.

Parameters:

  • response – Flask response
  • location – relative URL (i.e. without http://localhost)

assertStatus(response, status_code)

Helper method to check matching response status.

Parameters:

  • response – Flask response
  • status_code – response status code (e.g. 200)

assertTemplateUsed(name, tmpl_name_attribute='name')

Checks if a given template is used in the request. Only works if your version of Flask has signals support (0.6+) and blinker is installed. If the template engine used is not Jinja2, provide tmpl_name_attribute with a value of its Template class attribute name which contains the provided name value.

Versionadded:

0.2

Parameters:

  • name – template name
  • tmpl_name_attribute – template engine specific attribute name

assert_200(response)

Checks if response status code is 200

Parameters: response – Flask response

assert_400(response)

Checks if response status code is 400

Versionadded: 0.2.5

Parameters: response – Flask response

assert_401(response)

Checks if response status code is 401

Versionadded: 0.2.1

Parameters: response – Flask response

assert_403(response)

Checks if response status code is 403

Versionadded: 0.2

Parameters: response – Flask response

assert_404(response)

Checks if response status code is 404

Parameters: response – Flask response

assert_405(response)

Checks if response status code is 405

Versionadded: 0.2

Parameters: response – Flask response

assert_500(response)

Checks if response status code is 500

Versionadded: 0.4.1 Parameters: response – Flask response

assert_context(name, value)

Checks if given name exists in the template context and equals the given value.

Versionadded:

0.2

Parameters:

  • name – name of context variable
  • value – value to check against

assert_redirects(response, location)

Checks if response is an HTTP redirect to the given location.

Parameters:

  • response – Flask response
  • location – relative URL (i.e. without http://localhost)

assert_status(response, status_code)

Helper method to check matching response status.

Parameters:

  • response – Flask response
  • status_code – response status code (e.g. 200)

assert_template_used(name, tmpl_name_attribute='name')

Checks if a given template is used in the request. Only works if your version of Flask has signals support (0.6+) and blinker is installed. If the template engine used is not Jinja2, provide tmpl_name_attribute with a value of its Template class attribute name which contains the provided name value.

Versionadded:

0.2

Parameters:

  • name – template name
  • tmpl_name_attribute – template engine specific attribute name

create_app()

Create your Flask app here, with any configuration you need.

get_context_variable(name)

Returns a variable from the context passed to the template. Only works if your version of Flask has signals support (0.6+) and blinker is installed.

Raises a ContextVariableDoesNotExist exception if does not exist in context.

Versionadded: 0.2

Parameters: name – name of variable

class flask.ext.testing.Twill(app, host='127.0.0.1', port=5000, scheme='http')

Versionadded: 0.3

Twill wrapper utility class.

Creates a Twill browser instance and handles WSGI intercept.

Usage:

t = Twill(self.app)
with t:
    t.browser.go("/")
    t.url("/")

url(url)

Makes complete URL based on host, port and scheme Twill settings.

Parameters: url – relative URL

class flask.ext.testing.TwillTestCase(methodName='runTest')

Deprecated: use Twill helper class instead.

Creates a Twill browser instance and handles WSGI intercept.

make_twill_url(url)

Makes complete URL based on host, port and scheme Twill settings.

Parameters: url – relative URL