Part 2: concept, installation and simple “hello world” applicationStep 02: unit and functional testing
Of course, testing helps ensure future quality and facilitates refactoring. And this, of course, makes development faster, especially when using smart editors and
IDEs . Restarting your application and clicking on your browser is a drag.
In this step, the same code is used as in step one, but we will add some tests.
')
Goals
- Cover code with unit tests
- Create functional response tests
Technical requirements
- Write a unit test in Pyramid style
- Use WebTest to include a function test in the test module.
- Use the nose and nosetests viewer to run tests.
Steps
$ cd ../../creatingux; mkdir step02; cd step02
Create a directory in the right place.
Copy the following into the newly created file
step02/application.py
:
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world (request):
return response ( 'hello!' )
def main ():
config = Configurator ()
config . add_view (hello_world)
app = config . make_wsgi_app ()
return app
if __name__ == '__main__' :
app = main ()
server = make_server ( '0.0.0.0' , 8080 , app)
server . serve_forever ()
This is the same code that we saw in step one.
Copy the following in
step02/tests.py
:
import unittest
class ProjectorViewsUnitTests (unittest . TestCase):
def test_hello_world ( self ):
from application import hello_world
result = hello_world ({})
self . assertEqual (result . body, 'hello!' )
class ProjectorFunctionalTests (unittest . TestCase):
def setUp ( self ):
from application import main
app = main ()
from webtest import TestApp
self . testapp = TestApp (app)
def test_it ( self ):
res = self . testapp . get ( '/' , status = 200 )
self . failUnless ( 'hello' in res . body)
$ nosetests
After that we should see the following result:
..
--------------------------------------------------------
Ran 2 tests in 0.301s
OK
Additional questions
- How does nose know what tests are in the tests.py file?
- Does WebTest run a real HTTP server to send an HTTP request?
- If your code gives an error, does Pyramid handle it correctly?
Analysis
Unit tests are hard. Unit tests with the framework are even more difficult. The Pyramid culture, in spite of this, aims at full test coverage, and Pyramid works very hard to make writing tests a useful activity.
Even if you do not do full test coverage, you will find that most basic unit tests catch standard errors faster than opening your browser, for verification, with each code change. This is a bit like installing your editor, or IDE, to start
pylint which allows you to know before saving (much less before executing) whether you have any errors.
Functional tests are easier to write, and for UX-people, they help with the problem in question.
Theses
- Pyramid (and repoze.bfg before it) and loyalty to the test coverage
- Philosophy of unit testing against functional tests, against doc tests
- Configuration, registries, and machinery under the surface (both your frameworks and yours!)
Step 03: Hello World at Chameleon
Most web systems have a template language for generating HTML. This gives the UX-person the opportunity to concentrate on the thing they know (the markup) and interspersed in the code, and not vice versa.
Pyramid doesn’t have much to say about languages. This tutorial does though. We're Chameleon / ZPT folks. So let's do “hello world” using the page template.
Goals
- The easiest possible step to understand the pattern
Technical task
- Move the views (views) to a separate module
- Change application.py to find a module to declare views.
- Demonstrate the freshness of rendering and data-oriented views, especially for testing.
- Bring tests to data-oriented views.
Steps
Recall that the installation section
$ export PYRAMID_RELOAD_TEMPLATES=1
to make
$ export PYRAMID_RELOAD_TEMPLATES=1
, which allows you to edit templates and not have to reload your Pyramid application.
$ cd ../../creatingux; mkdir step03; cd step03
Copy the following to
step03/application.py
:
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
def main ():
config = Configurator ()
config . scan ( "views" )
app = config . make_wsgi_app ()
return app
if __name__ == '__main__' :
app = main ()
server = make_server ( '0.0.0.0' , 8080 , app)
server . serve_forever ()
Copy the following into
step03/views.py
:
from pyramid.view import view_config
@view_config (renderer = "hello.pt" )
def hello_view (request):
return { "tutorial" : "Little Dummy" }
And further, the HTML template layout is
step03/hello.pt
in
step03/hello.pt
:
<html>
<head>
<title> Hello </ title>
</ head>
<body>
<p> Hello, $ {tutorial} </ p>
</ body>
</ html>
And here it is -
step03/tests.py
:
import unittest
class ProjectorViewsUnitTests (unittest . TestCase):
def test_hello_view ( self ):
from views import hello_view
result = hello_view ({})
self . assertEqual (result [ 'tutorial' ], 'Little Dummy' )
class ProjectorFunctionalTests (unittest . TestCase):
def setUp ( self ):
from application import main
app = main ()
from webtest import TestApp
self . testapp = TestApp (app)
def test_it ( self ):
res = self . testapp . get ( '/' , status = 200 )
self . failUnless ( 'Hello' in res . body)
And finally, we write:
$ nosetests
Following what should see:
..
----------------------------------------------------------------
Ran 2 tests in 0.885s
OK
Trying to run:
$ python application.py
And open
127.0.0.1:8080
in your browser.
Additional questions
If you are editing a theme, do you need to restart the application to see the changes?
What other values are possible for the renderer, on @view_config?
Analysis
This step gives an idea of code parsing. There are several different ways to make configuration in Pyramid: imperative (which we saw in the first step: Hello World in Pyramid), scanning (close to many modern web frameworks), and our old friend ZCML. The choice is mainly one of the style, though there are some sharp edges in some cases.
Note the coolness ... all you have to do is return the dictionary to your submission, and your template is invoked on the way out the door, with this data.
Theses
Configuration history in Zope2, Zope3, BFG, then Pyramid
How things worked before renderers
Following is
part 4 .