Chapter 43
Writing Functional Tests

Difficulty

Newcomer

Skills

Problem/Task

Unit tests cover a large part of the testing requirements listed in the eXtreme Programming literature, but are not everything. There are also integration and functional tests. While integration tests can be handled with unit tests and doctests, functional tests cannot. For this reason the Zope 3 community members developed an extension to unittest that handles functional tests. This package is introduced in this chapter.

Solution

Unit tests are very good for testing the functionality of a particular object in absence of the environment it will eventually live in. Integration tests build on this by testing the behavior of an object in a limited environment. Then functional tests should test the behavior of an object in a fully running system. Therefore functional tests often check the user interface behavior and it is not surprising that they are found in the browser packages of Zope 3. In fact, in Zope 3’s implementation of functional tests there exists a base test case class for each view type, such as zope.testing.functional.BrowserTestCase or zope.app.dav.ftests.dav.DAVTestCase.

43.1 The Browser Test Case

Each custom functional test case class provides some valuable methods that help us write the tests in a fast and efficient manner. Here are the methods that the BrowserTestCase class provides.

43.2 Testing “ZPT Page” Views

Okay, now that we know how the BrowserTestCase extends the normal unittest.TestCase, we can use it to write some functional tests for the “add”, “edit” and “index” view of the “ZPT Page” content type.

Anywhere, create a file called test_zptpage.py and add the following functional testing code:


1  import time
2  import unittest
3  
4  from transaction import get_transaction
5  from zope.app.tests.functional import BrowserTestCase
6  from zope.app.zptpage.zptpage import ZPTPage
7  
8  class ZPTPageTests(BrowserTestCase):
9      """Funcional tests for ZPT Page."""
10  
11      template = u'''\
12      <html>
13        <body>
14          <h1 tal:content="modules/time/asctime" />
15        </body>
16      </html>'''
17  
18      template2 = u'''\
19      <html>
20        <body>
21          <h1 tal:content="modules/time/asctime">time</h1>
22        </body>
23      </html>'''
24  
25      def createPage(self):
26          root = self.getRootFolder()
27          root['zptpage'] = ZPTPage()
28          root['zptpage'].setSource(self.template, 'text/html')
29          get_transaction().commit()
30  
31      def test_add(self):
32          response = self.publish(
33              "/+/zope.app.zptpage.ZPTPage=",
34              basic='mgr:mgrpw',
35              form={'add_input_name' : u'newzptpage',
36                    'field.expand.used' : u'',
37                    'field.source' : self.template,
38                    'field.evaluateInlineCode.used' : u'',
39                    'field.evaluateInlineCode' : u'on',
40                    'UPDATE_SUBMIT' : 'Add'})
41  
42          self.assertEqual(response.getStatus(), 302)
43          self.assertEqual(response.getHeader('Location'),
44                           'http://localhost/@@contents.html')
45  
46          zpt = self.getRootFolder()['newzptpage']
47          self.assertEqual(zpt.getSource(), self.template)
48          self.assertEqual(zpt.evaluateInlineCode, True)
49  
50      def test_editCode(self):
51          self.createPage()
52          response = self.publish(
53              "/zptpage/@@edit.html",
54              basic='mgr:mgrpw',
55              form={'field.expand.used' : u'',
56                    'field.source' : self.template2,
57                    'UPDATE_SUBMIT' : 'Change'})
58          self.assertEqual(response.getStatus(), 200)
59          self.assert_('&gt;time&lt;' in response.getBody())
60          zpt = self.getRootFolder()['zptpage']
61          self.assertEqual(zpt.getSource(), self.template2)
62          self.checkForBrokenLinks(response.getBody(), response.getPath(),
63                                   'mgr:mgrpw')
64  
65      def test_index(self):
66          self.createPage()
67          t = time.asctime()
68          response = self.publish("/zptpage", basic='mgr:mgrpw')
69          self.assertEqual(response.getStatus(), 200)
70          self.assert_(response.getBody().find('<h1>'+t+'</h1>') != -1)
71  
72  def test_suite():
73      return unittest.TestSuite((
74          unittest.makeSuite(ZPTPageTests),
75          ))
76  
77  if __name__=='__main__':
78      unittest.main(defaultTest='test_suite')

43.3 Running Functional Tests

The testing code directly depends on the Zope 3 source tree, so make sure to have it in your Python path. In Un*x/Linux we can do this using


1  export PYTHONPATH=$PYTHONPATH:ZOPE3/src

where ZOPE3 is the path to our Zope 3 installation. Furthermore, functional tests depend on finding a file called ftesting.zcml, which is used to bring up the Zope 3 application server. Therefore it is best to just go to the directory ZOPE3, since there exists such a file. You can now execute our new funtional tests using


1  python path/to/ftest/test_zptpage.py

You will notice that these tests will take a couple seconds (5-10 seconds) to run. This is okay, since the functional tests have to bring up the entire Zope 3 system, which by itself will take about 4-10 seconds.


1  ...
2  ----------------------------------------------------------------------
3  Ran 3 tests in 16.623s
4  
5  OK

As usual you also use the test runner to execute the tests.

Exercises

  1. Add another functional test that checks the “Preview” and “Inline Code” screen.