forked from orbit-oss/flask
fixing some wording issues on the testing page
Signed-off-by: Armin Ronacher <armin.ronacher@active-4.com>
This commit is contained in:
parent
04e70bd5c7
commit
f58c98904f
1 changed files with 54 additions and 57 deletions
111
docs/testing.rst
111
docs/testing.rst
|
|
@ -5,26 +5,23 @@ 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
|
||||
The origin of this quote is unknown and while it is not entirely correct, it is also
|
||||
not 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 has automated tests, you can
|
||||
safely change things, and you will instantly know if your change broke
|
||||
something.
|
||||
safely make changes and instantly know if anything breaks.
|
||||
|
||||
Flask gives you a couple of ways to test applications. It mainly does
|
||||
that by exposing the Werkzeug test :class:`~werkzeug.test.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 use the
|
||||
:mod:`unittest` package that comes preinstalled with each Python
|
||||
installation.
|
||||
Flask provides a way to test your application by exposing the Werkzeug
|
||||
test :class:`~werkzeug.test.Client` and handling the context locals for you.
|
||||
You can then use that with your favourite testing solution. In this documentation
|
||||
we will use the :mod:`unittest` package that comes pre-installed with Python.
|
||||
|
||||
The Application
|
||||
---------------
|
||||
|
||||
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`_.
|
||||
First, we need an application to test; we will use the application from
|
||||
the :ref:`tutorial`. If you don't have that application yet, get the
|
||||
sources from `the examples`_.
|
||||
|
||||
.. _the examples:
|
||||
http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/
|
||||
|
|
@ -32,8 +29,8 @@ that application yet, get the sources from `the examples`_.
|
|||
The Testing Skeleton
|
||||
--------------------
|
||||
|
||||
In order to test that, we add a second module (
|
||||
`flaskr_tests.py`) and create a unittest skeleton there::
|
||||
In order to test the application, we add a second module
|
||||
(`flaskr_tests.py`) and create a unittest skeleton there::
|
||||
|
||||
import os
|
||||
import flaskr
|
||||
|
|
@ -55,13 +52,14 @@ In order to test that, we add a second module (
|
|||
unittest.main()
|
||||
|
||||
The code in the :meth:`~unittest.TestCase.setUp` method creates a new test
|
||||
client and initializes a new database. That function is called before
|
||||
each individual test function. To delete the database after the test, we
|
||||
close the file and remove it from the filesystem in the
|
||||
:meth:`~unittest.TestCase.tearDown` method. What the test client does is
|
||||
give 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.
|
||||
client and initializes a new database. This function is called before
|
||||
each individual test function is run. To delete the database after the
|
||||
test, we close the file and remove it from the filesystem in the
|
||||
:meth:`~unittest.TestCase.tearDown` method.
|
||||
|
||||
This test client will give 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. The
|
||||
|
|
@ -70,7 +68,7 @@ low-level file handle and a random file name, the latter we use as
|
|||
database name. We just have to keep the `db_fd` around so that we can use
|
||||
the :func:`os.close` function to close the file.
|
||||
|
||||
If we now run that test suite, we should see the following output::
|
||||
If we now run the test suite, we should see the following output::
|
||||
|
||||
$ python flaskr_tests.py
|
||||
|
||||
|
|
@ -79,17 +77,17 @@ If we now run that test suite, we should see the following output::
|
|||
|
||||
OK
|
||||
|
||||
Even though it did not run any tests, we already know that our flaskr
|
||||
Even though it did not run any actual 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 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::
|
||||
Now it's time to start testing the functionality of the application.
|
||||
Let's check that the application shows "No entries here so far" if we
|
||||
access the root of the application (``/``). To do this, we add a new
|
||||
test method to our class, like this::
|
||||
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
|
||||
|
|
@ -106,13 +104,14 @@ this::
|
|||
rv = self.app.get('/')
|
||||
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
|
||||
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.wrappers.BaseResponse.data` attribute to inspect the
|
||||
return value (as string) from the application. In this case, we ensure
|
||||
that ``'No entries here so far'`` is part of the output.
|
||||
Notice that our test functions begin with the word `test`; this allows
|
||||
:mod:`unittest` to automatically identify the method as a test to run.
|
||||
|
||||
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.wrappers.BaseResponse.data` attribute to inspect
|
||||
the return value (as string) from the application. In this case, we ensure that
|
||||
``'No entries here so far'`` is part of the output.
|
||||
|
||||
Run it again and you should see one passing test::
|
||||
|
||||
|
|
@ -123,18 +122,14 @@ Run it again and you should see one passing test::
|
|||
|
||||
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 administrative user, so we need a way to log our test client in to 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`.
|
||||
the administrative user, so we need a way to log our test client in and out
|
||||
of the application. To do this, we fire some requests to the login and logout
|
||||
pages with the required form data (username and password). And because the
|
||||
login and logout pages redirect, we tell the client to `follow_redirects`.
|
||||
|
||||
Add the following two methods to your `FlaskrTestCase` class::
|
||||
|
||||
|
|
@ -147,7 +142,7 @@ Add the following two methods to your `FlaskrTestCase` class::
|
|||
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
|
||||
Now we can easily test that logging in and out works and that it fails with
|
||||
invalid credentials. Add this new test to the class::
|
||||
|
||||
def test_login_logout(self):
|
||||
|
|
@ -163,7 +158,7 @@ invalid credentials. Add this new test to the class::
|
|||
Test Adding Messages
|
||||
--------------------
|
||||
|
||||
Now we can also test that adding messages works. Add a new test method
|
||||
We should also test that adding messages works. Add a new test method
|
||||
like this::
|
||||
|
||||
def test_messages(self):
|
||||
|
|
@ -189,7 +184,7 @@ Running that should now give us three passing tests::
|
|||
OK
|
||||
|
||||
For more complex tests with headers and status codes, check out the
|
||||
`MiniTwit Example`_ from the sources. That one contains a larger test
|
||||
`MiniTwit Example`_ from the sources which contains a larger test
|
||||
suite.
|
||||
|
||||
|
||||
|
|
@ -200,12 +195,12 @@ suite.
|
|||
Other Testing Tricks
|
||||
--------------------
|
||||
|
||||
Besides using the test client we used above, there is also the
|
||||
:meth:`~flask.Flask.test_request_context` method that in combination with
|
||||
the `with` statement can be used to activate a request context
|
||||
temporarily. With that you can access the :class:`~flask.request`,
|
||||
Besides using the test client as shown above, there is also the
|
||||
:meth:`~flask.Flask.test_request_context` method that can be used
|
||||
in combination with the `with` statement to activate a request context
|
||||
temporarily. With this you can access the :class:`~flask.request`,
|
||||
:class:`~flask.g` and :class:`~flask.session` objects like in view
|
||||
functions. Here's a full example that showcases this::
|
||||
functions. Here is a full example that demonstrates this approach::
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
|
|
@ -213,7 +208,8 @@ functions. Here's a full example that showcases this::
|
|||
assert flask.request.path == '/'
|
||||
assert flask.request.args['name'] == 'Peter'
|
||||
|
||||
All the other objects that are context bound can be used the same.
|
||||
All the other objects that are context bound can be used in the same
|
||||
way.
|
||||
|
||||
If you want to test your application with different configurations and
|
||||
there does not seem to be a good way to do that, consider switching to
|
||||
|
|
@ -225,7 +221,7 @@ Keeping the Context Around
|
|||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
Sometimes it can be helpful to trigger a regular request but keep the
|
||||
Sometimes it is helpful to trigger a regular request but still keep the
|
||||
context around for a little longer so that additional introspection can
|
||||
happen. With Flask 0.4 this is possible by using the
|
||||
:meth:`~flask.Flask.test_client` with a `with` block::
|
||||
|
|
@ -236,9 +232,10 @@ happen. With Flask 0.4 this is possible by using the
|
|||
rv = c.get('/?tequila=42')
|
||||
assert request.args['tequila'] == '42'
|
||||
|
||||
If you would just be using the :meth:`~flask.Flask.test_client` without
|
||||
If you were to use just the :meth:`~flask.Flask.test_client` without
|
||||
the `with` block, the `assert` would fail with an error because `request`
|
||||
is no longer available (because used outside of an actual request).
|
||||
Keep in mind however that :meth:`~flask.Flask.after_request` functions
|
||||
are already called at that point so your database connection and
|
||||
is no longer available (because you are trying to use it outside of the actual request).
|
||||
However, keep in mind that any :meth:`~flask.Flask.after_request` functions
|
||||
are already called at this point so your database connection and
|
||||
everything involved is probably already closed down.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue