Merge pull request #50 from ryanpetrello/next
Work on the scaffolded "Quickstart" project
This commit is contained in:
@@ -82,11 +82,6 @@ code here to define tables, ORM definitions, and parse bindings from your
|
||||
configuration file.
|
||||
|
||||
|
||||
.. note::
|
||||
The base project contains some ready-to-run tests. Try running
|
||||
``py.test`` (the recommended test runner for Pecan) and watch them pass!
|
||||
|
||||
|
||||
.. _running_application:
|
||||
|
||||
Running the application
|
||||
|
||||
@@ -2,108 +2,4 @@
|
||||
|
||||
Unit Testing
|
||||
=============
|
||||
UnitTesting in Pecan is handled by ``WebTest``. It creates a fake Pecan
|
||||
application that in turn allows you to make assertions on how those requests
|
||||
and responses are being handled without starting an HTTP server at all.
|
||||
|
||||
|
||||
Tools
|
||||
-----
|
||||
Pecan recommends using ``py.test``. It is actually a project requirement when
|
||||
you install Pecan so you should already have it installed.
|
||||
|
||||
|
||||
Structure
|
||||
---------
|
||||
This guide assumes that you have all your tests in a ``tests`` directory. If
|
||||
you have created a project from the ``base`` project template that Pecan
|
||||
provides, you should already have this directory with a few tests.
|
||||
|
||||
The template project uses UnitTest-type tests and some of those tests use
|
||||
WebTest. We will describe how they work in the next section.
|
||||
|
||||
This is how running those tests with ``py.test`` would look like::
|
||||
|
||||
$ py.test
|
||||
============== test session starts =============
|
||||
platform darwin -- Python 2.6.1 -- pytest-2.0.1
|
||||
collected 11 items
|
||||
|
||||
./tests/test_config.py .........
|
||||
./tests/test_root.py ..
|
||||
|
||||
========== 11 passed in 0.30 seconds ===========
|
||||
|
||||
|
||||
Configuration and Testing
|
||||
-------------------------
|
||||
When you create a new project using the ``base`` project template, Pecan adds
|
||||
a reference to its ``py.test`` plugin to your project's ``setup.cfg`` file.
|
||||
This handles loading your Pecan configuration and setting up your app as
|
||||
defined by your project's ``app.py`` file.
|
||||
|
||||
If you've created your own project without using Pecan's template, you can
|
||||
load the plugin yourself by adding this to your ``setup.cfg`` file::
|
||||
|
||||
[pytest]
|
||||
addopts = -p pecan.testing --with-config=./config.py
|
||||
|
||||
Alternatively, you can just pass those options to ``py.test`` directly.
|
||||
|
||||
By default, Pecan's testing plugin assumes you will be using the ``config.py``
|
||||
configuration file to run your tests. To change which configuration file gets
|
||||
used once, run ``py.test`` with the `--with-config` option. To make the change
|
||||
permanent, modify that option in the `addopts` setting of your ``setup.cfg``
|
||||
file.
|
||||
|
||||
Pecan's ``py.test`` plugin exposes two new variables in the ``py.test``
|
||||
namespace: ``temp_dir`` and ``wsgi_app``.
|
||||
|
||||
``py.test.temp_dir`` is a temporary directory that you can use for your tests.
|
||||
It's created at startup and deleted after all tests have completed. When using
|
||||
locally distributed testing with py.test, this is guaranteed to be shared by
|
||||
each test process. This is useful if you need to create some initial resource
|
||||
(e.g., a database template) that is later copied by each test. If you're using
|
||||
remotely distributed testing, the directory won't be shared across nodes.
|
||||
|
||||
``py.test.wsgi_app`` is your Pecan app loaded and configured per your project's
|
||||
``app.py`` file. In your test's ``setUp`` method, you would wrap this with
|
||||
``TestApp``::
|
||||
|
||||
from unittest import TestCase
|
||||
from webtest import TestApp
|
||||
|
||||
import py.test
|
||||
|
||||
class TestRootController(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.app = TestApp(py.test.wsgi_app)
|
||||
|
||||
|
||||
Using WebTest with a UnitTest
|
||||
-----------------------------
|
||||
Once you have a ``setUp`` method with your ``TestApp`` created, you have a
|
||||
wealth of actions provided within the test class to interact with your Pecan
|
||||
application::
|
||||
|
||||
* POST => self.app.post
|
||||
* GET => self.app.get
|
||||
* DELETE => self.app.delete
|
||||
* PUT => self.app.put
|
||||
|
||||
For example, if you want to assert that you can get to the root of your
|
||||
application, you could do something similar to this::
|
||||
|
||||
response = self.app.get('/')
|
||||
assert response.status_int == 200
|
||||
|
||||
If you are expecting error responses from your application, make sure to pass
|
||||
`expect_errors=True`::
|
||||
|
||||
response = self.app.get('/url/does/not/exist', expect_errors=True)
|
||||
assert response.status_int == 404
|
||||
|
||||
If you would like to dig in to more examples in how to test and verify more
|
||||
actions, take a look at the
|
||||
`WebTest documentation <http://pythonpaste.org/webtest/>`_
|
||||
TODO
|
||||
|
||||
@@ -19,6 +19,7 @@ DEFAULT = {
|
||||
'static_root' : 'public',
|
||||
'template_path' : '',
|
||||
'debug' : False,
|
||||
'logging' : False,
|
||||
'force_canonical' : True,
|
||||
'errors' : {
|
||||
'__force_dict__' : True
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
from pecan import expose
|
||||
from pecan import expose, redirect
|
||||
from formencode import Schema, validators as v
|
||||
from webob.exc import status_map
|
||||
|
||||
|
||||
class SampleForm(Schema):
|
||||
name = v.String(not_empty=True)
|
||||
age = v.Int(not_empty=True)
|
||||
class SearchForm(Schema):
|
||||
q = v.String(not_empty=True)
|
||||
|
||||
|
||||
class RootController(object):
|
||||
@@ -19,19 +18,18 @@ class RootController(object):
|
||||
|
||||
@index.when(
|
||||
method = 'POST',
|
||||
template = 'success.html',
|
||||
schema = SampleForm(),
|
||||
schema = SearchForm(),
|
||||
error_handler = '/index',
|
||||
htmlfill = dict(auto_insert_errors = True, prefix_error = False)
|
||||
htmlfill = dict(auto_insert_errors = True)
|
||||
)
|
||||
def index_post(self, name, age):
|
||||
return dict(name=name)
|
||||
def index_post(self, q):
|
||||
redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q)
|
||||
|
||||
@expose('error.html')
|
||||
def error(self, status):
|
||||
try:
|
||||
status = int(status)
|
||||
except ValueError:
|
||||
status = 0
|
||||
except ValueError: # pragma: no cover
|
||||
status = 500
|
||||
message = getattr(status_map.get(status), 'explanation', '')
|
||||
return dict(status=status, message=message)
|
||||
|
||||
@@ -20,23 +20,15 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To get an idea of how to develop applications with Pecan,
|
||||
here is a simple form:
|
||||
...or you can search the documentation here:
|
||||
</p>
|
||||
|
||||
<form method="POST" action="/">
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="name">Your Name:</label></td>
|
||||
<td><input name="name" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="age">Your Age:</label></td>
|
||||
<td><input name="age" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<input type="submit" value="Submit" />
|
||||
<fieldset>
|
||||
<input name="q" />
|
||||
<input type="submit" value="Search" />
|
||||
<fieldset>
|
||||
<small>Enter search terms or a module, class or function name.</small>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<%inherit file="layout.html"/>
|
||||
|
||||
## override the title
|
||||
<%def name="title()">
|
||||
Success!
|
||||
</%def>
|
||||
|
||||
## now define the body
|
||||
<header>
|
||||
<h1><img src="/images/logo.png" /></h1>
|
||||
</header>
|
||||
<p>Your form submission was successful! Thanks, ${name}!</p>
|
||||
<p><a href="/">Go Back</a></p>
|
||||
22
pecan/templates/project/+package+/tests/__init__.py_tmpl
Normal file
22
pecan/templates/project/+package+/tests/__init__.py_tmpl
Normal file
@@ -0,0 +1,22 @@
|
||||
import os
|
||||
from unittest import TestCase
|
||||
from pecan.configuration import set_config
|
||||
from pecan.testing import load_test_app
|
||||
|
||||
__all__ = ['FunctionalTest']
|
||||
|
||||
|
||||
class FunctionalTest(TestCase):
|
||||
"""
|
||||
Used for functional tests where you need to test your
|
||||
literal application and its integration with the framework.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.app = load_test_app(os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'config.py'
|
||||
))
|
||||
|
||||
def tearDown(self):
|
||||
set_config({}, overwrite=True)
|
||||
27
pecan/templates/project/+package+/tests/config.py_tmpl
Normal file
27
pecan/templates/project/+package+/tests/config.py_tmpl
Normal file
@@ -0,0 +1,27 @@
|
||||
# Server Specific Configurations
|
||||
server = {
|
||||
'port' : '8080',
|
||||
'host' : '0.0.0.0'
|
||||
}
|
||||
|
||||
# Pecan Application Configurations
|
||||
app = {
|
||||
'root' : '${package}.controllers.root.RootController',
|
||||
'modules' : ['${package}'],
|
||||
'static_root' : '%(confdir)s/../../public',
|
||||
'template_path' : '%(confdir)s/../templates',
|
||||
'reload' : True,
|
||||
'debug' : True,
|
||||
'logging' : False,
|
||||
'errors' : {
|
||||
'404' : '/error/404',
|
||||
'__force_dict__' : True
|
||||
}
|
||||
}
|
||||
|
||||
# Custom Configurations must be in Python dictionary format::
|
||||
#
|
||||
# foo = {'bar':'baz'}
|
||||
#
|
||||
# All configurations are accessible at::
|
||||
# pecan.conf
|
||||
@@ -1,41 +0,0 @@
|
||||
from unittest import TestCase
|
||||
import config
|
||||
|
||||
|
||||
class TestConfigServer(TestCase):
|
||||
|
||||
def test_server_port(self):
|
||||
assert config.server['port'] == '8080'
|
||||
|
||||
def test_server_host(self):
|
||||
assert config.server['host'] == '0.0.0.0'
|
||||
|
||||
|
||||
class TestConfigApp(TestCase):
|
||||
|
||||
def test_app_root(self):
|
||||
root = config.app['root']
|
||||
assert root.__class__.__name__ == 'RootController'
|
||||
|
||||
def test_app_modules(self):
|
||||
assert len(config.app['modules']) == 1
|
||||
|
||||
def test_app_static_root(self):
|
||||
assert 'public' in config.app['static_root']
|
||||
|
||||
def test_app_template_path(self):
|
||||
assert 'templates' in config.app['template_path']
|
||||
|
||||
def test_app_reload(self):
|
||||
assert config.app['reload']
|
||||
|
||||
def test_app_debug(self):
|
||||
assert config.app['debug']
|
||||
|
||||
def test_app_errors(self):
|
||||
errors = {
|
||||
'404' : '/error/404',
|
||||
'__force_dict__' : True
|
||||
}
|
||||
|
||||
assert config.app['errors'] == errors
|
||||
@@ -0,0 +1,19 @@
|
||||
from unittest import TestCase
|
||||
from webtest import TestApp
|
||||
from ${package}.tests import FunctionalTest
|
||||
|
||||
|
||||
class TestRootController(FunctionalTest):
|
||||
|
||||
def test_get(self):
|
||||
response = self.app.get('/')
|
||||
assert response.status_int == 200
|
||||
|
||||
def test_search(self):
|
||||
response = self.app.post('/', params={'q' : 'RestController'})
|
||||
assert response.status_int == 302
|
||||
assert response.headers['Location'] == 'http://pecan.readthedocs.org/en/latest/search.html?q=RestController'
|
||||
|
||||
def test_get_not_found(self):
|
||||
response = self.app.get('/a/bogus/url', expect_errors=True)
|
||||
assert response.status_int == 404
|
||||
@@ -1,18 +0,0 @@
|
||||
from unittest import TestCase
|
||||
from webtest import TestApp
|
||||
|
||||
import py.test
|
||||
|
||||
|
||||
class TestRootController(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.app = TestApp(py.test.wsgi_app)
|
||||
|
||||
def test_get(self):
|
||||
response = self.app.get('/')
|
||||
assert response.status_int == 200
|
||||
|
||||
def test_get_not_found(self):
|
||||
response = self.app.get('/a/bogus/url', expect_errors=True)
|
||||
assert response.status_int == 404
|
||||
7
pecan/templates/project/+package+/tests/test_units.py
Normal file
7
pecan/templates/project/+package+/tests/test_units.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestUnits(TestCase):
|
||||
|
||||
def test_units(self):
|
||||
assert 5 * 5 == 25
|
||||
@@ -20,9 +20,13 @@ div#content {
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 0 1em;
|
||||
padding: 1em;
|
||||
border: 5px transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
input.error {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[pytest]
|
||||
addopts = -p pecan.testing --with-config=./config.py
|
||||
6
pecan/templates/project/setup.cfg_tmpl
Normal file
6
pecan/templates/project/setup.cfg_tmpl
Normal file
@@ -0,0 +1,6 @@
|
||||
[nosetests]
|
||||
match=^test
|
||||
where=${package}
|
||||
nocapture=1
|
||||
cover-package=${package}
|
||||
cover-erase=1
|
||||
@@ -15,6 +15,7 @@ setup(
|
||||
install_requires = [
|
||||
"pecan",
|
||||
],
|
||||
test_suite = '${package}',
|
||||
zip_safe = False,
|
||||
paster_plugins = ${egg_plugins},
|
||||
include_package_data = True,
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
__all__ = ['collector']
|
||||
|
||||
def collector():
|
||||
try:
|
||||
from unittest import TestLoader
|
||||
assert hasattr(TestLoader, 'discover')
|
||||
return TestLoader().discover('pecan.tests')
|
||||
except:
|
||||
import unittest2
|
||||
return unittest2.collector
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
def setup_app(config):
|
||||
assert config.foo.sample_key == True
|
||||
return 'DEPLOYED!'
|
||||
@@ -1,9 +0,0 @@
|
||||
import sample_app
|
||||
|
||||
app = {
|
||||
'modules': ['sample_app']
|
||||
}
|
||||
|
||||
foo = {
|
||||
'sample_key': True
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import sample_app_missing
|
||||
|
||||
app = {
|
||||
'modules': ['sample_app_missing']
|
||||
}
|
||||
|
||||
foo = {
|
||||
'sample_key': True
|
||||
}
|
||||
Reference in New Issue
Block a user