Browse Source

Doc updates and typo fixes

pull/1638/head
Armin Ronacher 15 years ago
parent
commit
2f5a4f8dbc
  1. 116
      docs/testing.rst
  2. 2
      examples/minitwit/README
  3. 18
      examples/minitwit/minitwit.py
  4. 2
      examples/minitwit/templates/timeline.html

116
docs/testing.rst

@ -0,0 +1,116 @@
.. _testing:
Testing Flask Applications
==========================
**Something that is untested is broken.**
Not sure where that is coming from, and it's not entirely correct, but
also not that far from the truth. Untested applications make it hard to
improve existing code and developers of untested applications tend to
become pretty paranoid. If an application however has automated tests you
can savely change things and you will instantly know if your change broke
something.
Flask gives you a couple of ways to test applications. It mainly does
that by exposing the Werkzeug test :class:`~werkzeug.Client` class to your
code and handling the context locals for you. You can then use that with
your favourite testing solution. In this documentation we will us the
:mod:`unittest` package that comes preinstalled with each Python
installation.
The Application
---------------
First we need an application to test for functionality. Let's start
simple with a Hello World application (`hello.py`)::
from flask import Flask, render_template_string
app = Flask(__name__)
@app.route('/')
@app.route('/<name>')
def hello(name='World'):
return render_template_string('''
<!doctype html>
<title>Hello {{ name }}!</title>
<h1>Hello {{ name }}!</h1>
''', name=name)
The Testing Skeleton
--------------------
In order to test that, we add a second module (
`hello_tests.py`) and create a unittest skeleton there::
import unittest
import hello
class HelloWorldTestCase(unittest.TestCase):
def setUp(self):
self.app = hello.app.test_client()
if __name__ == '__main__':
unittest.main()
The code in the `setUp` function creates a new test client. That function
is called before each individual test function. What the test client does
for us is giving us a simple interface to the application. We can trigger
test requests to the application and the client will also keep track of
cookies for us.
If we now run that testsuite, we should see the following output::
$ python hello_tests.py
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Even though it did not run any tests, we already know that our hello
application is syntactically valid, otherwise the import would have died
with an exception.
The First Test
--------------
Now we can add the first test. Let's check that the application greets us
with "Hello World" if we access it on ``/``. For that we modify our
created test case class so that it looks like this::
class HelloWorldTestCase(unittest.TestCase):
def setUp(self):
self.app = hello.app.test_client()
def test_hello_world(self):
rv = self.app.get('/')
assert 'Hello World!' in rv.data
Test functions begin with the word `test`. Every function named like that
will be picked up automatically. By using `self.app.get` we can send an
HTTP `GET` request to the application with the given path. The return
value will be a :class:`~flask.Flask.response_class` object. We can now
use the :attr:`~werkzeug.BaseResponse.data` attribute to inspect the
return value (as string) from the application. In this case, we ensure
that ``'Hello World!'`` is part of the output.
Run it again and you should see one passing test. Let's add a second test
here::
def test_hello_name(self):
rv = self.app.get('/Peter')
assert 'Hello Peter!' in rv.data
Of course you can submit forms with the test client as well. For that and
other features of the test client, check the documentation of the Werkzeug
test :class:`~werkzeug.Client` and the tests of the MiniTwit example
application:
- Werkzeug Test :class:`~werkzeug.Client`
- `MiniTwit Example`_
.. _MiniTwit Example:
http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/

2
examples/minitwit/README

@ -10,7 +10,7 @@
~ How do I use it? ~ How do I use it?
1. edit the configurtion in the minitwit.py file 1. edit the configuration in the minitwit.py file
2. fire up a python shell and run this: 2. fire up a python shell and run this:

18
examples/minitwit/minitwit.py

@ -31,7 +31,7 @@ app = Flask(__name__)
def connect_db(): def connect_db():
"""Returns a new database connection to the database.""" """Returns a new connection to the database."""
return sqlite3.connect(DATABASE) return sqlite3.connect(DATABASE)
@ -52,19 +52,19 @@ def query_db(query, args=(), one=False):
def get_user_id(username): def get_user_id(username):
"""Convenience method to look up the id for a username""" """Convenience method to look up the id for a username."""
rv = g.db.execute('select user_id from user where username = ?', rv = g.db.execute('select user_id from user where username = ?',
[username]).fetchone() [username]).fetchone()
return rv[0] if rv else None return rv[0] if rv else None
def format_datetime(timestamp): def format_datetime(timestamp):
"""Format a timestamp for display""" """Format a timestamp for display."""
return datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d @ %H:%M') return datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d @ %H:%M')
def gravatar_url(email, size=80): def gravatar_url(email, size=80):
"""Return the gravatar image for the given email address""" """Return the gravatar image for the given email address."""
return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \ return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \
(md5(email.strip().lower().encode('utf-8')).hexdigest(), size) (md5(email.strip().lower().encode('utf-8')).hexdigest(), size)
@ -138,7 +138,7 @@ def user_timeline(username):
@app.route('/<username>/follow') @app.route('/<username>/follow')
def follow_user(username): def follow_user(username):
"""Adds the current user as follower of the given user""" """Adds the current user as follower of the given user."""
if not g.user: if not g.user:
abort(401) abort(401)
whom_id = get_user_id(username) whom_id = get_user_id(username)
@ -153,7 +153,7 @@ def follow_user(username):
@app.route('/<username>/unfollow') @app.route('/<username>/unfollow')
def unfollow_user(username): def unfollow_user(username):
"""Removes the current user as follower of the given user""" """Removes the current user as follower of the given user."""
if not g.user: if not g.user:
abort(401) abort(401)
whom_id = get_user_id(username) whom_id = get_user_id(username)
@ -168,7 +168,7 @@ def unfollow_user(username):
@app.route('/add_message', methods=['POST']) @app.route('/add_message', methods=['POST'])
def add_message(): def add_message():
"""Registers a new message for the user""" """Registers a new message for the user."""
if 'user_id' not in session: if 'user_id' not in session:
abort(401) abort(401)
if request.form['text']: if request.form['text']:
@ -182,7 +182,7 @@ def add_message():
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
"""Logs the user in""" """Logs the user in."""
if g.user: if g.user:
return redirect(url_for('timeline')) return redirect(url_for('timeline'))
error = None error = None
@ -203,7 +203,7 @@ def login():
@app.route('/register', methods=['GET', 'POST']) @app.route('/register', methods=['GET', 'POST'])
def register(): def register():
"""Registers the user""" """Registers the user."""
if g.user: if g.user:
return redirect(url_for('timeline')) return redirect(url_for('timeline'))
error = None error = None

2
examples/minitwit/templates/timeline.html

@ -43,7 +43,7 @@
{{ message.text }} {{ message.text }}
<small>&mdash; {{ message.pub_date|datetimeformat }}</small> <small>&mdash; {{ message.pub_date|datetimeformat }}</small>
{% else %} {% else %}
<li><em>There are no messages so far.</em> <li><em>There's no message so far.</em>
{% endfor %} {% endfor %}
</ul> </ul>
{% endblock %} {% endblock %}

Loading…
Cancel
Save