[Zope-dev] Unit testing an application's user interface

2000-06-29 Thread Andrew Wilcox

Hello everybody, I've made some progress on unit testing my application's
user interface.  I'll share what I've got so far.


For background, there was discussion on unit testing last February on
zope-dev: 

   http://lists.zope.org/pipermail/zope-dev/2000-February/003489.html
   http://lists.zope.org/pipermail/zope-dev/2000-February/003514.html
   http://lists.zope.org/pipermail/zope-dev/2000-February/003501.html

An introduction to why do unit testing can be found at:

   http://www.extremeprogramming.org/rules/unittests.html


First, I do start by splitting out business classes and other functionality
that isn't dependent on Zope, and test these separately in isolation.  This
gives me a solid foundation of working and tested code to build upon.

But I found that I have enough complexity in my user interface that I
really need to test that as well.

I experimented for a time with checking the HTML output of web requests.
The framework would remotely submit http requests, walk through the web
site, exercise various functionality, submit form requests, and check if
the resultant HTML was correct.  Unfortunately, as you might expect, the
tests were quite fragile.  Any change in the website, no matter how small,
would produce different HTML and break all the tests.  I found the HTML
testing of some limited usefulness while refactoring, but I'd have to throw
all the tests away as soon as I added any functionality.

And checking the HTML output isn't good unit testing -- not small, focused
tests of particular functionality.  More thought was needed.

One of the points unit testers make is to focus the time you put into
writing unit tests to target where bugs are most likely.  In my situation,
I found I wasn't being bit by bugs in presentation, in the rendering of
HTML from information in the ZODB.  Where I was getting bugs slipping past
me was in the more complex processing of when my form processing would
update ZODB objects.

With this realization, I came up with an approach that I have found very
useful.  Run a form submit (or other data changing web request) through
Zope, and then check if objects in the ZODB have been updated as you'd expect.

This is nice.  You have a small piece of data going into the test (the form
submit fields), and you can easily check particular attributes of affected
objects in the ZODB.  This is a good unit test: small, predictable, and you
can code the test before implementing the functionality.

It's a good fit for the pattern of bugs that I have in my own code.  I
check the form processing as well as the underlying updating of objects,
because my code tends to have bugs in those areas.  I'm not checking how
the results get rendered (the HTML that comes back), because I usually
don't have bugs in that area when the underlying code is correct and is
getting its own unit testing.  I check if objects in the ZODB are updated
correctly.

As an example, suppose I wanted to unit test the Zope user interface for
editing DTML Documents.  The familiar "Edit" tab has an input box for the
document title and a text box for entering the content.  The form submit is
"manage_edit".  If called remotely, the URL for editing a document named
"TestDocument" would look like
http://www.myzope.com/somepath/TestDocument/manage_edit with the "title"
and "data" fields submitted as form elements.

Here's an external method that will do this test, here checking if the
title is updated properly.

def testChangingDocument(self):
try:
document = self.TestDocument
document.manage_edit(data="Hi, I'm a DTML Document",
 title="hello",
 SUBMIT='Change')
assert document.title == "hello"
finally:
get_transaction().abort()
return "Test was successful"

I call "manage_edit" with the form data, and then check if the ZODB has
been updated as I expect. The transaction abort in the "finally:" clause
ensures that in all cases (whether the test is successful, the assertion
fails, or an error occurs) changes to the ZODB will be backed out of and
the database left unchanged.  This lets me run lots of tests without them
bumping into each other.

My realworld tests tend to be more complicated, first setting up objects
and then checking multiple things, but this is the essential framework.

So far so good.

There are some problems with this method of implementation though.  What I
really want to be doing is setting up the web request with the form fields,
and then running my request through the Zope machinery.  In my example
above, I cheated and looked at the source code for manage_edit() to see how
it wanted to be called.  manage_edit() happens to declare the form fields
it's looking for as arguments to the method, and Zope obligingly pulls
those values out of the request and supplies them.  But manage_edit() could
have been written to pull the values out of the REQUEST.form, and I would
have to call it differently.

Getting th

Re: [Zope-dev] Re: Zope security alert and hotfix product...

2000-08-14 Thread Andrew Wilcox

>Now - should methods of mutable types be off-limits in the 
>future?  [...]  I don't think it would 
>be acceptable for 'append' to be off-limits in this case, so 
>the alternative is that the security machinery would somehow 
>have to be able to distinguish mutables created by the user 
>from those obtained from the secure environment. That would 
>probably mean that API code would have to make some sort of 
>security assertions about mutables they returned.

I have another alternative --

Unless I really intend to return an unprotected mutable across subsystems,
most often it's a mistake.  Even if security isn't an issue, it leads to
wonderfully obscure bugs if a customer of my class blithely modifies my
return value.  (I returned it to you, after all, you thought it was yours
now!)  If I do want to return a dictionary for you to *read*, I could just
as easily code my intention and return a read-only class that implements
__getitem__. 

Since on occasion I might deliberately want to return an unprotected
mutable value for a good reason, what would be helpful is if the security
system forbid me from returning unprotected mutable values unless I
explicitly say I intend to do so.  I'd encourage the check to recursively
descend through tuples and, if we create a standard read-only dictionary
for everyone to use, through read-only dictionaries.

Note such a check would be just as helpful for products written using
Python Methods, or DTML, as it would be for products written in plain Python.

Then the security system doesn't need to keep track of where unprotected
mutables came from.  Once I've said explicitly that I intend to return an
unprotected mutable, I can take care to return a copy that I don't use
after I've passed it on to you.

Andrew



___
Zope-Dev maillist  -  [EMAIL PROTECTED]
http://lists.zope.org/mailman/listinfo/zope-dev
**  No cross posts or HTML encoding!  **
(Related lists - 
 http://lists.zope.org/mailman/listinfo/zope-announce
 http://lists.zope.org/mailman/listinfo/zope )




[Zope-dev] questions on ZPatterns plugin code

2000-09-25 Thread Andrew Wilcox

I've started browsing the ZPatterns code, and here are the questions that
came up for me while reading PlugsIns.py... Is zope-dev a good place for
this?  Would somewhere in the ZPatterns wiki be better?

The PlugInBase.manage_afterAdd() and manage_beforeDelete() methods
dynamically checks if their container has the _addPlugIn() and
_removePlugIn() methods before calling them.  Could this code be
simplified by requiring containers support an add/remove PlugIn
interface, with a default do-nothing implementation?

The PlugInContainer._addPlugIn() and _removePlugIn() will register a
plugin with multiple groups.  However _findGroupFor() appears to
assume that a plugin is associated with one plugin group.  What would
be an example of a plugin that registered itself with multiple plugin
groups?

The PlugInContainer directly accesses the __plugin_kind__ attribute of
a plugin.  Could we have a plugin.kind() method instead?

PlugIns.py patches the Zope ProductContext class to add the
_registerPlugInClass method.  Could we instead update the Zope
implementation to provide a _registerPlugInClass method, that called
the ZPatterns product if it was installed?



___
Zope-Dev maillist  -  [EMAIL PROTECTED]
http://lists.zope.org/mailman/listinfo/zope-dev
**  No cross posts or HTML encoding!  **
(Related lists - 
 http://lists.zope.org/mailman/listinfo/zope-announce
 http://lists.zope.org/mailman/listinfo/zope )




Re: [Zope-dev] Calling DTML methods from Python

2000-05-29 Thread Andrew Wilcox

>Is it just me or is there a lot of confusion between the terms
>namespace, self, client, and the REQUEST object (which, unlike it's name
>implies, seems to contain a lot more than stuff relating to the HTTP
>request, like the RESPONSE object, for example ;-)
>
>Perhaps this could be shaken down and then, a first for the Zope
>community I believe ;-), _documented_ somewhere!!!

Well, as far as I've been able to figure out.  But everybody correct me
where I get it wrong:

When a DTML Document is called from the web, it gets itself as the client
argument and a namespace as the second argument.  Note that it gets itself
twice: first as the standard Python object oriented "self" argument and
then as the client argument.

I seem to recall there's some magic in there where the the object is
wrapped in an acquisition class so that it can do a self.REQUEST, but I'm
not sure.

Any DTML methods called by the first object get None passed in as the
client argument.  Importantly, DTML methods do not lookup variables in
themselves (i.e. from the Python "self"), but only from the namespace.  The
original object has been added to the namespace at this point so these
called methods are able to access things.

An example would be if you had a DTML method called "doit" sitting in a
folder next to "standard_html_header".  Because doit acquires from its
container, you could say "doit.standard_html_header" from Python because
standard_html_header is a property of the containing folder.  But using
"" from inside doit only works if
standard_html_header is available in the namespace.

External methods add more fun :-).  As noted in the documentation, the
enclosing folder will be passed in automatically if you call the first
argument "self" (and you pass in yourself one fewer argument than the
function calls for).  Note this is NOT the standard python object oriented
"self" argument, which would be the external method sitting in the ZODB if
anything, but the enclosing folder.  You do have to call it "self" or you
don't get anything.  The "self" argument is able to acquire REQUEST so you
can say "self.REQUEST", but you don't get a namespace automatically.




___
Zope-Dev maillist  -  [EMAIL PROTECTED]
http://lists.zope.org/mailman/listinfo/zope-dev
**  No cross posts or HTML encoding!  **
(Related lists - 
 http://lists.zope.org/mailman/listinfo/zope-announce
 http://lists.zope.org/mailman/listinfo/zope )