After reading of Steve K's efforts on this list, and reading "Extreme Programming Java Cookbook", I would like to propose a Cocoon pipeline unit testing framework, based arount HTTPUnit, XMLUnit and the CocoonBean.
HTTPUnit
-------
HTTPUnit provides a user agent for engaging in 'conversations' with a web server, and allows you to test the responses at any stage within this conversation. E.g. is there a form called 'cart' on the HTML page; is an error message 'Email address required' displayed within the page.
HTTPUnit, to my mind, provides some valuable functionality, but given the nature of the web, it is primarily aimed at functional testing rather than unit testing. It tests whether the end result of a process is correct, but not what happened to get there.
XMLUnit
-------
XMLUnit extends JUnit to give XML aware assertions, which can compare such things as "<node/>" and "<node></node>" and correctly identify these as being the same.
CocoonUnit
----------
CocoonUnit will be based upon, and if possible extend, HTTPUnit. A 'web conversation' will take place in the same way as it would with HTTPUnit. The only difference is that you would be able to get at the XML from each stage of a pipeline with response.getPipelineXML(pipelinePos); . Then, using XMLUnit, you can test to ensure that the XML at any relevant stage in the pipeline is as you would want.
Example
-------
public class MyXMLTestCase extends XMLTestCase {
public MyXMLTestCase(String name) {
super(name);
}
public void testForEquality() throws Exception {
CocoonConversation cocoonConversation = new CocoonConversation();
CocoonRequest request = new GetMethodCocoonRequest("/samples/hello-world/hello.html");
CocoonResponse response = webConversation.getResponse(request);
String generatorXML = response.getPipelineXML(0);
String transformedXML = response.getPipelineXML(1);assertXpathExists("//para[contains(.,'Cocoon')]", generatorXML);
assertXpathNotExists("//para[contains(.,'Active Server Pages')]", generatorXML);
// further tests on the transformedXML } }
This class would then be executed as any normal JUnit test case, e.g. direct from your IDE, or as a part of your build process via an Ant build script.
Implementation
--------------
CocoonUnit would use the CocoonBean to pass requests to Cocoon. The bean would prepare an Environment (maybe even an HTTPEnvironment), and would place an empty ArrayList into the object model with a key of UNIT_TEST_XML.
Then, in AbstractProcessingPipeline, there are two process() methods, each of which call connectPipeline(). Immediately before these calls to connectPipeline, a test would be done for the UNIT_TEST_XML object in the object model. If it is present, the transformer ArrayLists (all four of them), would be modified. At the beginning, and then between each transformer, a DOMGatheringTransformer will be inserted. This DOMGatheringTransformer simply builds a DOM object and adds it to the UNIT_TEST_XML ArrayList stored in the object model. and then forwards its SAX events unmodified to the next stage in the pipeline.
Once a resource has been generated, the CocoonBean can extract this UNIT_TEST_XML ArrayList and pass it back to the CocoonResponse, so that its XML DOMs can be available via response.getPipelineXML();
This so far seems relatively straightforward. The most complicated part to implement, as I see it, will be extending HTTPUnit to interface with Cocoon instead of its own methods for getting web resources. I have only had a cursory look at HTTPUnit, but it does seem to have a reasonably active mailing list that I hope could help me. Should this part prove to be too complex, I would write my own (limited) version of HTTPUnit. It seems that you can either use HTTP to get pages, or use its own 'simulated servlet container' to run servlets without a real container. I could potentially extend or amend either of these to use the CocoonBean.
The CocoonBean may need some extending to enable it to create one Cocoon instance at the beginning of the testing process, and to then share that instance across all subsequent tests (whether simultaneous or sequential). Otherwise, each test would need to initialise and dispose the CocoonBeanm which would be a waste of resources.
Extension Possibilities
-----------------------
Once the above works, it would not be hard to allow this method to test specific sections of a pipeline, by supplying the input, as well as testing the output:
CocoonConversation cocoonConversation = new CocoonConversation();
CocoonRequest request = new GetMethodCocoonRequest("/samples/hello-world/hello.html");
request.setPipelineXML(1, "<xml>.....</xml>");
CocoonResponse response = webConversation.getResponse(request);
String transformedXML = response.getPipelineXML(2);
assertXpathExists("//para[contains(.,'Cocoon')]", transformedXML);
The above sets the input to the second stage of the pipeline, which the DOMGatheringTransformer (now a UnitTestTransformer) uses instead of the incoming SAX stream.
Conclusion
----------
Unit testing helps to provide reliable code. Unit testing in a Web environment is not always straightforward as your 'units' aren't always clearly defined. With Cocoon, however, stages in a pipeline can be considered to be 'units', and therefore could be tested independently of each other. Adding a CocoonUnit to the Cocoon codebase would further extend Cocoon's ability to be used in substantial web environments where automated testing is crucial to success.
What do you all think?
Upayavira
