forked from orbit-oss/flask
Beefed up the tutorial
This commit is contained in:
parent
1246f4088a
commit
6dd92ae4b3
7 changed files with 306 additions and 92 deletions
157
docs/testing.rst
157
docs/testing.rst
|
|
@ -22,72 +22,78 @@ installation.
|
|||
The Application
|
||||
---------------
|
||||
|
||||
First we need an application to test for functionality. Let's start
|
||||
simple with a Hello World application (`hello.py`)::
|
||||
First we need an application to test for functionality. For the testing
|
||||
we will use the application from the :ref:`tutorial`. If you don't have
|
||||
that application yet, get the sources from `the examples`_.
|
||||
|
||||
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 examples:
|
||||
http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/
|
||||
|
||||
The Testing Skeleton
|
||||
--------------------
|
||||
|
||||
In order to test that, we add a second module (
|
||||
`hello_tests.py`) and create a unittest skeleton there::
|
||||
`flaskr_tests.py`) and create a unittest skeleton there::
|
||||
|
||||
import unittest
|
||||
import hello
|
||||
import flaskr
|
||||
import tempfile
|
||||
|
||||
class HelloWorldTestCase(unittest.TestCase):
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.app = hello.app.test_client()
|
||||
self.db = tempfile.NamedTemporaryFile()
|
||||
self.app = flaskr.app.test_client()
|
||||
flaskr.DATABASE = self.db.name
|
||||
flaskr.init_db()
|
||||
|
||||
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.
|
||||
The code in the `setUp` function creates a new test client and initialize
|
||||
a new database. 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.
|
||||
|
||||
Because SQLite3 is filesystem based we can easily use the tempfile module
|
||||
to create a temporary database and initialize it. Just make sure that you
|
||||
keep a reference to the :class:`~tempfile.NamedTemporaryFile` around (we
|
||||
store it as `self.db` because of that) so that the garbage collector does
|
||||
not remove that object and with it the database from the filesystem.
|
||||
|
||||
If we now run that testsuite, we should see the following output::
|
||||
|
||||
$ python hello_tests.py
|
||||
$ python flaskr_tests.py
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 0 tests in 0.000s
|
||||
|
||||
OK
|
||||
|
||||
Even though it did not run any tests, we already know that our hello
|
||||
Even though it did not run any tests, we already know that our flaskr
|
||||
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::
|
||||
Now we can add the first test. Let's check that the application shows
|
||||
"No entries here so far" if we access the root of the application (``/``).
|
||||
For that we modify our created test case class so that it looks like
|
||||
this::
|
||||
|
||||
class HelloWorldTestCase(unittest.TestCase):
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.app = hello.app.test_client()
|
||||
self.db = tempfile.NamedTemporaryFile()
|
||||
self.app = flaskr.app.test_client()
|
||||
flaskr.DATABASE = self.db.name
|
||||
flaskr.init_db()
|
||||
|
||||
def test_hello_world(self):
|
||||
def test_empty_db(self):
|
||||
rv = self.app.get('/')
|
||||
assert 'Hello World!' in rv.data
|
||||
assert 'No entries here so far' 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
|
||||
|
|
@ -95,22 +101,87 @@ 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.
|
||||
that ``'No entries here so far'`` is part of the output.
|
||||
|
||||
Run it again and you should see one passing test. Let's add a second test
|
||||
here::
|
||||
Run it again and you should see one passing test::
|
||||
|
||||
def test_hello_name(self):
|
||||
rv = self.app.get('/Peter')
|
||||
assert 'Hello Peter!' in rv.data
|
||||
$ python flaskr_tests.py
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.034s
|
||||
|
||||
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:
|
||||
OK
|
||||
|
||||
Of course you can submit forms with the test client as well which we will
|
||||
use now to log our user in.
|
||||
|
||||
Logging In and Out
|
||||
------------------
|
||||
|
||||
The majority of the functionality of our application is only available for
|
||||
the administration user. So we need a way to log our test client into the
|
||||
application and out of it again. For that we fire some requests to the
|
||||
login and logout pages with the required form data (username and
|
||||
password). Because the login and logout pages redirect, we tell the
|
||||
client to `follow_redirects`.
|
||||
|
||||
Add the following two methods do your `FlaskrTestCase` class::
|
||||
|
||||
def login(self, username, password):
|
||||
return self.app.post('/login', data=dict(
|
||||
username=username,
|
||||
password=password
|
||||
), follow_redirects=True)
|
||||
|
||||
def logout(self):
|
||||
return self.app.get('/logout', follow_redirects=True)
|
||||
|
||||
Now we can easily test if logging in and out works and that it fails with
|
||||
invalid credentials. Add this as new test to the class::
|
||||
|
||||
def test_login_logout(self):
|
||||
rv = self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
assert 'You were logged in' in rv.data
|
||||
rv = self.logout()
|
||||
assert 'You were logged out' in rv.data
|
||||
rv = self.login(flaskr.USERNAME + 'x', flaskr.PASSWORD)
|
||||
assert 'Invalid username' in rv.data
|
||||
rv = self.login(flaskr.USERNAME, flaskr.PASSWORD + 'x')
|
||||
assert 'Invalid password' in rv.data
|
||||
|
||||
Test Adding Messages
|
||||
--------------------
|
||||
|
||||
Now we can also test that adding messages works. Add a new test method
|
||||
like this::
|
||||
|
||||
def test_messages(self):
|
||||
self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
rv = self.app.post('/add', data=dict(
|
||||
title='<Hello>',
|
||||
text='<strong>HTML</strong> allowed here'
|
||||
), follow_redirects=True)
|
||||
assert 'No entries here so far' not in rv.data
|
||||
self.login(flaskr.USERNAME, flaskr.PASSWORD)
|
||||
assert '<Hello>' in rv.data
|
||||
assert '<strong>HTML</strong> allowed here' in rv.data
|
||||
|
||||
Here we also check that HTML is allowed in the text but not in the title
|
||||
which is the intended behavior.
|
||||
|
||||
Running that should now give us three passing tests::
|
||||
|
||||
$ python flaskr_tests.py
|
||||
...
|
||||
----------------------------------------------------------------------
|
||||
Ran 3 tests in 0.332s
|
||||
|
||||
OK
|
||||
|
||||
For more complex tests with headers and status codes, check out the
|
||||
`MiniTwit Example`_ from the sources. That one contains a larger test
|
||||
suite.
|
||||
|
||||
- Werkzeug Test :class:`~werkzeug.Client`
|
||||
- `MiniTwit Example`_
|
||||
|
||||
.. _MiniTwit Example:
|
||||
http://github.com/mitsuhiko/flask/tree/master/examples/minitwit/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue