Tuesday, June 8, 2010

On Design

The most important thing to learn about design is that it’s all about trade-offs. There are no perfect designs. Every design has a down side, some draw drawback that will cause difficulties. The is true for all kinds of design whether it’s a building, an organization, a user interface, or software.

Since all designs have problems, the designers job is not to get rid of all problems in the design, it’s to decide which problems are the least costly to have, or which problems are most likely to cause the least damage to the goal of the product. So, to do design well you have to be able to understand what the options are, what the issues are with each option, and in the particular context you are operating in, which issues are the least harmful.

It’s important to understand this when it comes to software design patterns. Design patterns are just common ways of trading one set of problems for another, with the belief that the new set of problems is better than the old. The only time it makes sense to use a pattern is when you have the particular set of problems it was designed to fix, and you’re better off with the new set of problems it leaves you with. But, in order to make good decisions on this, you’ve got to understand what the problem sets are, and how they affect your particular context.

This is not easy! And it takes experience to do it well. Often, the only way to know what the issues are with a particular design choice is that you’ve done it that way before, and seen what issues came from it. However, studying, practicing, and learning from others can take you a long way. The important thing is to never to be fooled into thinking that there are no drawbacks to a particular design decision.

Tuesday, June 1, 2010

Auto injection in jUnit

After doing Java for many years, and TDD for several, I’ve settled into a fairly consistent way of designing and testing my classes. Any time you start to following a pattern in your code, if you’re paying attention, you’ll notice you’re writing the same code over and over. Now, it usually isn’t exactly the same or you’d easily just move it into a class and use the class. No, it’s usually code that is different every time, but it’s essentially doing the same thing, just with different classes. This is one example of what is generally referred to as boilerplate code. It’s aso one of the subtle forms of code duplication, and it’s something you want to avoid as much as possible.

I recently noticed that due to how I write my classes and tests, around 95% or more of my setup methods in my tests consisted of creating the class to be tested, creating some mocks, stubs, or fakes, and then injecting them into the class. Sure, technically every setup method was different, but this was boilerplate code for sure. And it was starting to get on my nerves. The setup usually looked something like this:

public class TestFoo {
   private Foo foo;
   private DependencyOne dependencyOne;
   private DependencyTwo dependencyTwo;

   @Before
   public void setup() {
      foo = new Foo();
      dependencyOne = new DependencyOneMock();
      dependencyTwo = new DependencyTwoMock();
      foo.setDependencyOne(dependencyOne);
      foo.setDependencyTwo(dependencyTwo);
   }
   ... then all the tests
}

One of the best ways to attack boilerplate code is by using the concept of convention over configuration. Basically all my setup methods were just configuration of the class I was testing, so what I needed to do is come up with a convention that made the configuration unnecessary, and some way to act upon the convention.

The convention starts with the @Target annotation. The idea is that you put this annotation on a field to signify that the field is the target of the test class that will need to have dependencies injected into it. You can also put @Target on a method, and whatever is returned from the method will be used as the target for injection.

Secondly, when you define a field on the test class if it matches a setter method on the target class, then it will be injected into the target using the setter. If no setter is found, but there is a field with the same name on the target, then the value will be copied to the field on the target.

In order to actually act on this convention I used the jUnit @Rule annotation. I called my rule AutoMockAndInject. If you aren’t familiar with how this annotation works, you can read about it here. I will put the code for my rule and the target annotation at the bottom of this post.

So, following the convention and using the rule my test class now looks like this:

public class TestFoo {
@Rule public AutoMockAndInject autoInject = new AutoMockAndInject();
@Target private Foo foo = new Foo();
private DependencyOne dependencyOne = new DependencyOneMock();
private DependencyTwo dependencyTwo = new DependencyTwoMock();

   ... then all the tests
}

One thing to remember here is that jUnit creates a new instance of the test class for each test method it runs. Otherwise, doing it this way could cause some problems.

I also use Mockito when I don’t want to make a hand written mock. The AutoMockAndInject rule works with the @Mock annotation from Mockito. It will create the mock object and inject it into the target. So if I want to use Mocito for my dependencies instead of hand written ones, the test class would look like this:

public class TestFoo {
@Rule public AutoMockAndInject autoInject = new AutoMockAndInject();
@Target private Foo foo = new Foo();
@Mock private DependencyOne dependencyOne;
@Mock private DependencyTwo dependencyTwo;

   ... then all the tests
}

I just started doing this a couple weeks ago, and so far I like it. It’s cut down on a lot of boilerplate code. But, in my experience, it usually takes at least a few months of doing something before you really see if it was a good idea or not. So, we’ll see if in the long run it really makes thing better.

Here is the code for my annotation and the jUnit rule I used for doing this.

@Retention(RetentionPolicy.RUNTIME)
public @interface Target {
}

public class AutoMockAndInject implements MethodRule {
   private static final String specialFields = "$VRc,serialVersionUID";
   private Object target;

   public final Statement apply(final Statement base, FrameworkMethod method, final Object target) {
      return new Statement() {
         @Override public void evaluate() throws Throwable {
            before(target);
            base.evaluate();
         }
      };
   }

   protected void before(Object source) throws Throwable {
      createMockitoMocks(source);
      if (hasTargetAnnotation(source))
      autoInject(source);
   }

   private void createMockitoMocks(Object source) {
      MockitoAnnotations.initMocks(source);
   }

   private boolean hasTargetAnnotation(Object source) throws Exception {
      return hasTargetFiled(source) || hasTargetMethod(source);
   }

   private boolean hasTargetMethod(Object source) throws Exception {
      for (Method method : source.getClass().getMethods()) {
         if (method.getAnnotation(Target.class) != null) {
            target = method.invoke(source);
            return true;
         }
      }
      return false;
   }

   private boolean hasTargetFiled(Object source) throws Exception {
      for (Field field : getAllFields(source.getClass())) {
         if (field.getAnnotation(Target.class) != null) {
            target = getFieldValue(source, field);
            return true;
         }
      }
      return false;
   }

   private Object getFieldValue(Object target, Field field) throws Exception {
      field.setAccessible(true);
      return field.get(target);
   }

   private Set<Field> getAllFields(Class<?> clazz) {
      return getAllFields(new HashSet<Field>(), clazz);
   }

   private Set<Field> getAllFields(Set<Field> fields, Class<?> clazz) {
      for (Field field : clazz.getDeclaredFields())
         if (notSpecialField(field))
            fields.add(field);
      if (clazz.getSuperclass() != null)
         getAllFields(fields, clazz.getSuperclass());
      return fields;
   }

   private boolean notSpecialField(Field field) {
      return !specialFields.contains(field.getName());
   }

   private void autoInject(Object source) throws Exception {
      ensureTargetExists();
      Set<Field> targetFields = getAllFields(target.getClass());
      for (Field field : getAllFields(source.getClass()))
         if (!callSetterIfExists(source, field))
            setFieldIfExists(source, targetFields, field);
   }

   private void ensureTargetExists() {
      if (target == null)
         throw new RuntimeException("Target value is null, did you forget to create it?");
   }

   private boolean callSetterIfExists(Object source, Field field) throws Exception {
      Method method = getMethod(target, getSetterName(field));
      if (method != null) {
         method.invoke(target, getFieldValue(source, field));
         return true;
      }
      return false;
   }

   private Method getMethod(Object target, String setterName) {
      for (Method method : target.getClass().getMethods())
         if (method.getName().equals(setterName))
            return method;
      return null;
   }

   private String getSetterName(Field field) {
      return "set" + StringUtils.capitalize(field.getName());
   }

   private void setFieldIfExists(Object source, Set<Field> targetFields, Field field) throws Exception {
      Field destField = getField(field.getName(), targetFields);
      if (destField != null)
         setField(target, destField, getFieldValue(source, field));
   }

   private Field getField(String name, Set<Field> fields) {
      for (Field field : fields)
         if (field.getName().equals(name))
            return field;
      return null;
   }

   private void setField(Object target, Field destField, Object fieldValue) throws Exception {
      destField.setAccessible(true);
      destField.set(target, fieldValue);
   }
}