[Checkins] SVN: zc.async/trunk/src/zc/async/ added helpers for setting up and tearing down Zope 3 functional tests

Gary Poster gary at zope.com
Wed Jun 25 23:31:06 EDT 2008


Log message for revision 87783:
  added helpers for setting up and tearing down Zope 3 functional tests
  (ftesting.py), and a discussion of how to write Zope 3 functional tests with
  layers (zope.app.testing.functional) in ftesting.txt.
  
  
  

Changed:
  U   zc.async/trunk/src/zc/async/CHANGES.txt
  A   zc.async/trunk/src/zc/async/ftesting.py
  A   zc.async/trunk/src/zc/async/ftesting.txt
  U   zc.async/trunk/src/zc/async/tests.py

-=-
Modified: zc.async/trunk/src/zc/async/CHANGES.txt
===================================================================
--- zc.async/trunk/src/zc/async/CHANGES.txt	2008-06-26 02:46:51 UTC (rev 87782)
+++ zc.async/trunk/src/zc/async/CHANGES.txt	2008-06-26 03:31:04 UTC (rev 87783)
@@ -1,6 +1,10 @@
 (unreleased)
 ============
 
+- added helpers for setting up and tearing down Zope 3 functional tests
+  (ftesting.py), and a discussion of how to write Zope 3 functional tests with
+  layers (zope.app.testing.functional) in ftesting.txt.
+
 - remove obsolete retry approach for success/failure callbacks
   (``completeStartedJobArguments``): it is now handled by retry policies.
 

Added: zc.async/trunk/src/zc/async/ftesting.py
===================================================================
--- zc.async/trunk/src/zc/async/ftesting.py	                        (rev 0)
+++ zc.async/trunk/src/zc/async/ftesting.py	2008-06-26 03:31:04 UTC (rev 87783)
@@ -0,0 +1,38 @@
+import sys
+import transaction
+import zope.component
+
+import zc.async.dispatcher
+import zc.async.subscribers
+import zc.async.testing
+
+# helper functions convenient for Zope 3 functional tests
+
+def setUpAsync(
+    connection=None,
+    queue_installer=zc.async.subscribers.queue_installer,
+    dispatcher_installer=zc.async.subscribers.ThreadedDispatcherInstaller(
+        poll_interval=0.1),
+    agent_installer=zc.async.subscribers.agent_installer):
+    """Set up zc.async, as is needed for Zope 3 functional tests.
+    """
+    if connection is None:
+        connection = sys._getframe(1).f_locals['getRootObject']()._p_jar
+    db = connection.db()
+    zope.component.provideHandler(agent_installer)
+    event = zc.async.interfaces.DatabaseOpened(db)
+    
+    dispatcher_installer(event)
+    dispatcher = zc.async.dispatcher.get()
+    _ = transaction.begin()
+    queue_installer(event)
+    zc.async.testing.get_poll(dispatcher, count=0)
+    assert "" in zc.async.testing.get_poll(dispatcher)
+    assert dispatcher.activated is not None
+
+
+def tearDownAsync():
+    dispatcher = zc.async.dispatcher.get()
+    dispatcher.reactor.callFromThread(dispatcher.reactor.stop)
+    dispatcher.thread.join(3)
+

Added: zc.async/trunk/src/zc/async/ftesting.txt
===================================================================
--- zc.async/trunk/src/zc/async/ftesting.txt	                        (rev 0)
+++ zc.async/trunk/src/zc/async/ftesting.txt	2008-06-26 03:31:04 UTC (rev 87783)
@@ -0,0 +1,143 @@
+Normally, in a Zope 3 configuration that uses zc.async, you configure it
+when you start your application.  For instance, you might include a zc.async
+zcml file like basic_dispatcher_policy.zcml that performs the necessary set up.
+
+However, the Zope 3 ftesting layer database dance doesn't play well with
+zc.async unless you take a bit of extra care.
+
+This is because zc.async will be started with the ftests' underlying database,
+and then the test will be run with a DemoStorage wrapper. The zc.async
+dispatcher will run, then, but it will never see the changes that you make in
+the wrapper DemoStorage that your test manipulates.  This can be mystifying
+and frustrating.
+
+Because of this, when you write a Zope 3 app that wants to use both layered
+ftests and zc.async, you have to set things up in a mildly inconvenient way.
+
+When you start your application normally, use configuration (zcml or grok or
+whatever) to register subscribers like the ones in subscribers.py: adding
+queues, starting dispatchers, and adding agents.
+
+But don't have this configuration registered for your ftests. Instead, bypass
+that part of your site's configuration in your ftesting layer, and use the
+``zc.async.ftesting.setUpAsync`` function to set zc.async up in tests when you
+need it, in a footnote of your test or in a similar spot.
+
+You'll still want the basic adapters registered, as found in zc.async's
+configure.zcml or configure.py files; and maybe the
+zc.async.queue.getDefaultQueue adapter too.  This can be registered in
+ftesting.zcml with this snippet::
+
+  <include package="zc.async" />
+  <adapter factory="zc.async.queue.getDefaultQueue" />
+
+Or in Python, you might want to do something like this:
+
+    >>> import zc.async.configure
+    >>> zc.async.configure.base() # or, more likely, ``minimal`` for Zope 3
+    >>> import zope.component
+    >>> import zc.async.queue
+    >>> zope.component.provideAdapter(zc.async.queue.getDefaultQueue)
+
+Don't forget to call ``tearDownAsync`` (see below) at the end of your test!
+
+Here's a usage example.
+
+As mentioned above, ``setUpAsync`` does expect the necessary basic adapters to
+already be installed.
+
+Zope 3 ftests generally have a ``getRootObject`` hanging around to give you the
+root object in the Zope application (but not in the ZODB).  Therefore, this
+function tries to be helpful, for better and worse, and muck around in the
+locals to find it.  If you want it to leave your locals alone, pass it a
+database connection.
+
+So, here's some set up.  We create a database and make our stub
+``getRootObject`` function.  
+
+    >>> import transaction
+    >>> import BTrees
+    >>> import ZODB.FileStorage
+    >>> storage = ZODB.FileStorage.FileStorage(
+    ...     'zc_async.fs', create=True)
+    >>> from ZODB.DB import DB
+    >>> db = DB(storage)
+    >>> conn = db.open()
+    >>> root = conn.root()
+    >>> PseudoZopeRoot = root['Application'] = BTrees.family32.OO.BTree()
+    >>> transaction.commit()
+    >>> def getRootObject():
+    ...     return PseudoZopeRoot
+    ...
+
+Notice we are using a real FileStorage, and not a DemoStorage, as is usually
+used in ftests.  The fact that DemoStorage does not have MVCC can sometimes
+lead standard ftests to raise spurious ReadConflictErrors that will not
+actually occur in production.  The ConflictErrors will generally be retried, so
+your tests should usually pass, even though you might see some "complaints".
+
+Now we can call ``setUpAsync`` as if we were in a functional test.
+
+    >>> import zc.async.ftesting
+    >>> zc.async.ftesting.setUpAsync()
+
+Now the dispatcher is activated and the polls are running.  The function sets
+up a dispatcher that polls much more frequently than usual--every 0.1 seconds
+rather than every 5, so that tests might run faster--but otherwise uses
+typical zc.async default values.
+
+It's worth noting a few tricks that are particularly useful for tests here. 
+We'll also use a couple of them to verify that ``setUpAsync`` did its work.
+
+``zc.async.dispatcher.get()`` returns the currently installed dispatcher. This
+can let you check if it is activated and polling and use its simple statistical
+methods, if you want.
+
+    >>> import zc.async.dispatcher
+    >>> dispatcher = zc.async.dispatcher.get()
+
+For now, we'll just see that the dispatcher is activated.
+
+    >>> bool(dispatcher.activated)
+    True
+
+See the dispatcher.txt for information on information you can get from the
+dispatcher object.
+
+zc.async.testing has a number of helpful functions for testing. ``get_poll`` is
+the most pertinent here: given a dispatcher, it will give you the next poll. 
+This is a good way to make sure that a job you just put in has had a chance to
+be claimed by a dispatcher.  It's also a reasonable way to verify that the
+dispatcher has started.  ``setUpAsync`` already gets the first two polls, so
+it's definitely all started.
+
+    >>> import zc.async.testing
+    >>> import pprint
+    >>> pprint.pprint(zc.async.testing.get_poll(dispatcher))
+    {'': {'main': {'active jobs': [],
+                   'error': None,
+                   'len': 0,
+                   'new jobs': [],
+                   'size': 3}}}
+
+Other useful testing functions are ``zc.async.testing.wait_for_result``, which
+waits for the result on a give job and returns it; and
+``zc.async.testing.wait_for_annotation``, which waits for a given annotation
+on a given job.  These are demonstrated in various doctests in this package,
+but should also be reasonably simple and self-explanatory.
+
+Once you have finished your tests, make sure to shut down your dispatcher, or
+the testing framework will complain about an unstopped daemon thread.
+zc.async.ftesting.tearDownAsync will do the trick.
+
+    >>> zc.async.ftesting.tearDownAsync()
+    >>> dispatcher.activated
+    False
+
+If you used a FileStorage, as we did here, you'll need to clean up as well.
+
+    >>> db.close()
+    >>> storage.close()
+    >>> storage.cleanup()
+
+    >>> del storage # we just do this to not confuse our own tearDown code.


Property changes on: zc.async/trunk/src/zc/async/ftesting.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: zc.async/trunk/src/zc/async/tests.py
===================================================================
--- zc.async/trunk/src/zc/async/tests.py	2008-06-26 02:46:51 UTC (rev 87782)
+++ zc.async/trunk/src/zc/async/tests.py	2008-06-26 03:31:04 UTC (rev 87783)
@@ -144,6 +144,7 @@
             'README.txt',
             'README_2.txt',
             'catastrophes.txt',
+            'ftesting.txt',
             setUp=modSetUp, tearDown=modTearDown,
             optionflags=doctest.INTERPRET_FOOTNOTES),
         ))



More information about the Checkins mailing list