Testing Flask Applications

按照pytest

$ pip install pytest

https://github.com/pallets/flask/tree/1.1.2/examples/tutorial下载示例。然后进入到example/tutorial目录。

在源码目录创建一个tests目录,然后在其下创建一个文件test_flaskr.py来保存测试代码。pytest会自动识别以test_*.py格式的文件名,并从中提取测试用例。

接着创建一个pytest的fixture:

import os
import tempfile

import pytest

from flaskr import flaskr


@pytest.fixture
def client():
    db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
    flaskr.app.config['TESTING'] = True

    with flaskr.app.test_client() as client:
        with flaskr.app.app_context():
            flaskr.init_db()
        yield client

    os.close(db_fd)
    os.unlink(flaskr.app.config['DATABASE'])

client()作为一个fixture,在执行每个用例前都会被调用。所以数据库被放在了临时的文件,并且TESTING选项被开启。然后client()会被调用两次,第一次yield一个app.test_client示例,第二次应该是在结束的时候,关掉数据库文件。

The First Test

在test_flaskr添加一个测试函数:

def test_empty_db(client):
    """Start with a blank database."""

    rv = client.get('/')
    assert b'No entries here so far' in rv.data

Logging In and Out

首先准备两个工具函数:

def login(client, username, password):
    return client.post('/login', data=dict(
        username=username,
        password=password
    ), follow_redirects=True)


def logout(client):
    return client.get('/logout', follow_redirects=True)

然后测试登录和退出:

def test_login_logout(client):
    """Make sure login and logout works."""

    rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'])
    assert b'You were logged in' in rv.data

    rv = logout(client)
    assert b'You were logged out' in rv.data

    rv = login(client, flaskr.app.config['USERNAME'] + 'x', flaskr.app.config['PASSWORD'])
    assert b'Invalid username' in rv.data

    rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'] + 'x')
    assert b'Invalid password' in rv.data

Test Adding Messages

测试添加消息:

def test_messages(client):
    """Test that messages work."""

    login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'])
    rv = client.post('/add', data=dict(
        title='<Hello>',
        text='<strong>HTML</strong> allowed here'
    ), follow_redirects=True)
    assert b'No entries here so far' not in rv.data
    assert b'&lt;Hello&gt;' in rv.data
    assert b'<strong>HTML</strong> allowed here' in rv.data

Other Testing Tricks

除了使用test client之外,也可以使用test_request_context(),有了这个就可以访问request, g, 以及session对象:

import flask

app = flask.Flask(__name__)

with app.test_request_context('/?name=Peter'):
    assert flask.request.path == '/'
    assert flask.request.args['name'] == 'Peter'

这种情况下, before_request() 和after_request() 不会被运行,但是teardown_request() 会被with语块自动运行,如果真的需要before_request() 被调用(比如需要创建数据库链接),那么可以使用preprocess_request() :

app = flask.Flask(__name__)

with app.test_request_context('/?name=Peter'):
    app.preprocess_request()
    ...

同样的,通过显示调用process_response()可以触发after_request() :

app = flask.Flask(__name__)

with app.test_request_context('/?name=Peter'):
    resp = Response('...')
    resp = app.process_response(resp)
    ...

你也可以使用Application Factories

Faking Resources and Context

对于保存在flask.g里面的局部全局对象,可以通过 flask.appcontext_pushed 来模拟:

from contextlib import contextmanager
from flask import appcontext_pushed, g

@contextmanager
def user_set(app, user):
    def handler(sender, **kwargs):
        g.user = user
    with appcontext_pushed.connected_to(handler, app):
        yield

from flask import json, jsonify

@app.route('/users/me')
def users_me():
    return jsonify(username=g.user.username)

with user_set(app, my_user):
    with app.test_client() as c:
        resp = c.get('/users/me')
        data = json.loads(resp.data)
        self.assert_equal(data['username'], my_user.username)

Keeping the Context Around

如果对test_client()不适用with语句,那么context将不可用:

app = flask.Flask(__name__)

with app.test_client() as c:
    rv = c.get('/?tequila=42')
    assert request.args['tequila'] == '42'

上面assert会失败,因为那个时刻context已经消失。

Accessing and Modifying Sessions

你可以这样写测试:

with app.test_client() as c:
    rv = c.get('/')
    assert flask.session['foo'] == 42

但是上面的代码无法修改会话内容,一个变通办法是使用session_transaction():

with app.test_client() as c:
    with c.session_transaction() as sess:
        sess['a_key'] = 'a value'

    # once this is reached the session was stored and ready to be used by the client
    c.get(...)

Testing JSON APIs

from flask import request, jsonify

@app.route('/api/auth')
def auth():
    json_data = request.get_json()
    email = json_data['email']
    password = json_data['password']
    return jsonify(token=generate_token(email, password))

with app.test_client() as c:
    rv = c.post('/api/auth', json={
        'email': 'flask@example.com', 'password': 'secret'
    })
    json_data = rv.get_json()
    assert verify_token(email, json_data['token'])

在post函数中设置json参数可以将http内容类型设置为appliation/json,通过get_json可以从request或者response中获取json数据。

Testing CLI Commands¶

example/tutorial中例子的执行步骤

使用venv创建一个虚拟环境,并调用其activate.bat进入(假设在Windows的CMD环境):

python3 -m venv vv
vv\Scripts\activate.bat

venv是python3.3引入到标准库的。

然后在虚拟环境vv中安装flaskr,以及pytest:

pip install -e .
pip install ".[test]" # 等同于pip install pytest

然后运行pytest以及coverage

pytest
pip install coverage
coverage run -m pytest
coverage repor

其他

windows竟然自带一个Python,在C:\Windows\py.exe。

(草草结束)