Junit Solutions Web Applications and Services
Standing on the shoulders of giants l Special thanks to: Mike Clark JUnit FAQ Maintainer Glenn Vanderburg
Philosophy “Any program feature without an automated test simply doesn’t exist.” from Extreme Programming Explained, Kent Beck
Philosophy l Test-driven development l Refactoring l Evolution / emergence
Analogy “It's like trying to build a pyramid by starting at the top, rather than the bottom. No matter how hard you try, you're doomed to failure.” from
Testing pyramid API Developer time Delivered application Angry customers Iteration builds Customer / developer rework
Assertions l Software must and will change over time. Right? l We desire clean uncluttered code. Right? l Can we do both? If so, how?
Refactoring Restructuring of software by applying a series of internal changes that do not affect its observable behavior Fowler, Refactoring, 1999
What is JUnit l De facto Java unit testing framework l Integrated nicely with IDEs and Ant l Easy to learn
Our first unit test package org.example.antbook.common; import junit.framework.TestCase; public class SearchUtilTest extends TestCase { public void testSearch() throws Exception { // right API? Document[] docs = SearchUtil.findDocuments("erik"); assertTrue(docs.length > 0); }
Lean and green package org.example.antbook.common; public class SearchUtil { public static final Document[] findDocuments(String queryString) throws SearchQueryException, SystemException { Document[] results = new Document[1]; return results; }
Test Runners l Text l Swing l Ant Demo
JUnit Assertions l assertTrue(boolean condition) assertFalse(boolean condition) l assertEquals(Object expected, Object actual) Uses equals() comparison Overloaded for all primitive types l assertSame(Object expected, Object actual) assertNotSame(Object expected, Object actual) Uses == comparison l assertEquals(float expected, float actual, float tolerance) l assertNull(Object o) assertNotNull(Object o) l fail(String message) + overloaded String methods
JUnit Design - Pattern dense Assert TestCaseTestSuite > Test YourTest * Courtesy of Mike Clark
JUnit Rules and Conventions Subclass TestCase Prior to v3.8, String-arg constructor required l Test methods public void testXXX() [throws …] Any number of assertions per method Implement main to run from command-line, but not necessary l Optionally add setUp / tearDown methods.
Test fixture package org.example.antbook.ant.lucene; import java.io.IOException; import junit.framework.TestCase; public class HtmlDocumentTest extends DocumentTestCase { HtmlDocument doc; public void setUp() throws IOException { doc = new HtmlDocument(getFile("test.html")); } public void testDoc() { assertEquals("Title", "Test Title", doc.getTitle()); assertEquals("Body", "This is some test", doc.getBodyText()); } public void tearDown() { doc = null; }
TestCase lifecycle setUp testXXX() tearDown() Repeats 1 through 3 for each testXXX method…
Test Suites package org.example.antbook; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import org.example.antbook.junit.SimpleTest; import org.example.antbook.ant.lucene.HtmlDocumentTest; public class AllTests { static public Test suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(SimpleTest.class); suite.addTestSuite(HtmlDocumentTest.class); return suite; } Demo
JUnit Best Practices l Separate production and test code l But typically in the same packages l Compile into separate trees, allowing deployment without tests l Don’t forget OO techniques, base classing l Test-driven development –Write failing test first –Write enough code to pass –Refactor –Run tests again –Repeat until software meets goal –Write new code only when test is failing
Base classing l Groups of tests share common needs package org.example.antbook.ant.lucene; import java.io.File; import java.io.IOException; import junit.framework.TestCase; public abstract class DocumentTestCase extends TestCase { protected File getFile(String filename) throws IOException { String fullname = this.getClass().getResource(filename).getFile(); File file = new File(fullname); return file; }
Ant and JUnit l Two great tastes that taste great together l You’re building with Ant already, now test with it too! l Facilitates continuous integration and testing l Richer and more flexible reporting than otherwise possible, including publishing and ing l Fail builds if tests fail
task
and formatters <fileset dir="${test.dir}” includes="**/*Test.class” />
Tricks of the trade <junit printsummary="no" errorProperty="test.failed" failureProperty="test.failed" fork="${junit.fork}"> <test name="${testcase}" todir="${test.data.dir}" if="testcase"/>
l Aggregates XML generated by XML formatter output l Transforms result using XSLT into report l Default “frames” and “noframes” HTML stylesheets built-in, but customizable
syntax <report format="frames” todir="${test.reports.dir}" />
Demo
Ant Best Practices l Use naming conventions: *Test.java *TestCase.java Fork, typically Use errorproperty and failureproperty Ironically, don’t failonerror Use after l Use Ant properties for location of XML results and reports resources to build directory Pass parameters using
test data
Passing parameters to tests <junit printsummary="no”… <sysproperty key="docs.dir” file="${test.dir}” /> <sysproperty key="index.dir” file="${test.dir}/index” /> … private String docsDir = System.getProperty("docs.dir"); private String indexDir = System.getProperty("index.dir");
Cactus l In-container unit testing l Excellent for testing: EJB Servlets, Filters, Taglibs Container-dependent frameworks, like Struts
Cactus Architecture
Cactus test case package org.example.antbook; import org.apache.cactus.ServletTestCase; import org.apache.cactus.WebRequest; public class RequestUtilTest extends ServletTestCase { public void beginGetValueParam(WebRequest theRequest) { theRequest.setURL("localhost:8080", "/antbook", "/test/test.jsp", null, "param=url"); } public void testGetValueParam() { request.setAttribute("param", "request"); assertEquals("url", RequestUtil.getValue(request, "param")); } public void testGetValueAttribute() { request.setAttribute("param", "request"); assertEquals("request", RequestUtil.getValue(request, "param")); }
Back to green package org.example.antbook; import javax.servlet.http.HttpServletRequest; public class RequestUtil { public static final String getValue (HttpServletRequest request, String key) { String value = request.getParameter(key); if (value != null) { return value; } value = (String) request.getAttribute(key); if (value != null) { return value; } return null; }
Cactus details l task to start, test, stop l Deploy Tests Cactus APIs Cactus-enabling web.xml
XDoclet diversion - conditional Cactus <webdoclet destdir="${build.dir}/web/WEB-INF" force="${xdoclet.force}" mergedir="metadata/web"> <configParam name="cactusOn” value="${enable.cactus}” /> <deploymentdescriptor validatexml="true" destdir="${build.dir}/${site}" />
XDoclet web.xml merge point <XDtConfig:ifConfigParamEquals paramName="cactusOn” value="true"> ServletRedirector org.apache.cactus.server.ServletTestRedirector ServletTestRunner org.apache.cactus.server.runner.ServletTestRunner
StrutsTestCase l Runs as Mock or on Cactus l Provides assertions for expected Struts ActionErrors and forwards
StrutsTestCase Example package org.example.antbook.struts; import servletunit.struts.CactusStrutsTestCase; public class SearchFormTest extends CactusStrutsTestCase { public SearchFormTest(String s) { super(s); } public void testValidation() { addRequestParameter("query",""); setRequestPathInfo("/search"); actionPerform(); verifyActionErrors(new String[] {"query.required"}); verifyInputForward(); }
Cactus - Servlet Test Runner l New addition, allows tests to be run through browser l XML results returned l Using XSLT capable browser, report transformed to HTML on the fly
Other JUnit Extensions l HttpUnit Parses HTML results into DOM Easy link navigation and form population Useful for automated acceptance tests l Canoo WebTest HttpUnit inside Ant l JUnitPerf Wrap any JUnit tests Measure desired performance and scalability tolerances
xUnit l JUnit l NUnit l CppUnit l RubyUnit l XMLUnit l dbUnit l Etc, etc, etc
Continuous Integration l Build often, triggered by commit even l Anthill Maciej & Urbancode rock! l CruiseControl l Gump
Unit testing issues l How do I test database dependent code? dbUnit l Should I test my user interface? How? HttpUnit Canoo WebTest l But, before you test at these levels, see if refactoring is possible
Code Coverage l Clearly see how much is being tested
Conclusions l “Any program feature without an automated test simply doesn’t exist” l Testable code improves confidence and design l Easy! l “Keep the bar green to keep the code clean!”
Online Resources l JUnit.org l Cactus l Clover l dbUnit l HttpUnit l Canoo WebTest l Mike Clark’s site l Glenn’s reference card
Book Resources l eXtreme Programming Explained: Embrace Change & Test Driven Design Kent Beck l Refactoring: Improving the Design of Existing Code Martin Fowler (Addison-Wesley, 1999) l Java Tools for Extreme Programming Rick Hightower and Nick Lesiecki (Wiley, 2001)
Last but not least… l Chapter 4: Testing with JUnit l Chapter 12: coverage of Cactus l Chapter 15: Testing web services l Test-centric throughout
The End l Some examples provided on the symposium CD l Examples from my book freely available at See Sections/Learning/ch04 specifically l Thank You! l Q & A assertTrue(you.willReturnSpeakerEvaluation())