7 min read

Adding unit tests

Apart from functional tests, we can also create pure Python test cases which the test runner can find. While functional tests cover application behavior, unit tests focus on program correctness. Ideally, every single Python method in the application should be tested.

The unit test layer does not load the Grok infrastructure, so tests should not take anything that comes with it for granted; just the basic Python behavior.

To add our unit tests, we’ll create a module named unit_tests.py. Remember, in order for the test runner to find our test modules, their names have to end with ‘tests’. Here’s what we will put in this file:

""" 
Do a Python test on the app.

:unittest:
"""

import unittest
from todo.app import Todo

class InitializationTest(unittest.TestCase):
todoapp = None

def setUp(self):
self.todoapp = Todo()

def test_title_set(self):
self.assertEqual(self.todoapp.title,u'To-do list manager')

def test_next_id_set(self):
self.assertEqual(self.todoapp.next_id,0)

The :unittest: comment at the top, is very important. Without it, the test runner will not know in which layer your tests should be executed, and will simply ignore them.

Unit tests are composed of test cases, and in theory, each should contain several related tests based on a specific area of the application’s functionality. The test cases use the TestCase class from the Python unittest module. In these tests, we define a single test case that contains two very simple tests.

We are not getting into the details here. Just notice that the test case can include a setUp and a tearDown method that can be used to perform any common initialization and destruction tasks which are needed to get the tests working and finishing cleanly.

Every test inside a test case needs to have the prefix ‘test’ in its name, so we have exactly two tests that fulfill this condition. Both of the tests need an instance of the Todo class to be executed, so we assign it as a class variable to the test case, and create it inside the setUp method. The tests are very simple and they just verify that the default property values are set on instance creation.

Both of the tests use the assertEqual method to tell the test runner that if the two values passed are different, the test should fail. To see them in action, we just run the bin/test command once more:

$ bin/test
Running tests at level 1
Running todo.FunctionalLayer tests:
Set up
in 2.691 seconds.
Running:
.......2009-09-30 22:00:50,703 INFO sqlalchemy.engine.base.
Engine.0x...684c PRAGMA table_info("users")

2009-09-30 22:00:50,703 INFO sqlalchemy.engine.base.Engine.0x...684c ()

Ran 7 tests with 0 failures and 0 errors in 0.420 seconds.
Running zope.testing.testrunner.layer.UnitTests tests:
Tear down todo.FunctionalLayer ... not supported
Running in a subprocess.
Set up zope.testing.testrunner.layer.UnitTests in 0.000 seconds.
Ran 2 tests with 0 failures and 0 errors in 0.000 seconds.
Tear down zope.testing.testrunner.layer.UnitTests in 0.000 seconds.
Total: 9 tests, 0 failures, 0 errors in 5.795 seconds

Now, both the functional and unit test layers contain some tests and both are run one after the other. We can see the subtotal for each layer at the end of its tests as well as the grand total of the nine passed tests when the test runner finishes its work.

Extending the test suite

Of course, we just scratched the surface of which tests should be added to our application. If we continue to add tests, hundreds of tests may be there by the time we finish. However, this article is not the place to do so.

As mentioned earlier, its way easier to have tests for each part of our application, if we add them as we code. There’s no hiding from the fact that testing is a lot of work, but there is great value in having a complete test suite for our applications. More so, when third parties might use our work product independently.

Debugging

We will now take a quick look at the debugging facilities offered by Grok. Even if we have a very thorough test suite, chances are there that we will find a fair number of bugs in our application. When that happens, we need a quick and effective way to inspect the code as it runs and find the problem spots easily.

Often, developers will use print statements (placed at key lines) throughout the code, in the hopes of finding the problem spot. While this is usually a good way to begin locating sore spots in the code, we often need some way to follow the code line by line to really find out what’s wrong. In the next section, we’ll see how to use the Python debugger to step through the code and find the problem spots. We’ll also take a quick look at how to do post-mortem debugging in Grok, which means jumping into the debugger to analyze program state immediately after an exception has occurred.

Debugging in Grok

For regular debugging, where we need to step through the code to see what’s going on inside, the Python debugger is an excellent tool. To use it, you just have to add the next line at the point where you wish to start debugging:

import pdb; pdb.set_trace()

Let’s try it out. Open the app.py module and change the add method of the AddProjectForm class (line 108) to look like this:

@grok.action('Add project') 
def add(self,**data):
import pdb; pdb.set_trace()
project = Project()
project.creator = self.request.principal.title
project.creation_date = datetime.datetime.now()
project.modification_date = datetime.datetime.now()
self.applyData(project,**data)
id = str(self.context.next_id)
self.context.next_id = self.context.next_id+1
self.context[id] = project
return self.redirect(self.url(self.context[id]))

Notice that we invoke the debugger at the beginning of the method. Now, start the instance, go to the ‘add project’ form, fill it up, and submit it. Instead of seeing the new project view, the browser will stay at the ‘add form’ page, and display the waiting for… message. This is because control has been transferred to the console for the debugger to act. Your console will look like this:


> /home/cguardia/work/virtual/grok1/todo/src/todo/app.py(109)add()
-> project = Project()
(Pdb) |

The debugger is now active and waiting for input. Notice that the line number where debugging started, appears right beside the path of the module where we are located. After the line number, comes the name of the method, add(). Below that, the next line of code to be executed is shown.

The debugger commands are simple. To execute the current line, type n:

(Pdb) n 
> /home/cguardia/work/virtual/grok1/todo/src/todo/app.py(110)add()
-> project.creator = self.request.principal.title
(Pdb)

You can see the available commands if you type h:

(Pdb) h 

Documented commands (type help <topic>):
========================================
EOF break condition disable help list q step w
a bt cont down ignore n quit tbreak whatis
alias c continue enable j next r u where
args cl d exit jump p return unalias
b clear debug h l pp s up

Miscellaneous help topics:
==========================
exec pdb

Undocumented commands:
======================
retval rv

(Pdb)

The list command id is used for getting a bird’s eye view of where in the code are we:

(Pdb) list 
105
106 @grok.action('Add project')
107 def add(self,**data):
108 import pdb; pdb.set_trace()
109 project = Project()
110 -> project.creator = self.request.principal.title
111 project.creation_date = datetime.datetime.now()
112 project.modification_date = datetime.datetime.now()
113 self.applyData(project,**data)
114 id = str(self.context.next_id)
115 self.context.next_id = self.context.next_id+1
(Pdb)

As you can see, the current line is shown with an arrow.

It’s possible to type in the names of objects within the current execution context and find out their values:

(Pdb) project 
<todo.app.Project object at 0xa0ef72c>
(Pdb) data
{'kind': 'personal', 'description': u'Nothing', 'title':
u'Project about nothing'}

(Pdb)

We can of course, continue stepping line by line through all of the code in the application, including Grok’s own code, checking values as we proceed. When we are through reviewing, we can click on c to return control to the browser. At this point, we will see the project view.

The Python debugger is very easy to use and it can be invaluable for finding obscure bugs in your code.

LEAVE A REPLY

Please enter your comment!
Please enter your name here