====================
Functional doc tests
====================

Doctests are a way to write tests while documenting the thing that is
tested at the same time.  As an example, this file both documents
functional doc tests *and* tests them.

Doctests look like regular interactive interpreter sessions.  That
makes them very easy to create.  Doctests can either occur in an
object's or method's docstring or in a separate file.  Use either
``DocTestSuite`` or ``DocFileSuite`` in these cases.


Creating functional doctests
----------------------------

Creating functional doctests is just as easy.  Obviously, you cannot
simply use an interpreter shell for the initial test creation.
Instead, you can use the `tcpwatch` program to record browser sessions
and turn them into tests:

1. Start out with a clean ZODB database.

   - Create a folder named `test_folder_1_` in the root folder.

   - Create a user in the root user folder called `test_user_1_` with
     the password `secret`.

    - Create a role `test_role_1_` and grant the role to the test
      user.  Grant the permissions 'Access contents information' and
      'View' to the role.

2. Install tcpwatch.  You can get a recent version from Zope CVS:
   http://cvs.zope.org/Packages/tcpwatch/

3. Create a temporary directory to record tcpwatch output.

4. Run tcpwatch using:
   tcpwatch.py -L 8081:8080 -s -r tmpdir
   (the ports are the listening port and forwarded-to port; the
   second port must match the Zope configuration)

5. In a browser, connect to the listening port and do whatever needs
   to be recorded.

6. Shut down tcpwatch.

7. Run the script at Zope/lib/python/Testing/ZopeTestCase/doctest/dochttp.py
   python2.3 dochttp.py tmpdir > .../mytest.txt

8. Edit the generated text file to add explanations and elide
   uninteresting portions of the output.

9. In a functional test module (usually ftests.py), import
   ``FunctionalDocFileSuite`` and instantiate it, passing the name of the
   text file containing the test.  For example:

   import os, sys
   if __name__ == '__main__':
       execfile(os.path.join(sys.path[0], 'framework.py'))

   from Testing import ZopeTestCase

   from unittest import TestSuite
   from Testing.ZopeTestCase import FunctionalDocFileSuite

   def test_suite():
       suite = TestSuite()
       suite.addTest(FunctionalDocFileSuite('FunctionalDocTest.txt'))
       return suite

   if __name__ == '__main__':
       framework()


Examples
--------

Test Publish Document

  >>> print http(r"""
  ... GET /test_folder_1_/index_html HTTP/1.1
  ... """, handle_errors=False)
  HTTP/1.1 200 OK
  Content-Length: 5
  Content-Type: text/plain
  <BLANKLINE>
  index

Test Publish Script

  >>> print http(r"""
  ... GET /test_folder_1_/script HTTP/1.1
  ... """, handle_errors=False)
  HTTP/1.1 200 OK
  Content-Length: 1
  Content-Type: text/plain
  <BLANKLINE>
  1

Test Publish Script with Argument

  >>> print http(r"""
  ... GET /test_folder_1_/script?a:int=2 HTTP/1.1
  ... """, handle_errors=False)
  HTTP/1.1 200 OK
  Content-Length: 1
  Content-Type: text/plain
  <BLANKLINE>
  3

Test Server Error

  >>> print http(r"""
  ... GET /test_folder_1_/script?a=2 HTTP/1.1
  ... """, handle_errors=False)
  HTTP/1.1 500 Internal Server Error
  ...Content-Type: text/html...exceptions.TypeError...

Test Unauthorized

  >>> self.folder.index_html.manage_permission('View', ['Owner'])
  >>> print http(r"""
  ... GET /test_folder_1_/index_html HTTP/1.1
  ... """, handle_errors=False)
  HTTP/1.1 401 Unauthorized
  ...

Test Basic Authentication

  >>> from AccessControl.Permissions import manage_properties
  >>> self.setPermissions([manage_properties])

  >>> print http(r"""
  ... GET /test_folder_1_/index_html/change_title?title=Foo HTTP/1.1
  ... Authorization: Basic %s
  ... """ % user_auth, handle_errors=False)
  HTTP/1.1 200 OK
  Content-Length: 0
  ...

  >>> self.folder.index_html.title_or_id()
  'Foo'

Test passing in non-base64-encoded login/pass

  >>> from Testing.ZopeTestCase import user_name, user_password
  >>> print http(r"""
  ... GET /test_folder_1_/index_html/change_title?title=Baz HTTP/1.1
  ... Authorization: Basic %s:%s
  ... """ % (user_name, user_password), handle_errors=False)
  HTTP/1.1 200 OK
  Content-Length: 0
  ...

  >>> self.folder.index_html.title_or_id()
  'Baz'

