Skip to content

Commit

Permalink
doc(tutorial): Work on testing in progress.
Browse files Browse the repository at this point in the history
  • Loading branch information
butla committed Nov 6, 2016
1 parent c5cdf2b commit 525c057
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 16 deletions.
3 changes: 3 additions & 0 deletions docs/api/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
Testing
=======

Reference
---------

.. automodule:: falcon.testing
:members: Result, Cookie,
simulate_request, simulate_get, simulate_head, simulate_post,
Expand Down
132 changes: 116 additions & 16 deletions docs/user/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,40 @@ the terminology used by the framework.
First Steps
-----------

Before continuing, be sure you've got Falcon :ref:`installed <install>`. Then,
create a new project folder called "look" and cd into it:
Before continuing, be sure you've got Falcon :ref:`installed <install>` inside
a `virtualenv <http://docs.python-guide.org/en/latest/dev/virtualenvs/>`_.
Then, create a new project folder called "look" and cd into it:

.. code:: bash
$ mkdir look
$ cd look
It's customary for the project's top-level module to be called the same as the
project, so let's create another "look" folder inside the first one and mark
it as a python module by creating an empty ``__init__.py`` file in it:

.. code:: bash
$ mkdir look
$ touch look/__init__.py
Next, let's create a new file that will be the entry point into your app:

.. code:: bash
$ touch app.py
$ touch look/app.py
Open that file in your favorite text editor and add the following lines:
The file hierarchy should look like this:

.. code:: bash
look
└── look
├── app.py
└── __init__.py
Now, open ``app.py`` in your favorite text editor and add the following lines:

.. code:: python
Expand Down Expand Up @@ -73,9 +92,10 @@ what you need.

.. tip::

`bpython <http://bpython-interpreter.org/>`_ is another super-
powered REPL that is good to have in your toolbox when
exploring a new library.
`bpython <http://bpython-interpreter.org/>`_ or
`ptpython <https://github.com/jonathanslenders/ptpython>`_ are another
super-powered REPLs that are good to have in your toolbox when exploring
a new library.


Hosting Your App
Expand All @@ -88,7 +108,7 @@ let's use something that you would actually deploy in production.
.. code:: bash
$ pip install gunicorn
$ gunicorn app
$ gunicorn look.app
Now try querying it with curl:

Expand Down Expand Up @@ -125,8 +145,8 @@ an action that the API client can request be performed in order to fetch
or transform the resource in question.

Since we are building an image-sharing API, let's create an "images"
resource. Create a new file, ``images.py`` within your project directory,
and add the following to it:
resource. Create a new file, ``images.py`` next to ``app.py``, and add the
following to it:

.. code:: python
Expand All @@ -137,6 +157,8 @@ and add the following to it:
def on_get(self, req, resp):
resp.body = '{"message": "Hello world!"}'
# This line can be ommited, because 200 is the default code falcon
# returns, but it shows how you can set a status code.
resp.status = falcon.HTTP_200
As you can see, ``Resource`` is just a regular class. You can name the
Expand Down Expand Up @@ -190,12 +212,12 @@ OK, now let's wire up this resource and see it in action. Go back to
import falcon
import images
from .images import Resource
api = application = falcon.API()
images = images.Resource()
images = Resource()
api.add_route('/images', images)
Now, when a request comes in for "/images", Falcon will call the
Expand All @@ -208,6 +230,74 @@ Restart gunicorn, and then try sending a GET request to the resource:
$ http GET localhost:8000/images
Testing your application
------------------------

Up to this point we didn't care about tests, but when creating applications
that will be used by someone you should have those, so from now on we will
create code in accordance with `Test Driven Development
<http://www.obeythetestinggoat.com/book/praise.harry.html>`_

Let's first create the missing tests for the current behavior of the application.
Create ``tests`` directory with ``__init__.py`` and the test file (``test_app.py``)
inside it. The project's structure should look like this:

.. code:: bash
look
├── look
│   ├── app.py
│   ├── images.py
│   └── __init__.py
└── tests
├── __init__.py
└── test_app.py
Falcon supports unit testing its API object by simulated HTTP requests.
There's two styles of writing tests - using built-in unittest module, and with Pytest
(more details can be found in :ref:`testing reference <testing>`). Pytest may not
be a part of Python's standard library, but it allows for more "pythonic" test code
than unittest which is highly influenced by Java's JUnit.
Therefore, we'll stick with Pytest. Let's install it

.. code:: bash
$ pip install pytest
and edit ``test_app.py`` to look like this:

.. code:: python
import falcon
from falcon import testing
import msgpack
import pytest
from look.app import api
@pytest.fixture
def client():
return testing.TestClient(api)
# Pytest will inject the object returned by "client" function as a parameter
# for this function.
def test_get_message(client):
doc = {u'message': u'Hello world!'}
response = client.simulate_get('/images')
result_doc = msgpack.unpackb(response.content, encoding='utf-8')
assert result_doc == doc
assert response.status == falcon.HTTP_OK
See your tests pass by running Pytest against ``tests`` directory while in the main
project directory.

.. code:: bash
py.test tests/
Request and Response Objects
----------------------------
Expand All @@ -230,10 +320,19 @@ Response class members using the same technique used above:
In [3]: help(falcon.Response)
Let's see how this works. When a client POSTs to our images collection, we
want to create a new image resource. First, we'll need to specify where the
images will be saved (for a real service, you would want to use an object
storage service instead, such as Cloud Files or S3).
This will be useful when creating a POST endpoint in the application that can
add new image resources to our collection. Because we decided to do TDD we need
to create a test for this feature before we write the code for it.
That way we define precisely what we want the application to do, and then code until
the tests tell us that we're done.

.. code:: python
TODO test post return code and location; mock out file write
See that the test fails by running Pytest again. Now we can get to the implementation.
First, we'll need to specify where the images will be saved (for a real service,
you would want to use an object storage service instead, such as Cloud Files or S3).

Edit your ``images.py`` file and add the following to the resource:

Expand All @@ -243,6 +342,7 @@ Edit your ``images.py`` file and add the following to the resource:
self.storage_path = storage_path
Then, edit ``app.py`` and pass in a path to the resource initializer.
TODO add an example for that

Next, let's implement the POST responder:

Expand Down
5 changes: 5 additions & 0 deletions examples/look/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Tutorial application
====================

This is the application that you can build by going along with the tutorial.

Empty file added examples/look/look/__init__.py
Empty file.
9 changes: 9 additions & 0 deletions examples/look/look/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import falcon

from .images import Resource


api = application = falcon.API()

images = Resource()
api.add_route('/images', images)
11 changes: 11 additions & 0 deletions examples/look/look/images.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import falcon

import msgpack


class Resource(object):

def on_get(self, req, resp):
resp.data = msgpack.packb({'message': 'Hello world!'})
resp.content_type = 'application/msgpack'
resp.status = falcon.HTTP_200
5 changes: 5 additions & 0 deletions examples/look/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
falcon==1.1.0rc1
gunicorn==19.6.0
msgpack-python==0.4.8
python-mimeparse==1.6.0
six==1.10.0
Empty file added examples/look/tests/__init__.py
Empty file.
2 changes: 2 additions & 0 deletions examples/look/tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
py==1.4.31
pytest==3.0.3
23 changes: 23 additions & 0 deletions examples/look/tests/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import falcon
from falcon import testing
import msgpack
import pytest

from look.app import api


@pytest.fixture
def client():
return testing.TestClient(api)


# Pytest will inject the object returned by "client" function as a parameter
# for this function.
def test_get_message(client):
doc = {u'message': u'Hello world!'}

response = client.simulate_get('/images')
result_doc = msgpack.unpackb(response.content, encoding='utf-8')

assert result_doc == doc
assert response.status == falcon.HTTP_OK

0 comments on commit 525c057

Please sign in to comment.