Monday, February 22, 2010

Career 2.0 Video

Preparing to present Jared Richardson's video on Career 2.0. http://qik.com/video/1009098. Thanks Jared for making this public. If you don't like to watch videos, read his book: http://career20.blogspot.com/

I think this is a great video that everyone who is a developer should watch to make sure they still have a job in 5, 10, 15 years from now.

Tuesday, February 16, 2010

JUnit and Parameterized tests

So, over the past few weeks, I have been playing with JUnit 4's Parameterized tests, and found them to be extremely useful in cases that you want to throw a lot of different values at a method to make sure you've covered all of your bases. A great, simple, example of this is when you want to test an equals method in your code.

Let's take a class Person which looks like this:

public class Person {
    private final String firstName;
    private final String lastName;
    private final long id;
    public Person (String firstName, String lastName, long id) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.id = id;
    }
    ... 
    (getters, hashCode, toString go here)

    public boolean equals(Object obj) {
       if (this == obj)
           return true;
       if (obj == null)
           return false;
       if (getClass() != obj.getClass())
           return false;
       Person other = (Person) obj;
       if (firstName == null) {
           if (other.firstName != null)
               return false;
       } else if (!firstName.equals(other.firstName))
           return false;
       if (lastName == null) {
           if (other.lastName != null)
               return false;
       } else if (!lastName.equals(other.lastName))
           return false;
       if (id != other.id) 
           return false;
       return true;
   }
}

Now, you can create a test, or series of tests in a method to do this, but that is cumbersome. I found this an easy way to miss tests, so I looked to something different. Parameterized tests were actually really easy to set up and get going, so I created something like this:

@RunWith(Parameterized.class)
public class PersonEqualsTest {

    @SuppressWarnings("unchecked")
    @Parameters
    public static Collection data() {
        //
        return Arrays.asList(new Object[][] {
                {
                    new Person("Jane", "doe", 1234), null, false 
                },
                {
                    new Person("Jane", "doe", 1234),
                    new Person("Jane", "doe", 1234), 
                    true 
                },
                {
                    new Person("Jane", "doe", 1234),
                    new Person("Jack", "doe", 1234), 
                    false 
                },
... (more variations of the above going through each case)
        });
    }

    private final Person expected;
    private final Person actual;
    private final boolean expectedResult;

    public PersonEqualsTest(Person expected, Person actual, boolean expectedResult) {
        this.expected = expected;
        this.actual = actual;
        this.expectedResult = expectedResult;
    }

    @Test
    public void testEqualsObject() {
        assertEquals("expected: " + expected.toString() + " but was " + actual, expectedResult, expected.equals(actual));
    }

}

If I need to add a new way to test the equals method, I can just add a new array object to the data method. Very quick and easy, and makes testing a method with a high cyclomatic complexity, much more simple. This works really well when using Mock objects with JMock or EasyMock and testing multiple results.

There are a few limitations to this approach. First, you can only test one method at a time. If you try doing multiple tests, you will run each test as many times as you have objects in your data array. Second, the results can be hard to read. It is very important to have good error messages in your assertions, otherwise tracking down which test broke can be difficult. Finally, it is easy over use these tests. Like all new technologies, it is easy to try and use this test case for all tests (this is a bad idea). Find out what makes sense for you and go from there.

Please let me know if you have any questions, or clarifications needed for this post, as I will be happy to help.

Jon