Skip to content. | Skip to navigation

Sections
Personal tools
What is this?
Hi, my name is Tom Lazar and I'm a Plone and Zope developer based in Berlin, Germany and this is my personal and professional (no big difference, really...) website.
 

tdd

Oct 26, 2007

Debugging Functional Browser tests

Filed Under:

I've just had one of those slap-yourself-on-the-forehead-why-haven't-I-thought-of-this-before moments that I'd like to share with my esteemed readership.

In Martin Aspeli's new (and highly recommended) book Professional Plone Development he shares a handy little tidbit regarding the debugging of the raw console output of a browser test's page contents:

"Because reading raw HTML in the console can be a little cumbersome, the author sometimes uses this trick during debugging:

>>> open('/tmp/test-output.html', 'w').write(browser.contents)

[...] When executed, this statement will write the current contents of the test-browser to /tmp/test-output.html. Images and stylesheets will not work, but the text in the page is normally enough to understand why some output is not as expected."

That's a real life-saver right there. But it can still be a bit cumbersome if you're debugging a form interactively. So I've done the following:

  1. Subclass Five's Browser class
  2. add a convenience method for dumping the contents
  3. in tests import the extended browser class

In a nutshell this is what I added to my current project's base.py test class:

from Products.Five.testbrowser import Browser as BaseBrowser
[...]
class Browser(BaseBrowser):

    def dc(self, filename="/tmp/test-output.html"):
        open(filename, 'w').write(self.contents)

In my tests I use the abovementioned browser instead of Five's and whenever I'm in a debug session, I just enter browser.dc() to get a 'snapshot' of the current browser page. The tmp-file is left open in my editor (TextMate, which does an excellent job of picking up refreshed contents)...

Debugging a form with a large number of fields, spread out over multiple pages has just become a whole lot easier ;-)

Sep 20, 2007

Debugging browser tests in Plone 3.0

Filed Under:

Some things were better back in the old days...

Plone 3.0 is a lot more tightlipped about displaying potentially sensitive error messages. Instead it just displays a log number which can then be used to send in feedback. That's great. Except when you're writing browsertests, because then you don't have a log to look up the number...

Naturally, this makes debugging page templates using browser tests more tedious (than it already is). Solution? Use the verbose error message template from Plone 2.5. Like so:

svn cat https://svn.plone.org/svn/plone/CMFPlone/tags/2.5.3/skins/plone_templates/default_error_message.pt > parts/plone/CMFPlone/skins/plone_templates/default_error_message.pt

Now you can actually see which attribute caused an AttributeError etc. Usually, that helps ;-)

Aug 24, 2007

Integrating pure Python tests with PloneTestCase

Filed Under:

Running tests takes long enough as it is...

Often, when writing a Plone Product I come across small methods that I need to implement, that actually have no dependency upon Plone itself but are too trivial and/or specific as to warrant putting them into a separate package. If that's the case for you, too, you will undoubtedly start to get impatient when testing these methods, because with each friggin' testrun an entire zope instance is created and torn down again. This I have become used to and accept it as part of life as Zope developer, if the code I'm testing actually needs that setup. But it just kept bugging me that I need to wait so long for tests that actually execute in under one second. So I've poked around today and came up with the following scenario:

  1. put your methods into a separate module that has no import dependencies from zope
  2. add a test runner to that module to execute its tests without starting up zope
  3. create a test suite for your product that will pick up the tests of your module

1. standalone module

For example, I've created a (crude) helper for batching some items in a file named batching.py on the root level of my Product (along with a little doctest testing some arbitrary edge cases):
def batch(items=[], batch=0, batchsize=5):
"""returns a batch from the given items

>>> items = range(12)
>>> batch(items, 3, 5)
Traceback (most recent call last):
...
IndexError: No batch number 3

>>> batch(items, 0, 5)
[0, 1, 2, 3, 4]

>>> batch(items, 1, 5)
[5, 6, 7, 8, 9]

>>> batch(items, 2, 5)
[10, 11]

>>> batch(items, 2, 4)
[8, 9, 10, 11]
"""
if batch >= batchnum(len(items), batchsize):
raise IndexError, \
"No batch number %d" % batch
else:
return items[batch * batchsize:(batch+1) * batchsize]
I can use this method in my product with the following import statement:
from Products.PRODUCTNAME.batching import batch

2. add a test runner

Here you just need to follow the straightforward example given in the doctest documentation and add the following snippet to the bottom of batching.py:
if __name__ == "__main__":
import doctest
doctest.testmod()
Now you can simply execute your module and it will run its tests at blazing speed - buckle up, Dorothy... This definitely makes for much nicer test driven development!

3. Zope integration

Now we just need to make sure, that the tests will also be executed, when we run our Product's entire suite. This is done by writing a light-weight  Testcase based on Zope's DocTestSuite that 'picks' up the standalone tests. I've named mine 'testUtilities.py' and stuck it right into my Product's 'test' folder:
import unittest
from unittest import TestSuite
from zope.testing.doctestunit import DocTestSuite

def test_suite():
return TestSuite((
DocTestSuite('Products.PRODUCTNAME.batching'),
))

if __name__ == '__main__':
unittest.main(defaultTest="test_suite")
Edit: removed some superflous code in the examples.