Step 02: Unit and Functional Testing

Sure, testing helps ensure future quality and facilitate refactoring. But it also makes up-front development faster, particularly in smart editors and IDEs. Restarting your app and clicky-clicking in your browser is a drag.

In this step keep the same code as step01 but here we add some tests.

Goals

  • Create unit tests on code
  • Create functional tests on responses

Objectives

  • Write a Pyramid-style unit test
  • Use WebTest to include a functional test in the tests module
  • Use nose and the nosetests test runner to execute tests

Steps

  1. $ cd ../../creatingux; mkdir step02; cd step02

    This ensures that you make the next directory in the right location.

  2. Copy the following into step02/application.py:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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 module as we saw in step01.

  3. Copy the following into step02/tests.py:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import unittest
    
    class ProjectorViewsUnitTests(unittest.TestCase):
        def test_hello_world(self):
            from application import hello_world
            result = hello_world({})
            self.assertEqual(result.body, b'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.assertTrue(b'hello' in res.body)
    
  4. $ nosetests

    You should see the following output:

    ..
    --------------------------------------------------------
    Ran 2 tests in 0.301s
    
    OK
    

Extra Credit

  1. How does nose know the tests are in tests.py?
  2. Did WebTest really launch an HTTP server and issue an HTTP request?
  3. If your code generates an error, will Pyramid handle it gracefully?

Analysis

Unit tests are hard. Unit tests with a framework is even harder. The culture of Pyramid, though, is dedicated to full test coverage, and thus Pyramid works very hard to make test writing a productive experience.

Even if you don’t provide full test coverage, you will find that the most basic unit test will catch obvious errors quicker than re-clicking in your browser on every change. This is somewhat similar to setting up your editor or IDE to run pylint to let you know, before saving (much less before executing) if you have a bogus variable.

Functional tests are even easier to write, and for UX people, help on the part of the problem that is the focus.

Discussion

  • Pyramid (and repoze.bfg before it) and the commitment to test coverage
  • Philosophies on unit tests vs. functional tests vs. doctests
  • The challenge in setup/teardown regarding configuration, registries, and machinery under the surface (both the frameworks and yours!)

Table Of Contents