In the company I work in we have some complex tags that are not easily unit-testable because there are some dependencies between them, the best option here should be to re-factor them, unfortunately as this would break backward compatibility, we have a process of deprecation to go through until we can actually do that.
But code with hardly any tests on them always makes me feel uncomfortable. so I decided to do some (almost integration) tests but running as part of the normal unit test suite
I’m doing this by placing the tags on a jsp, running this on a embedded Jetty Server and using Selenium Webdriver to test the resulting html.
First the base class, which takes care of starting/stopping the embedded Jetty Server and setting up the web driver.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public abstract class BaseTest { private static int port = 9595; private static Server server = new Server(port); protected static String baseUrl = "http://localhost:" + port + "/"; protected static WebDriver driver; @BeforeClass public static void setup() throws Exception { //configure jetty as an exploded war URL webAppUrl = BaseTest.class.getClassLoader().getResource("/"); WebAppContext wac = new WebAppContext(); wac.setContextPath("/"); wac.setWar(webAppUrl.toExternalForm()); server.setHandler(wac); server.setStopAtShutdown(true); //makes sure the sever is stopped even if the @AfterClass is never reached server.start(); //HtmlUnit drive doesn't give a popup, the rest of the drivers do driver = new HtmlUnitDriver(); } @AfterClass public static void teardown() throws Exception { driver.close(); server.stop(); } |
In my project I have setup a resources directory that contains a WEB-INF/ with a web.xml containing my custom tag definitions and my tld file as you would with a normal web project
Webdriver comes with a set of Drivers for different browsers (Firefox, chrome, IE, iPhone, Android, etc) for testing browser compatibility, I don’t care about that, the tags I’m testing do not output any html/css that might require this.
The HtmlUnit driver is internal and doesn’t popup a browser window so ideal for my purpose.
Writing a test now becomes very easy:
In this case I have a custom tag that when invoked simply outputs “Hello World”
my test jsp:
1 2 3 4 5 6 7 8 9 10 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<%@ taglib uri="/customtaglib" prefix="custom" %>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<p><custom:helloworld /></p>
</body>
</html> |
The Test class:
1 2 3 4 5 6 7 8 9 10 | public class HelloWorldTest extends BaseTest { @Test public void testHelloWorldTag() { driver.get(baseUrl + "helloworld.jsp"); Assert.assertEquals("Hello World", driver.getTitle()); WebElement element = driver.findElement(By.cssSelector("body p")); Assert.assertEquals("Hello World", element.getText()); } |
Testing the resulting html is made very easy, by having the WebElement findElement() and List
and then setting predicates with the By class. for instance By.cssSelector(), By.tagName() etc.
It is also possible to test click throughs and form submits, by calling click() or submit() on WebElements.
Added bonus is that although the tag code is running in the embedded jetty server and not in the unit tests itself it’s coverage is measured by our code coverage tools
One thing to look out for is for the embedded Jetty to work with JSP’s you need to add jetty’s version of the jsp spec to your project not the javax.servlet.* ones.
my maven dependencies look likes this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <dependencies> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty</artifactId> <version>6.1.22</version> </dependency> <dependency> <groupId>org.mortbay.jetty</groupId> <artifactId>jsp-api-2.1</artifactId> <version>6.1.14</version> </dependency> <dependency> <groupId>org.mortbay.jetty</groupId> <artifactId>jsp-2.1</artifactId> <version>6.1.14</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <scope>test</scope> </dependency> </dependencies> |
Unittests are an integral part of building quality software and the sole responsibility of the developer.
Here I explain some of the lessons i’ve learned after the last couple of years.
- Unittests takes times to write
I’m even inclined to say that writing your tests should take at least the time as writing your functionality takes. - Start with the unit tests
Start writing your unittests first, setting up just enough of your classes to get the test compiling, this will force you write your test based on the functionality required not the buggy implementation you’ve just written. - Independent
as the name says, a unit test will test a single unit of code, use integration tests to see how parts are working together. - Small
A unit test should test a small bit of functionality, one class, or even one method. I usually write extra unittests that test entire use-cases, but those work next to the normal ones. - Test negatives as wel as positives
Your first test will probably check whether the functionality works when specifying the correct parameters , but also test whether the right results are returned or the correct exceptions are thrown when specifying the wrong ones. - Use code coverage tools to measure effectiveness
Coverage tools, like for instance Clover will tell you how much of your code is covered, and also which classes are the most risk (high complexy, low coverage) - Be careful when using mock objects
Using mock objects keeps your unittests indepent but it has the danger that you are testing your mock objects instead of your own functionality, write integration tests - Don’t change outputted results into asserts
It can be very tempting to first see what your class outputs when running a test, and then writing asserts to test on those results, but you will just be validating that bug that is still in there.


