Chapter 7: Testing and Debugging
As you develop Zope applications you will run into problems. This chapter covers debugging and testing techniques that can help you. The Zope debugger allow you to peek inside a running process and find exactly what is going wrong. Unit testing allows you to automate the testing process to ensure that your code still works correctly as you change it. Finally, Zope provides logging facilities which allow you to emit warnings and error messages.
Anonymous User - Jan. 24, 2002 7:01 am - (This comment does not really fit here but I think it is important) Use the source! If you want to know how a function in Zope doesn't do what it should try to find it: "cd $ZOPE_HOME/lib/python; grep -irs "def foo" * Windows Users: grep is included in cygwin (http://www.cygwin.com/)
Anonymous User - Jan. 8, 2003 11:50 pm: rwar
Debugging
Zope provides debugging information through a number of sources. It also allows you a couple avenues for getting information about Zope as it runs.
The Control Panel
The control panel provides a number of views that can help you debug Zope, especially in the area of performance. The Debugging Information link on the control panel provides two views, Debugging Info and Profiling.
Anonymous User - May 8, 2005 11:50 pm: Whee!
Debugging info provides information on the number of object references and the status of open requests. The object references list displays the name of the object and the number of references to that object in Zope. Understanding how reference counts help debugging is a lengthy subject, but in general you can spot memory leaks in your application if the number of references to certain objects increases without bound. The busier your site is, or the more content it holds, the more reference counts you will tend to have.
Profiling uses the standard Python profiler. This is turned on
by setting the PROFILE_PUBLISHER
environment variable before
executing Zope.
Anonymous User - Mar. 11, 2003 5:31 am: It doesn't work for me :( In ZOPE_HOME/start i set export PROFILE_PUBLISHER="/usr/local/zope/profile.out" exec /usr/local/bin/python $cwd/z2.py -D -u flo "$@" but i don't get any change in the profiling tab
Anonymous User - Mar. 17, 2003 4:22 pm: It's ok now. - I'm not sure what my mistake was.
When the profiler is running, the performance of your Zope system will suffer a lot. Profiling should only be used for short periods of time, or on a separate ZEO client so that your normal users to not experience this significant penalty.
Anonymous User - Jan. 28, 2002 4:01 pm - typo /users to not/users do not/
Profiling provides you with information about which methods in your Zope system are taking the most time to execute. It builds a profile, which lists the busiest methods on your system, sorted by increasing resource usage. For details on the meaning of the profiler's output, read the standard Python documentation.
Anonymous User - Nov. 3, 2004 7:22 am: testing
Product Refresh Settings
As of Zope 2.4 there is a Refresh view on all Control Panel Products. Refresh allows you to reload your product's modules as you change them, rather than having to restart Zope to see your changes. The Refresh view provides the same debugging functionality previously provided by Shane Hathaway's Refresh Product.
To turn on product refresh capabilities place a refresh.txt
file in your product's directory. Then visit the Refresh view
of your product in the management interface. Here you can
manually reload your product's modules with the Refresh this
product button. This allows you to immediately see the effect
of your changes, without restarting Zope. You can also turn on
automatic refreshing which causes Zope to frequently check for
changes to your modules and refresh your product when it detects
that your files have changed. Since automatic refresh causes
Zope to run more slowly, it is a good idea to only turn it on
for a few products at a time.
Anonymous User - Nov. 16, 2002 4:04 am: Does refresh.txt require any content, or is just the presence of the file enough to work the 'magic'?
Anonymous User - Nov. 16, 2002 4:04 am: And just how do you turn on (and off) automatic refreshing?
Anonymous User - Nov. 28, 2002 11:39 am: I just found the answer to your question here: http://www.zope.org/Members/dylanr/prod_tips It must be an empty file. Anyway, I agree that they should be more specific. About your second question, I also don't have any idea about how does it work.
Anonymous User - Nov. 30, 2002 1:36 pm: Doesn't work for me (Zope 2.5.1) using the Poll Product from the earlier in the book. Neither explicit refreshes of the product nor the refresh.txt mechanism appear to reload changed code. I need to restart zope before changes take effect.
Anonymous User - Jan. 28, 2003 2:59 am: I also have to restart Zope before changes take effect. (2.6.0)
to_be - May 8, 2003 7:00 am: An empty refresh.txt is sufficient, but if there is text in it, this will be displayed as 'Important information about refreshing this product' (Zope 2.6.1)
Debug Mode
Z_DEBUG_MODE=1
environment puts Zope into debug
mode. This mode reduces the performance of Zope a little bit.
Debug model has a number of wide ranging effects:
- Tracebacks are shown on the browser when errors are raised.
- External Methods and DTMLFile objects are checked to see if they have been modified every time they are called. If modified, they are reloaded.
- Zope will not fork into the background in debug mode, instead, it will remain attached to the terminal that started it and the main logging information will be redirected to that terminal.
Normally, debug mode is set using the -D
switch when starting
Zope, though you can set the environment variable directly if
you wish.
Anonymous User - Nov. 17, 2001 11:07 pm - In the source directory of your zope installation your start file should look something like this<br> #! /bin/sh<br> reldir=`dirname $0`<br> PYTHONHOME=`cd $reldir; pwd`<br> export PYTHONHOME<br> exec /usr/bin/python \<br> $PYTHONHOME/z2.py \<br> -D "$@"<br> <br>You will also notice that when running from the command line that the shell isn't returned to a command mode, this is normal.<br><br>
By using debug mode and product refresh together you will have little reason to restart Zope while developing.
The Python Debugger
Zope is integrated with the Python debugger (pdb). The Python debugger is pretty simple as command line debuggers go, and anyone familiar with other popular command line debuggers (like gdb) will feel right at home in pdb.
For an introduction to pdb see the standard pdb documentation.
There are a number of ways to debug a Zope process:
- You can shut down the Zope server and simulate a request on the command line.
- You can run a special ZEO client that debugs a running server.
- You can run Zope in debug model and enter the debugger through Zope's terminal session.
The first method is an easy way to debug Zope if you are not running ZEO. First, you must first shut down the Zope process. It is not possible to debug Zope in this way and run it at the same time. Starting up the debugger this way will by default start Zope in single threaded mode.
Anonymous User - Jan. 25, 2002 9:25 am - Typo: debug modeL
Anonymous User - July 6, 2002 7:08 am: Typo: first you must first
For most Zope developer's purposes, the debugger is needed to debug some sort of application level programming error. A common scenario is when developing a new product for Zope. Products extend Zope's functionality but they also present the same kind of debugging problems that are commonly found in any programming environment. It is useful to have an existing debugging infrastructure to help you jump immediately to your new object and debug it and play with it directly in pdb. The Zope debugger lets you do this.
In reality, the "Zope" part of the Zope debugger is actually just a handy way to start up Zope with some pre-configured break points and to tell the Python debugger where in Zope you want to start debugging.
Simulating HTTP Requests
Now for an example. Remember, for this example to work, you
must shut down Zope. Go to your Zope's lib/python
directory
and fire up Python and import Zope
and 'ZPublisher':
$ cd lib/python $ python Python 1.5.2 (#0, Apr 13 1999, 10:51:12) [MSC 32 bit (Intel)] on win32 Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam >>> import Zope, ZPublisher >>>
Here you have run the Python interpreter (which is where using the
debugger takes place) and imported two modules, Zope
and
ZPublisher
. If Python complains about an ImportError
and not
being able to find either module, then you are probably in the wrong
directory, or you have not compiled Zope properly. If you get
this message:
ZODB.POSException.StorageSystemError: Could not lock the database file. There must be another process that has opened the file.
This tells you that Zope is currently running. Shutdown Zope and try again.
The Zope
module is the main Zope application module. When you
import Zope
it sets up Zope. ZPublisher
is the Zope ORB. See
Chapter 2 for more information about ZPublisher
.
You can use the ZPublisher.Zope
function to simulate an HTTP
request. Pass the function a URL relative the your root Zope
object. Here is an example of how to simulate an HTTP request
from the debugger:
>>> ZPublisher.Zope('') Status: 200 OK X-Powered-By: Zope (www.zope.org), Python (www.python.org) Content-Length: 1238 Content-Type: text/html <HTML><HEAD><TITLE>Zope</TITLE> ... blah blah... </BODY></HTML> >>>
Anonymous User - Nov. 22, 2002 8:42 am: Can I use ZPublisher.Zope() as a link between an HTTP server and the ZPublisher machinery? I need to bypass the ZServer and make Apache pass a web request directly to ZPublisher. Any howtos/docs available? Thanks
If you look closely, you will see that the content returned is exactly what is returned when you call your root level object through HTTP, including all the HTTP headers.
Keep in mind that calling Zope this way does NOT involve
a web server. No ports are opened, the ZServer
code is not even
imported. In fact, this is just an interpreter front end to
the same application code the ZServer does call.
Interactive Debugging
Debugging involves publishing a request up to a point where you think it's failing, and then inspecting the state of your variables and objects. The easy part is the actual inspection, the hard part is getting your program to stop at the right point.
So, for the sake our example, let's say that you have a News
object which is defined in a Zope Product called ZopeNews
, and
is located in the lib/python/Products/ZopeNews
directory. The
class that defines the News
instance is also called News
,
and is defined in the News.py
module in your product.
Anonymous User - Mar. 9, 2003 2:00 pm: What exactly is a News object and how was it defined? Is it a class that was registered with the registrar object? <a href="http://www.zope.org/Documentation/Books/ZDG/current/Products.stx">http://www.zope.org/Documentation/ Boo ks/ZDG/current/Products.stx</a> Object is to ambiguous of a term.
Therefore, from Zope's perspective the fully qualified name of
your class is Products.ZopeNews.News.News
. All Zope objects
have this kind of fully qualified name. For example, the
ZCatalog
class can be found in
Products.ZCatalog.ZCatalog.ZCatalog
(The redundancy is because
the product, module, and class are all named ZCatalog
).
Now let's create an example method to debug. You want your news
object to have a postnews
method, that posts news:
class News(...): ... def postnews(self, news, author="Anonymous"): self.news = news def quote(self): return '%s said, "%s"' % (self.author, self.news)
You may notice that there's something wrong with the postnews
method. The method assigns news
to an instance variable, but
it does nothing with author
. If the quote
method is called,
it will raise an AttributeError
when it tries to look up the
name self.author
. Although this is a pretty obvious goof,
we'll use it to illustrate using the debugger to fix it.
Running the debugger is done in a very similar way to how you called Zope through the python interpreter before, except that you introduce one new argument to the call to 'Zope':
>>> ZPublisher.Zope('/News/postnews?new=blah', d=1) * Type "s<cr>c<cr>" to jump to beginning of real publishing process. * Then type c<cr> to jump to the beginning of the URL traversal algorithm. * Then type c<cr> to jump to published object call. > <string>(0)?() pdb>
Here, you call Zope from the interpreter, just like before,
but there are two differences. First, you call the postnews
method with an argument using the URL,
/News/postnews?new=blah
. Second, you provided a new
argument to the Zope call, d=1
. The d
argument, when
true, causes Zope to fire up in the Python debugger, pdb.
Notice how the Python prompt changed from >>>
to pdb>
.
This indicates that you are in the debugger.
When you first fire up the debugger, Zope gives you a helpful message that tells you how to get to your object. To understand this message, it's useful to know how you have set Zope up to be debugged. When Zope fires up in debugger mode, there are three breakpoints set for you automatically (if you don't know what a breakpoint is, you need to read the python debugger documentation.).
The first breakpoint stops the program at the point that
ZPublisher (the Zope ORB) tries to publish the application
module (in this case, the application module is Zope
). The
second breakpoint stops the program right before ZPublisher
tries to traverse down the provided URL path (in this case,
/News/postnews
). The third breakpoint will stop the program
right before ZPublisher calls the object it finds that matches
the URL path (in this case, the News
object).
So, the little blurb that comes up and tells you some keys to
press is telling you these things in a terse way. Hitting s
will step you into the debugger, and hitting c
will
continue the execution of the program until it hits a
breakpoint.
Note however that none of these breakpoints will stop the program at
postnews
. To stop the debugger right there, you need to tell the
debugger to set a new breakpoint. Why a new breakpoint? Because
Zope will stop you before it traverse your objects path, it will
stop you before it calls the object, but if you want to stop it
exactly at some point in your code, then you have to be explicit.
Sometimes the first three breakpoints are convienent, but often you
need to set your own special break point to get you exactly where
you want to go.
Setting a breakpoint is easy (and see the next section for an even easier method). For example:
pdb> import Products pdb> b Products.ZopeNews.News.News.postnews Breakpoint 5 at C:\Program Files\WebSite\lib\python\Products\ZopeNews\News.py:42 pdb>
First, you import Products
. Since your module is a Zope
product, it can be found in the Products
package. Next, you
set a new breakpoint with the break debugger command (pdb
allows you to use single letter commands, but you could have
also used the entire word break
). The breakpoint you set is
Products.ZopeNews.News.News.postnews
. After setting this
breakpoint, the debugger will respond that it found the method
in question in a certain file, on a certain line (in this
case, the fictitious line 42) and return you to the debugger.
Now, you want to get to your postnews
method so you can
start debugging it. But along the way, you must first
continue through the various breakpoints that Zope has set
for you. Although this may seem like a bit of a burden, it's
actually quite good to get a feel for how Zope works
internally by getting down the rhythm that Zope uses to
publish your object. In these next examples, my
comments will begin with '#". Obviously, you won't see these
comments when you are debugging. So let's debug:
pdb> s # 's'tep into the actual debugging > <string>(1)?() # this is pdb's response to being stepped into, ignore it pdb> c # now, let's 'c'ontinue onto the next breakpoint > C:\Program Files\WebSite\lib\python\ZPublisher\Publish.py(112)publish() -> def publish(request, module_name, after_list, debug=0, # pdb has stopped at the first breakpoint, which is the point where # ZPubisher tries to publish the application module. pdb> c # continuing onto the next breakpoint you get... > C:\Program Files\WebSite\lib\python\ZPublisher\Publish.py(101)call_object() -> def call_object(object, args, request):
Here, ZPublisher
(which is now publishing the application)
has found your object and is about to call it. Calling your
object consists of applying the arguments supplied by
ZPublisher
to the object. Here, you can see how
ZPublisher
is passing three arguments into this process.
The first argument is object
and is the actual object you
want to call. This can be verified by printing the object:
pdb> p object <News instance at 00AFE410>
Now you can inspect your object (with the print command) and
even play with it a bit. The next argument is args
. This
is a tuple of arguments that ZPublisher
will apply to your
object call. The final argument is request
. This is the
request object and will eventually be transformed in to the
DTML usable object REQUEST
. Now continue, your breakpoint is
next:
pdb> c > C:\Program Files\WebSite\lib\python\Products\ZopeNews\News.py(42)postnews() -> def postnews(self, N)
Now you are here, at your method. To be sure, tell the
debugger to show you where you are in the code with the l
command. Now you can examine variable and perform all the
debugging tasks that the Python debugger provides. From here,
with a little knowledge of the Python debugger, you should be
able to do any kind of debugging task that is needed.
Interactive Debugging Triggered From the Web
If you are running in debug mode you can set break points in your code and then jump straight to the debugger when Zope comes across your break points. Here's how to set a breakpoint:
import pdb pdb.set_trace()
Now start Zope in debug mode and point your web browser at a URL that causes Zope to execute the method that includes a breakpoint. When this code is executed, the Python debugger will come up in the terminal where you started Zope. Also note that from your web browser it looks like Zope is frozen. Really it's just waiting for you do your debugging.
strobl - Oct. 19, 2001 2:59 am - Frankly, I don't unterstand the explaination given in the last two paragraphs at all. Zope has to be started, somehow, in order to serve requests from the web - but how? Using z2.py? Please elaborate.
chrism - Jan. 15, 2002 11:04 pm - Start Zope normally (using "start" or "start.bat") passing in the -D switch to put it in "debug" mode, which really means "dont detach from the controlling terminal". When Zope encounters a pdb.set_trace statement, the debugger will just "show up" on the terminal from which Zope was started.
mreinsch - Feb. 13, 2005 5:52 am: Use 'runzope' You should start runzope from an emacs shell. The debugger in the emacs python-mode always jumps to the current position in the source code. This is also useful for non emacs users! Start emacs. Open a shell by 'alt-x' 'shell'. Type 'PATHTORUNZOPE/runzope' Sometimes emacs doesn't open the source code while debugging. Usually it helps to open the python source in an extra buffer before starting the debug session.
From the terminal you are inside the debugger as it is executing your request. Be aware that you are just debugging one thread in Zope, and other requests may be being served by other threads. If you go to the Debugging Info screen while in the debugger, you can see your debugging request and how long it has been open.
It is often more convenient to use this method to enter the
debugger than it is to call ZPublisher.Zope
as detailed in the
last section.
Post-Mortem Debugging
Often, you need to use the debugger to chase down obscure
problems in your code, but sometimes, the problem is obvious,
because an exception gets raised. For example, consider the
following method on your News
class:
def quote(self): return '%s said, "%s"' % (self.Author, self.news)
Here, you can see that the method tries to substitute
self.Author
in a string, but earlier we saw that this should
really be self.author
. If you tried to run this method from
the command line, an exception would be raised:
>>> ZPublisher.Zope('/News/quote') Traceback (most recent call last): File "<stdin>", line 1, in ? File "./News.py", line 4, in test test2() File "./News.py", line 3, in test2 return '%s said, "%s"' % (self.Author, self.news) NameError: Author >>>
Using Zope's normal debugging methods, you would typically need to start from the "beginning" and step your way down through the debugger to find this error (in this case, the error is pretty obvious, but more often than not errors can be pretty obscure!).
Post-mortem debugging allows you to jump directly to the
spot in your code that raised the exception, so you do not
need to go through the possibly tedious task of stepping your
way through a sea of Python code. In the case of our example,
you can just pass ZPublisher.Zope
call a pm
argument that
is set to 1:
>>> ZPublisher.Zope('/News/quote', pm=1) Traceback (most recent call last): File "<stdin>", line 1, in ? File "./News.py", line 4, in test test2() File "./News.py", line 3, in test2 return '%s said, "%s"' % (self.Author, self.news) NameError: Author (pdb)
Here, you can see that instead of taking you back to a python prompt, the post mortem debugging flag has caused you to go right into the debugging, exactly at the point in your code where the exception is raised. This can be verified with the debugger's (l)ist command. Post mortem debugging offers you a handy way to jump right to the section of your code that is failing in some obvious way by raising an exception.
Debugging With ZEO
ZEO presents some interesting debugging abilities. ZEO lets you debug one ZEO client when other clients continue to server requests for your site. In the above examples, you have to shut down Zope to run in the debugger, but with ZEO, you can debug a production site while other clients continue to serve requests. Using ZEO is beyond the scope of this chapter. However, once you have ZEO running, you can debug a client process exactly as you debug a single-process Zope.
Anonymous User - Jan. 29, 2004 6:25 am: I there a way to log which objects where written to the ZODB on commit? I want to know the objects (oid would be enough) and the size of the pickle.
Unit Testing
Unit testing allows you to automatically test your classes to make sure they are working correctly. By using unit tests you can make sure as you develop and change your classes that you are not breaking them. Zope comes with Pyunit. You can find out more information on Pyunit at the Pyunit home page. Pyunit is also part of the Python standard library as of Python 2.1.
to_be - May 8, 2003 7:21 am: Some newbie questions: 1. Are the tests run automatically when a product is recognised/refreshed? 2. If not, is there a possibility to enforce this? 3. How to run the tests manually (import problems; PYTHONPATH)?
What Are Unit Tests
A "unit" may be defined as a piece of code with a single intended purpose. A "unit test" is defined as a piece of code which exists to codify the intended behavior of a unit and to compare its intended behavior against its actual behavior.
Unit tests are a way for developers and quality assurance engineers to quickly ascertain whether independent units of code are working as expected. Unit tests are generally written at the same time as the code they are intended to test. A unit testing framework allows a collection of unit tests to be run without human intervention, producing a minimum of output if all the tests in the collection are successful.
It's a good idea to have a sense of the limits of unit testing. From the
Extreme Programming Enthusiast website here is a list of things that unit tests are not :- Manually operated.
- Automated screen-driver tests that simulate user input (these are "functional tests").
- Interactive. They run "no questions asked."
- Coupled. They run without dependencies except those native to the thing being tested.
- Complicated. Unit test code is typically straightforward procedural code that simulates an event.
Writing Unit Tests
Here are the times when you should write unit tests:
- When you write new code
- When you change and enhance existing code
- When you fix bugs
It's much better to write tests when you're working on code than to wait until you're all done and then write tests.
You should write tests that exercise discrete "units" of functionality. In other words, write simple, specific tests that test one capability. A good place to start is with interfaces and classes. Classes and especially interfaces already define units of work which you may wish to test.
Since you can't possibly write tests for every single capability and special case, you should focus on testing the riskiest parts of your code. The riskiest parts are those that would be the most disastrous if they failed. You may also want to test particularly tricky or frequently changed things.
Here's an example test script that tests the News
class defined
earlier in this chapter:
import unittest import News class NewsTest(unittest.TestCase): def testPost(self): n=News() s='example news' n.postnews(s) assert n.news==s def testQuote(self): n=News() s='example news' n.postnews(s) assert n.quote()=='Anonymous said: "%s"' % s a='Author' n.postnews(s, a) assert n.quote()=='%s said: "%s"' % (a, s) def test_suite(): return unittest.makeSuite(NewsTest, 'news test') def main(): unittest.TextTestRunner().run(test_suite()) if __name__=="__main__": main()
You should save tests inside a tests
sub-directory in your
product's directory. Test scripts file names should start with
test, for example testNews.py
. You may accumulate many test
scripts in your product's tests
directory. You can run test
your product by running the test scripts.
We cannot cover all there is to say about unit testing here. Take a look at the Pyunit documentation for more background on unit testing.
Zope Test Fixtures
One issue that you'll run into when unit testing is that you may need to set up a Zope environment in order to test your products. You can solve this problem in two ways. First, you can structure your product so that much of it can be tested without Zope (as you did in the last section). Second, you can create a test fixture that sets up a Zope environment for testing.
To create a test fixture for Zope you'll need to:
- Add Zope's
lib/python
directory to the Python path. - Import
Zope
and any other needed Zope modules and packages. - Get a Zope application object.
- Do your test using the application object.
- Clean up the test by aborting or committing the transaction and closing the Zope database connection.
Here's an example Zope test fixture that demonstrates how to do each of these steps:
import os, os.path, sys, string try: import unittest except ImportError: fix_path() import unittest class MyTest(unittest.TestCase): def setUp(self): # Get the Zope application object and store it in an # instance variable for use by test methods import Zope self.app=Zope.app() def tearDown(self): # Abort the transaction and shut down the Zope database # connection. get_transaction().abort() self.app._p_jar.close() # At this point your test methods can perform tests using # self.app which refers to the Zope application object. ... def fix_path(): # Add Zope's lib/python directory to the Python path file=os.path.join(os.getcwd(), sys.argv[0]) dir=os.path.join('lib', 'python') i=string.find(file, dir) sys.path.insert(0, file[:i+len(dir)]) def test_suite(): return unittest.makeSuite(MyTest, 'my test') def main(): unittest.TextTestRunner().run(test_suite()) if __name__=="__main__": fix_path() main()
This example shows a fairly complete Zope test fixture. If your Zope tests only needs to import Zope modules and packages you can skip getting a Zope application object and closing the database transaction.
Some times you may run into trouble if your test assuming that
there is a current Zope request. There are two ways to deal with
this. One is to use the makerequest
utility module to create a
fake request. For example:
class MyTest(unittest.TestCase): ... def setup(self): import Zope from Testing import makerequest self.app=makerequest.makerequest(Zope.app())
Anonymous User - Nov. 9, 2002 3:03 pm: /if your test assuming/if your test assumes/
to_be - May 8, 2003 7:13 am: this isn't very understandable for someone who tries to add tests to his product the first time. How is this 'app' used then? Is it possible to just create a "REQUEST" object which can then be passed as an argument?
This will create a Zope application object that is wrapped in a
request. This will enable code that expects to acquire a REQUEST
attribute work correctly.
Another solution to testing methods that expect a request is to
use the ZPublisher.Zope
function described earlier. Using this
approach you can simulate HTTP requests in your unit tests. For
example:
import ZPublisher class MyTest(unittest.TestCase): ... def testWebRequest(self): ZPublisher.Zope('/a/url/representing/a/method?with=a&couple=arguments', u='username:password', s=1, e={'some':'environment', 'variable':'settings'})
If the s
argument is passed to ZPublisher.Zope
then no output
will be sent to sys.stdout
. If you want to capture the output of
the publishing request and compare it to an expected value you'll
need to do something like this:
f=StringIO() temp=sys.stdout sys.stdout=f ZPublisher.Zope('/myobject/mymethod') sys.stdout=temp assert f.getvalue() == expected_output
Here's a final note on unit testing with a Zope test fixture: you may find Zope helpful. ZEO allows you to test an application while it continues to serve other users. It also speeds Zope start up time which can be a big relief if you start and stop Zope frequently while testing.
Despite all the attention we've paid to Zope testing fixtures, you should probably concentrate on unit tests that don't require a Zope test fixture. If you can't test much without Zope there is a good chance that your product would benefit from some refactoring to make it simpler and less dependent on the Zope framework.
Logging
Zope provides a framework for logging information to Zope's application log. You can configure Zope to write the application log to a file, syslog, or other back-end.
Anonymous User - Nov. 16, 2002 4:19 am: Where does logging output go by default? Is that platform-dependent? What's the default logging level?
The logging API defined in the
zLOG
module. This module provides
the
LOG
function which takes the following required arguments:
- subsystem
- The subsystem generating the message (e.g. "ZODB")
- severity
- The "severity" of the event. This may be an integer or a floating point number. Logging back ends may consider the int() of this value to be significant. For example, a back-end may consider any severity whose integer value is WARNING to be a warning.
- summary
- A short summary of the event
LOG
function
are optional:
- detail
- A detailed description
- error
- A three-element tuple consisting of an error type, value, and traceback. If provided, then a summary of the error is added to the detail.
- reraise
- If provided with a true value, then the error given by error is reraised.
You can use the LOG
function to send warning and errors to the
Zope application log.
Here's an example of how to use the LOG
function to write
debugging messages:
from zLOG import LOG, DEBUG LOG('my app', DEBUG, 'a debugging message')
charlesz - Dec. 11, 2002 7:53 am: I get 'import of "LOG" from "zLOG" is unauthorized'. No doubt I'll work out why - but someone else might not.....
You can use LOG
in much the same way as you would use print
statements to log debugging information while Zope is running. You
should remember that Zope can be configured to ignore log messages
below certain levels of severity. If you are not seeing your logging
messages, make sure that Zope is configured to write them to the
application log.
In general the debugger is a much more powerful way to locate problems than using the logger. However, for simple debugging tasks and for issuing warnings the logger works just fine.
Other Testing and Debugging Facilities
There is a few other testing and debugging techniques and tools not commonly used to test Zope. In this section we'll mention several of them.
Anonymous User - Oct. 16, 2001 6:49 pm - The Medusa monitor client is also a good debugging tool. It permits querying the ZODB from an interactive commandline interface, and would be familiar to many Python users. The only downside is that because persisted objects are based on ExtensionClass, it is not possible to query an object for its functions and attributes in the style of "dir(thisobject)". -- Chui Tey
Anonymous User - Nov. 1, 2001 8:39 am - Actually dir(object) works fine. It just breaks the *first* time you try to use it on a persistent object, where it returns an empty list. The second and subsequent times, it will show you the methods and attributes. Of course it won't show you acquired methods and attributes, but that's what aq_parent and such are for. -- chrism
Anonymous User - Mar. 6, 2003 5:06 pm: Perhaps it's something I'm doing, but the downside of Medusa monitor debugging on my setup is that all errors fail silently, rather than raising a traceback. So if you try to look at attribute "a" of object "o", and it doesn't exist, you get no response, rather than a TB/exception. Is this just me?
Debug Logging
Zope provides an analysis tool for debugging log output. This output allows may give you hints as to where your application may be performing poorly, or not responding at all. For example, since writing Zope products lets your write unrestricted Python code, it's very possibly to get yourself in a situation where you "hang" a Zope request, possibly by getting into a infinite loop.
To try and detect at which point your application hangs, use the
requestprofiler.py script in the utilities directory of your
Zope installation. To use this script, you must run Zope with
the -M
command line option. This will turn on "detailed debug
logging" that is necessary for the requestprofiler.py script
to run. The requestprofiler.py script has quite a few options
which you can learn about with the --help
switch.
chrism - Dec. 10, 2001 11:10 am - Note that the requestprofiler.py script is really just an analysis script for the data collected by in the the detailed debug log. The description of the relationship above is a little muddled.
In general debug log analysis should be a last resort. Use it when Zope is hanging and normal debugging and profiling is not helping you solve your problem.
HTTP Benchmarking
HTTP load testing is notoriously inaccurate. However, it is
useful to have a sense of how many requests your server can
support. Zope does not come with any HTTP load testing tools,
but there are many available. Apache's ab
program is a widely
used free tool that can load your server with HTTP requests.
chrism - Dec. 10, 2001 11:12 am - HTTP load testing itself is not notoriously inaccurate. It's probably better to say that it's difficult to do "right".
Summary
Zope provides a number of different debugging and testing facilities. The debugger allows you to interactively test your applications. Unit tests allow help you make sure that your application is develops correctly. The logger allows you to do simple debugging and issue warnings.
Anonymous User - May 28, 2002 1:57 am: Typo: "Unit tests allow help you ..." should be "Unit tests help you to ..."
Anonymous User - Dec. 28, 2003 4:57 am: This doc shows how to check security issues. http://www.zopelabs.com/cookbook/1046449715
To help maintain your sanity you should keeping your Zope products as simple as possible, use interfaces to describe functionality, and test your components outside as well as inside Zope.
Anonymous User - Apr. 8, 2002 9:41 pm: I would nice to have some detail about how to debug security problems. Trying to work out why something is not authorized seems to be the hardest thing I do.
Anonymous User - July 2, 2002 11:00 am: I agree with this comment. I am struggling with several permissions problems right now and cannot figure out where to even start looking for clues as to what might be happening. The callstack traces just don't yield enough information.
Anonymous User - Aug. 22, 2005 8:02 am: Every time I read this stupid documentation I end up more annoyed and frustrated, and with my questions still unanswered.