Wednesday, March 12, 2008

Only your unit needs this unit test

Unit testing is great – test your code, but without your system!

It shows it's promise when you're out a fix to a problem, and that problem happens to be buried. So buried that reproduction only happens when the stars align and your operating system has a totally different setup and time zone or your application server decides it's going to leak or something equally nefarious.

The real difficulty is being able to quickly send wacky input to your code to test it, no matter where it runs. Because you can't always easily control these inputs.

And the first mistake is that people start building up a test for the unit and everything it references. This is bad, because then you're not just testing your unit, your testing the entire subsystem. Which often has it's own runtime requirements.

This leads me to my rule...

Only your unit needs your unit test

When you need to run everything underneath the unit, what happens if the unit happens to be at the top of the pile? Yep, you end up running the entire damn thing. And chances are, that isn't easy.


Java has made this easy for you

When your code references other code through interfaces, you have an automatic win. Your unit test just has to somehow mimic how the interface is attached to reality. And lo and behold, there are alot of ways we achieve dependency injection in Java. Spring, Guice, EJBs.

We use a lot of EJBs, so I'm going to mention how that can work.

Cheat on your EJBs

Generally, our system uses a lot of stateless session beans, so we actually just use the whole injection framework purely for connecting up code. So all you need to do is fake the injection.

public static void inject(Object target, String fieldName, Object value) throws ...
Field field = target.getClass().getDeclaredField(fieldName);
field.set(target, value);

Yeah, it seems dirty, but that works on each instance I've actually needed, because we don't happen to have much else in the way of dependencies.

So now, to say, test my EJB, I might write the following code:

MyEJB myEJB = new MyEJB();
MyDependency myDependency = new TestDependency(); // TestDependency derives from the MyDependency interface
inject(myEJB, "myDependency", myDependency);

// do test stuff

Here is what MyEJB probably looks like:

public class MyEJB implements Whatever
@EJB private MyDependency myDependency;

Of course, if you have more initialization and whatever going on, then you might want to take a look at EasyGloss for your needs.

Another issue is that TestDependency really doesn't need to do much, other than return some stub stuff. This leads to...

Use EasyMock to fake it

The EasyMock project was born to handle generating things like TestDependency. You have a lot of control on what you want to guarantee happens to things like MyDependency when MyEJB is running. The above example becomes

MyEJB myEJB = new MyEJB();
MyDependency myDependency = createMock(MyDependency.class);
inject(myEJB, "myDependency", myDependency);

.andReturn("No thanks, I'm doing my hair");

// do test stuff

Now we have verified that MyDependency successfully declined MyEJB's advances.

Mocking your code is great, but...

A final note is pointing out the major weaknesses of this approach. First, because real system isn't testing, mocking guarantees consistency, not success. Second, if your code isn't described in wonderful Java, you are probably not going to test it using this approach.

And what is used in Java but isn't Java? Database queries, of course... ew...

No comments: