Author: craigmcc Date: Sat Oct 29 17:45:51 2005 New Revision: 329501 URL: http://svn.apache.org/viewcvs?rev=329501&view=rev Log: Flesh out the description of the Shale Test Framework feature, and use the <source> markup element that Maven supports to create boxes around the code examples.
Modified: struts/shale/trunk/xdocs/features.xml Modified: struts/shale/trunk/xdocs/features.xml URL: http://svn.apache.org/viewcvs/struts/shale/trunk/xdocs/features.xml?rev=329501&r1=329500&r2=329501&view=diff ============================================================================== --- struts/shale/trunk/xdocs/features.xml (original) +++ struts/shale/trunk/xdocs/features.xml Sat Oct 29 17:45:51 2005 @@ -342,7 +342,7 @@ <li>Define your dialogs in an XML document, conventionally named <code>/WEB-INF/dialog-config.xml</code>, that conforms to the required DTD, which defines all the state transitions: - <blockquote><pre> +<source> <!DOCTYPE dialogs PUBLIC "-//Apache Software Foundation//DTD Shale Dialog Configuration 1.0//EN" "http://struts.apache.org/dtds/shale-dialog-config_1_0.dtd"> @@ -360,18 +360,18 @@ ... </dialogs> -</pre></blockquote></li> +</source></li> <li>If you have more than one dialog configuration file, or you have defined your only dialog configuration file as a web application resource with a name different than the one described above, use a context initiaization parameter to define a comma-delimited list of context-relative paths to configuration resources to be loaded: - <blockquote><pre> +<source> <context-param> <param-name>org.apache.shale.dialog.CONFIGURATION</param-name> <param-value>/WEB-INF/foo.xml,/WEB-INF/bar.xml</param-value> </context-param> -</pre></blockquote></li> +</source></li> <li>In addition to the dialog configuration resources defined by this context initialization parameter, a resource named <code>/WEB-INF/dialog-config.xml</code> will be automatically @@ -463,7 +463,7 @@ </ul> <p>Here's an example:</p> -<pre> +<source> <%@ taglib uri="http://struts.apache.org/shale/core" prefix="s" %> ... <h:form onsubmit="validateForm(this)"> @@ -493,7 +493,7 @@ <s:validatorScript functionName="validateForm"/> </form> ... -</pre> +</source> <p>In the preceding example, we've attached three Commons validators to a single JSF input component. To pass validation, the field must have a value that starts with a number between 4 and 6 inclusive and that value must be a valid @@ -587,49 +587,49 @@ environment entry to define whether your application is running in debug mode or not, declared in <code>web.xml</code> like this:</p> - <blockquote><pre> +<source> <env-entry> <description>Flag indicating whether we run in debug mode.</description> <env-entry-name>debugMode</env-entry-name> <env-entry-value>false</env-entry-value> <env-entry-type>java.lang.Boolean<env-entry-type> </env-entry> -</pre></blockquote> +</source> <p>Now, assume you have a status message that you only want to have displayed when debug mode is enabled. You can bind the <code>rendered</code> property of the component to this environment entry value:</p> - <blockquote><pre> +<source> <h:outputText ... rendered="#{jndi.debugMode}" .../> -</pre></blockquote> +</source> <p><strong>(2) Programmatic Resource Access</strong></p> <p>Assume you have a data source reference (discussed in the introduction) defined in your <code>web.xml</code> like this:</p> - <blockquote><pre> +<source> <resource-ref> <description>Customer Database</description> <res-ref-name>jdbc/CustomerDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> -</pre></blockquote> +</source> <p>You can acquire a <code>java.sql.Connection</code> from this data source with code like the following (note that the convenience base class <code>BaseViewController</code> contains a <code>getBean()</code> method that substantially reduces the amount of code needed):</p> - <blockquote><pre> +<source> FacesContext context = FacesContext.getCurrentInstance(); ValueBinding vb = context.getApplication().createValueBinding("#{jndi['jdbc/CustomerDB'].connection}"); Connection conn = (Connection) vb.getValue(context); -</pre></blockquote> +</source> <p>This works by first retrieving the JNDI-configured data source instance, and then calling its <code>getConnection()</code> method. @@ -674,9 +674,9 @@ for inheritance relationships. Clay's approach is unique. The granularity is targeted at the declaration of JSF components versus composition of JSP fragments. - <blockquote><pre> +<source> <clay:clay id="address" jsfid="addressPanel"/> -</pre></blockquote></li> +</source></li> <li>The subtree can be formed using a Tapestry like HTML layout. HTML elements are bound to corresponding JSF components using a <code>jsfid</code> attribute. This attribute binds the HTML mock-up @@ -688,9 +688,9 @@ standard <code>verbatim</code> (<code>ouputText</code>) component. This combines the first option's component definitions with the flexibility of using HTML for layout.<br/> - <blockquote><pre> +<source> <clay:clay id="address" jsfid="address.html"/> -</pre></blockquote></li> +</source></li> <li>The subtree can also be defined at runtime. The Clay component provides a postback validation method event, <code>shapeValidator</code>, that can be bound to the associated @@ -700,9 +700,9 @@ graph of objects used by the Clay component to build the subcomponent tree. The object's graph is representative of the first two declarative approaches. - <blockquote><pre> +<source> <clay:clay id="address" jsfid="RUNTIME" shapeValidator="#{fullAddress.createSubtree}"/> -</pre></blockquote></li> +</source></li> </ol> </subsection> @@ -723,19 +723,21 @@ "top-level" elements can be the root of a clay subtree. The <code>componentType</code> attribute defines the association to the face's resource. The <code>jsfid</code> attribute is a - logical unique identifier. - <blockquote><pre> + logical unique identifier.</p> + +<source> <component jsfid="outputText" componentType="javax.faces.HtmlOutputText"/> <component jsfid="validateLongRange" componentType="javax.faces.LongRange"/> -</pre></blockquote></p> +</source> <p>A <code>component</code> can extend another component, thereby inheriting <code>attributes</code> and contained <code>element</code> nodes from the parent component. This is accomplished by using the <code>extends</code> attribute. The value of the <code>extends</code> attribute should be the <code>jsfid</code> of - the parent <code>component</code> definition. - <blockquote><pre> + the parent <code>component</code> definition.</p> + +<source> <component jsfid="baseLabel" extends="outputLabel" allowBody="false"> <attributes> <set name="style" value="color:blue"/> @@ -747,14 +749,15 @@ <set name="for" value="city"/> </attributes> </component> -</pre></blockquote></p> +</source> <p>A <code>component</code> node has an attribute container. This is a generic container to hold all <code>attributes</code> that would be represented by associated JSF/JSP Tags. Attributes are added or overridden in inheritance using the attribute <code>name</code> as the - unique identifier. - <blockquote><pre> + unique identifier.</p> + +<source> <component jsfid="addressPanel" extends="panelGrid"> <attributes><br/> <set name="columns" value="2" /> @@ -768,7 +771,7 @@ </attributes> <element renderId="3" jsfid="street1Message"/> </component> -</pre></blockquote></p> +</source> <p>The <code>element</code> node is the composition glue. Components are uniquely defined by a <code>renderId</code> attribute. This @@ -777,8 +780,9 @@ acts as the "method signature" for the <code>element</code> when resolving inheritance. This means components can extend other components by overriding or extending elements based on the - <code>renderId</code> of the first level of child components. - <blockquote><pre> + <code>renderId</code> of the first level of child components.</p> + +<source> <component jsfid="ssnColumn" extends="column" id="ssn"> <element renderId="1" jsfid="outputText" facetName="header"> <attributes> @@ -807,7 +811,7 @@ <element renderId="0" jsfid="ssnColumn"/> <element renderId="3" jsfid="birthDateColumn"/> </component> -</pre></blockquote></p> +</source> <p>Clay also allows reuse of view fragments bound to different logical managed bean names. Managed bean names in Shale will most likely @@ -818,8 +822,9 @@ "managed-bean-name" is replaced with the value of the Clay <code>managedBeanName</code> property. This preprocessing is performed prior to applying the meta attribute values to the target - JavaServer Faces component's properties.<br/> - <blockquote><pre> + JavaServer Faces component's properties.</p> + +<source> <clay:clay id="saveResidential" managedBeanName="residentialAddress" jsfid="saveCommand"/> <clay:clay id="saveBusiness" managedBeanName="businessAddress" jsfid="saveCommand"/> <component jsfid="saveCommand" extends="commandButton"> @@ -830,7 +835,7 @@ </attributes> <actionListener jsfid="logNavigationActionListener"/> </component> -</pre></blockquote></p> +</source> </subsection> @@ -839,7 +844,269 @@ <section name="Shale Test Framework"> <a name="test"/> - <p>FIXME - Describe Test Framework feature.</p> + <a name="test-introduction"/> + <subsection name="Introduction"> + + <p>Modern application development processes have embraced the idea of + <em>unit testing</em> as an integral step in creating high quality + software. In the Java world, a popular framework for building and + executing such unit tests is the <a href="http://junit.org">JUnit</a> + framework. It is straightforward for an application developer to + create a corresponding <em>test case</em> class for each class in the + application itsef, and ensure that the tests contained in the test case + get executed as part of the normal application build process.</p> + + <p>One of the tenets of unit testing is that a test case should focus + <em>only</em> on the methods of the class under test, in isolation from + related application classes, or APIs provided by any container that the + class under test might be installed into at runtime. But, how do you + test an application class that has dependencies on such APIs (such as + depending on the Servlet API to provide an <code>HttpServletRequest</code> + object representing an incoming HTTP request?</p> + + <p>A popular answer to this dilemna is to utilize a library of + <em>mock objects</em> -- classes that implement and emulate the container + APIs, but still run in the isolated environment of a JUnit test case. + Shale provides mock object implementations for its own features, as + well as features of the underlying container (Servlet + and JavaServer Faces) environment. In addition, convenient base classes + are provided to make it very easy to build your own test cases utilizing + these mock objects. This library is used to create unit tests for Shale + components itself, but it is primarily focused on making it easy to + build unit tests for application classes such as + <code>ViewController</code>s.</p> + + </subsection> + + <a name="test-services"/> + <subsection name="Provided Services"> + + <p>The Shale Test Framework provides mock object libraries, plus base + classes for creating your own JUnit <code>TestCase</code>s.</p> + + <p>Mock objects are provided in package <code>org.apache.shale.test.mock</code> + for the following container APIs:</p> + <ul> + <li>JavaServer Faces</li> + <li>Servlet</li> + </ul> + <p>These mock object classes implement the majority of the functionality + defined by the container API Javadocs (although some methods currently + throw <code>UnsupportedOperationException</code>). In addition, many + of these classes support public methods, outside of the defined API, + for configuring the object in a test environment. For example, + <code>MockServletContext</code> includes <code>addInitParameter()</code> + and <code>setDocumentRoot()</code> methods, to add context initialization + parameters to the set returned via <code>getInitParameter()</code> and + <code>getInitParameterNames()</code>, and to establish the base directory + for resolving servlet context resources, respectively.</p> + + <p>The <code>org.apache.shale.test.base</code> package contains abstract + base classes that wire together instances of the various container API + mock objects, in a manner similar to the way they would become available + at runtime. The following base classes are available:</p> + <ul> + <li><code>AbstractJsfTestCase</code> - Base class for unit tests that + require Servlet and JavaServer Faces objects to be available.</li> + <li><code>AbstractViewControllerTestCase</code> - Extension of + <code>AbstractJsfTestCase</code> that also provides convenient + utility methods needed to test common scenarios in unit tests for + <code>ViewController</code> implementation classes.</li> + </ul> + + <p>If you use one of these base classes, the <code>setUp()</code> method + found there will initialize a set of <code>protected</code> instance + variables for the container-managed objects you might need to access. + The set of initialized variables includes (variable name and type):</p> + <ul> + <li><code>application</code> (<code>MockApplication</code>)</li> + <li><code>config</code> (<code>MockServletConfig</code>)</li> + <li><code>externalContext</code> (<code>MockExternalContext</code>)</li> + <li><code>facesContext</code> (<code>MockFacesContext</code>)</li> + <li><code>lifecycle</code> (<code>MockLifecycle</code>)</li> + <li><code>request</code> (<code>MockHttpServletRequest</code>)</li> + <li><code>response</code> (<code>MockHttpServletResonse</code>)</li> + <li><code>servletContext</code> (<code>MockServletContext</code>)</li> + <li><code>session</code> (<code>MockHttpSession</code>)</li> + </ul> + + </subsection> + + <a name="test-using"/> + <subsection name="Using View Controller"> + + <p>The most common scenario for using the Test Framework is to construct + test cases for <code>ViewController</code> implementation classes. + Because the runtime environment of a <code>ViewController</code> is + quite constrained, it is easy to construct isolated unit tests that + exercise the methods exposed by a <code>ViewController</code> class. + The <em>Shale Use Cases</em> web application (included in the distribution) + contains many examples of such test cases, in the <code>src/test</code> + directory. We will use <code>org.apache.shale.usecases.locale.SelectTestCase</code> + (which tests the <code>org.apache.shale.usecases.locale.Select</code> + implementation) as an example of how such a test case can be constructed.</p> + + <ol> + <li>Create a new Java class <code>SelectTestCase</code>, in a package + directory (typically under <code>src/test</code> in your project) + that is the same as the package directory for the class you will be + testing. This allows your test case to access package private and + protected variables and methods in the class being tested.</li> + <li>Make sure that the package declaration matches that of the class to + be tested (in this case, <code>org.apache.shale.usecases.locale</code>.</li> + <li>Declare your class to extend <code>AbstractViewControllerTestCase</code> + (or, if you are not testing a <code>ViewController</code> implementation, + extend <code>AbstractJsfTestCase</code>): +<source> +public class SelectTestCase extends AbstractViewControllerTestCase { + ... +} +</source></li> + <li>Create a constructor that takes a <code>String</code> parameter, and + passes it to the superclass constructor: +<source> +public SelectTestCase(String name) { + super(name); +} +</source></li> + <li>Create a <code>setUp()</code> method and <strong>be sure</strong> + to call <code>super.setUp()</code> at the beginning. This method + will be called by JUnit immediately before it executes each + test method. +<source> +public void setUp() { + super.setUp(); + // Customization will go here +} +</source></li> + <li>After the call to the superclass <code>setUp()</code> method, + perform any other initialization required to execute the tests + in this test case. In our example case, a configuration method + on the <code>MockApplication</code> instance will be used to + define the default and supported <code>Locale</code>s for this + set of tests. This corresponds to what would happen at runtime, + when the JavaServer Faces initialization process used the contents + of the <code>/WEB-INF/faces-config.xml</code> resource to initialize + these values. In addition, we will create a new instance of the + <code>Select</code> class to be tested. It is important to create + a new instance for each test, to ensure that execution of one test + does not get influenced by the leftover property settings from a + previous test. +<source> +public void setUp() { + super.setUp(); + + // Configure the supported locales for this application + List list = new ArrayList(); + list.add(new Locale("en")); + list.add(new Locale("fr")); + list.add(new Locale("de")); + list.add(new Locale("es")); + application.setSupportedLocales(list); + + // Construct a new ViewController instance + vc = new Select(); + +} +</source></li> + <li>Create a static <code>suite()</code> method that identifies the + test methods to be provided by this test case. The simplest + approach is to leverage a constructor of the <code>TestSuite</code> + class that accepts the <code>Class</code> instance for this test + suite: +<source> +public static suite() { + return new TestSuite(SelectTestCase.class); +} +</source></li> + <li>Create a <code>tearDown()</code> method that cleans up any custom + variables you allocated in your <code>setUp()</code> method, and + then calls the <code>super.tearDown()</code> method. This will be + called by JUnit after each test is executed. +<source> +public void tearDown() { + vc = null; + super.tearDown(); +} +</source></li> + <li>Declare the custom instance variable(s) that you are setting up + in your <code>setUp()</code> method. In this case, we create an + instance of the <code>ViewController</code> class to be tested. + A new instance will be created (via a call from JUnit to the + <code>setUp()</code> method) before each test method is executed. +<source> +// The instance to be tested +Select vc = null; +</source></li> + <li>Create one or more individual test methods (which must be + <code>public</code>, return <code>void</code>, take no arguments, + and have a method name of the form <code>testXXXX</code>. For + advice on how to construct such methods, consult the + <a href="http://junit.org/">JUnit Web Site</a>, or any of the + large number of resources on the web describing how to use JUnit + to build unit tests. The following example tests what happens + when the <code>select()</code> method (which is executed when + the <em>Go</em> button is pressed), but the value entered is not + one of the valid options. <strong>NOTE</strong> that the test + method must emulate the runtime calls to the <code>ViewController</code> + event methods, because there is no actual runtime container + available to perform these tasks automatically: +<source> +// Test behavior of select() with an invalid value +public void testSelectInvalid() { + + Locale locale = new Locale("en"); + facesContext.getViewRoot().setLocale(locale); + vc.init(); + vc.preprocess(); + vc.setLocale("it"); + String result = vc.select(); + assertEquals(Select.FAILURE, result); + checkMessageCount(1); + assertEquals(locale, facesContext.getViewRoot().getLocale()); + +} +</source> + The test case sets the <code>locale</code> property (which is + bound to a dropdown component at runtime, but we are simulating + the behavior of Update Model Values here) to an invalid value, + then calls the <code>select()</code> method. The test then + verifies that the logical outcome returned matches that which + is expected (<code>Select.FAILURE</code>), that there was an error + message queued to be displayed, and that the <code>locale</code> + for the current view was <strong>NOT</strong> actually changed. + <br/></li> + <li>Finally, integrate the execution of this test case into your + build script. Many IDEs will take care of this for you; however, + if you are creating an Ant build script by hand, you might find + the <code>test</code> target from the Shale Use Cases example + a useful starting point. It locates <em>all</em> the test cases + related to the entire application, and executes them: +<source> + <target name="test" depends="test.compile" + description="Execute unit tests"> + + <mkdir dir="${build.home}/test-results"/> + + <echo message="Running unit tests ..."/> + <junit printSummary="no" fork="yes" + haltonfailure="yes" haltonerror="yes"> + <classpath refid="test.classpath"/> + <formatter type="plain" + usefile="false"/> + <formatter type="xml" + usefile="true"/> + <batchtest todir="${build.home}/test-results"> + <fileset dir="${build.home}/test-classes" + includes="org/apache/shale/usecases/*/*TestCase.class"/> + </batchtest> + </junit> + + </target> +</source></li> + </ol> + + </subsection> </section> --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]