Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Saturday, February 14, 2009

Ipsedixit - Java unit testing with less code

As software engineers, we don't want to spend a lot of time performing repetitive tasks. Repetitive tasks in coding can almost always be solved through some software solution. Smart IDE's, tools for refactoring, and libraries to assist database access are some examples of how software helps reduce duplication.

Unit testing has gotten a lot of focus in the last few years, primarily from the Agile/XP practice of Test-Driven Development. Arguably, TDD is now mainstream practice, not some crazy XP idea that only those mad Agile evangelists espouse on their blogs. In terms of tool support, we have JUnit, TestNG, EasyMock, JMock, and a lot of other tools to help us write and run tests.

While thats all great, there is still the issue of test data. I've seen a lot of unit tests contain a lot of setup code just to create suitable test data.

Consider a reasonably simple JUnit4/EasyMock test for a service, where we want our service to call out to a DAO which returns a particular object:
public class MyServiceUnitTest {
private MyDao myDao;
private MyService myService;;

@Before
private void setup() {
myDao = EasyMock.createMock(MyDao.class);
myService = new MyService(myDao);
}

@Test
public void canGetDataFromDaoAndReturn() {
Serializable primaryKey = "Don't care";
MyDomainObject myDomainObject = new MyDomainObject();

EasyMock.expect(myDao.load(primaryKey)).andReturn(myDomainObject);
EasyMock.replay(myDao);

MyDomainObject result = myService.findById(primaryKey);
Assert.assertSame(myDomainObject, resut);
EasyMock.verify(myService);
}
}
Now there's nothing actually wrong with this test, but taking TDD to the letter (fake it till you make it), the implementation of the class would actually be wrong. It would look like:
public class MyService {
...

public MyObject findById(Serializable id) {
return myDao.load("Don't Care");
}
}
The only way to force the correct implementation is to triangulate. That is, to perform the same test but pass in a different ID. That sounds like duplicated effort to me!

Whilst the example above is trivial and somewhat contrived, there is one other key point I'd like to make: I had to set up some test data to get the tests to work. There are two pieces of data that are used.

The first is the value of "primary key" that is an instance of Serializable. Actually, that's only partly true, it's a String as far as the test is concerned (the value is "Don't Care"). I could have chosen an Integer, or Float, or any type that implements Serializable. That's a fatal flaw in my test because I risk getting a ClassCastException if somewhere, someone assumes that ID's are String's and tries to cast it as such.

The second piece of test data is the instance of MyDomainObject. Lucky in our case it was easy to create, but what if MyDomainObject had several arguments on the constructor? Just for fun, let's put on the constructor a javax.xml.transform.Transformer, org.hibernate.Session and javax.mail.Session. And since the domain object is in a JAR that you don't have the source to, you can't just go and change the API :-)
   ....

@Test
public void canGetDataFromDaoAndReturn() {
Serializable primaryKey = "Don't care";
Transformer transformer = TransformerFactory.newTransformer();
org.hibernate.Session hibernateSession = SessionFactory.openSession(); // uh oh, this needs a hibernate config or it will throw an exception!
javax.mail.Session mailSession = ...// umm, how do I get one of these things without JavaEE?

MyDomainObject myDomainObject = new MyDomainObject(transformer, hibernateSession, umm...);

// continue testing ...
}
Not quite so easy to create one for the purpose of testing, is it? You could spend quite a bit of time messing around with this, just to set up your test data. Not only is that tedious, but when the next person looks at the test they'll need to interpret all that setup code, which makes the test harder to understand.

Enter Ipsedixit. Ipsedixit is a tool that takes care of thinking up that test data for you. It even knows how to "instantiate" interfaces and classes, and provides them to your test, so in the example above it doesn't matter what MyDomainObject needs on the constructor.

Using Ipsedixit, lets have a look at the unit test again...
@RunWith(JUnit4IpsedixitTestRunner.class)
public class MyServiceUnitTest {
private MyService myService;
@Mock private MyDao myDao;
@Arbitrary private MyDomainObject myDomainObject;
@Arbitrary private Serializable primaryKey;

@Before
private void setup() {
myService = new MyService(myDao);
}

@Test
public void canGetDataFromDaoAndReturn() {
EasyMock.expect(myDao.load(primaryKey)).andReturn(myDomainObject);
EasyMock.replay(myDao);

MyDomainObject result = myService.findById(primaryKey);
Assert.assertSame(myDomainObject, resut);
EasyMock.verify(myService);
}
}
This has the advantage of the previous test in a number of ways:
  • I don't have to construct any test data at all. The @Arbitrary annotation will make Ipsedixit provide an instance for you.
  • I don't need to set up my mock. Ipsedixit can create one automatically using the @Mock annotation. You may also use Atunit for automocking, if you prefer.
  • My test code is reduced, meaning the intent of the test is clearer
For common Java types and primitives, Ipsedixit can provide random values. For example placing an @Arbitrary annotation before an int field will make Ipsedixit populate that field with a random number. String fields get random Strings. You can even customise how the random value is provided (ie, a string of a particular length, or a number in a certain range).

For other types, such as the ones in our example, Ipsedixit will provide a dynamic proxy. So, for the Serializable we get a JDK Proxy, and for MyDomainObject we get a CGLIB proxy. It doesn't actually matter what they are, but the point is that they are objects of the types we require.

Ipsedixit does not require any particular testing framework to run, but there are integration points for JUnit 3, JUnit 4, and Spring-Test. It would not be a hard ask to integrate TestNG and other frameworks either.

You might be surprised at how seemingly complicated tests can be simplified by introducing Ipsedixit. Give it a go today!

Monday, January 12, 2009

The joys of Websphere 6.1 and Commons Logging

My latest project uses libraries that use Jakarta Commons Logging (JCL) and logging works great using Log4J, until we deploy to Websphere which is our target environment. This blog post is a war story in getting it to work.

The Problem

First I'll mention that there seems to be known problems with JCL and Websphere. IBM have some documentation on their website as well as some helpful tips to resolve the issue, and many folk have blogged their experiences with this issue. The Commons Logging Wiki also has some good info. While all of them were really helpful, none completely solved my problem.

Before explaining my solution, lets understand that the root cause of this pain is IBM bundling up a Commons Logging distribution inside one of their server JAR's, and not only that, placing a configuration file in there too that overrides Commons Logging's default behavior. You can see the JCL classes and commons-logging.properties file in the jar ${WAS_INSTALL_ROOT}/plugins/com.ibm.ws.runtime_6.1.0.jar. That means that without fiddling with the class loading order, you are stuck with IBM's version and default configuration of JCL. I'm in a corporate environment and messing too much with classloaders raises red flags, so fiddling with the classloader was something I wanted to avoid, if possible.

The version of WAS and it's embedded JCL implementation are crucial to this discussion. Firstly, my WAS version is 6.1.0.17. The version of JCL is hard to determine but is seems to be 1.0.3. I understand different fixpacks of WAS handle this slightly differently but no doubt they are all variations on the same theme.

The Solution

The obvious solution to this is to configure JCL to use Log4J instead of the default defined in WAS's commons-logging.properties. There are a number of ways to do this, I chose to use the JDK 1.3 Services Discovery mechanism (as described bn the Commons Logging Javadoc which explains the various config options). Using information gleaned from other blog posts, I made my org.apache.commons.logging.LogFactory file and added a single line, "org.apache.commons.logging.impl.Log4jFactory". I redeployed the application, held my breath, and ....

... nothing happened. It didn't work! Here is why.

First problem was that my application bundled JCL v1.1.1, and the org.apache.commons.logging.impl.Log4jFactory class was removed in the 1.1.x stream. So, JCL was actually throwing a ClassNotFoundException when attempting to load the class and then falling back to the default (as specified in that wretched properties file bundled deep in WAS's server jar). No Bingo!

But doesn't WAS bundle up JCL 1.0.3, which does have that class? Apparently not. Having a look in that Websphere JAR file shows lots of JCL classes but not that one. So, the answer is to ensure you do the following:
  • ensure you deploy Jakarta Commons Logging 1.0.3 with your application, and
  • add the org.apache.commons.logging.LogFactory file to META-INF/services.
Once I did that, it all worked. Half a day wasted.

Why IBM chose to bundle such a common library (it even has "common" in the name) and put it first on the classpath is baffling. By doing that, it forces applications to use that version of the library, regardless of whether they package it up or not. JavaEE applications should (actually, they must) include JAR's that are not part of the JDK or JavaEE stack, and as application developers we should expect third-party libraries to work without having to mess around with classpath or server settings. By no means are IBM the only ones guilty of this but it is frustrating to waste half a day chasing a problem caused by classpath hell.

Monday, December 29, 2008

JNDI weirdness on IBM Websphere

I didn't want my first blog post to be about JNDI or Websphere, but here it is.

The most recent project I'm working on involves IBM Websphere 6.1. I've fought the technology and lost several times, but I was determined to get some pretty basic JNDI stuff working properly.

I have a resource set up on Websphere, lets make it a JMS ConnectionFactory and call it Bob. It's JNDI location is jms/bob and its all set up correctly.

My web application, being totally clueless about Bob or it's JNDI location, declares a resource in web.xml in the usual way, and specifies it's internal name as jms/fred. This is all standard JavaEE. Deploying the app into Websphere, it gives me the opportunity to map my resource at jms/bob to my declared resource jms/fred. Looks good! Except, when performing the deployment I get errors in the logs:

Looking up JNDI object with name [java:/comp/env/jms/fred]
...
javax.naming.NameNotFoundException: Name not found in context "java:".


What the heck, name "blank" not found in the java context? I'm sure I just asked for /comp/env/jms/fred, and it says so in the log! Thanks WAS for another unhelpful error message. Google didn't help me either, but I did end up finding the problem. Here is is for posterity.

See that first slash, the one before "comp". IBM Websphere does not like it, and will give that cryptic error if you include it. Some application servers seem to be forgiving, but not WAS.

Get rid of the leading slash, and all will be well. It's not java:/comp/env, it's java:comp/env!