1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
|
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.
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*.
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.
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.
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":http://www.python.org/doc/current/lib/profile.html.
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.
Debug Mode
Setting the '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:
o Tracebacks are shown on the browser when errors are raised.
o External Methods and DTMLFile objects are checked to see if
they have been modified every time they are called. If
modified, they are reloaded.
o 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.
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":http://www.python.org/doc/current/lib/module-pdb.html.
There are a number of ways to debug a
Zope process:
o You can shut down the Zope server and simulate a request on the
command line.
o You can run a special ZEO client that debugs a running server.
o 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.
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>
>>>
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.
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":http://www.python.org/doc/current/lib/module-pdb.html.).
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.
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.
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":http://pyunit.sourceforge.net/. Pyunit is also part of the
Python "standard
library":http://www.python.org/doc/lib/module-unittest.html as of
Python 2.1.
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":http://c2.com/cgi/wiki?UnitTestsDefined
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":http://pyunit.sourceforge.net/pyunit.html 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:
1. Add Zope's 'lib/python' directory to the Python path.
2. Import 'Zope' and any other needed Zope modules and packages.
3. Get a Zope application object.
4. Do your test using the application object.
5. 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())
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.
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
These arguments to the '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')
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.
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.
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.
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.
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.
|