[Zope-dev] Unit testing an application's user interface
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...
>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
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
>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 )