Oct 13, 2012

Last.fm open source commons libraries

We recently open-sourced a few our our common libraries at Last.fm. This was primarily because we had a number of larger projects that we wanted to make public such as Moji - our Java MogileFS client. These projects have a dependencies on convenience classes in our common libraries. Rather than duplicate these classes we decided to also make the effort to open source the libraries themselves.

These libraries contain a few classes that I find particularly useful when writing tests:

lastcommons-lang

This library contains a nice system clock abstraction named (unsurprisingly) 'Clock' which was inspired by this StackOverflow answer. A clock instance allows you to get access to various forms of the current time. So far, millis, nanos, java.util.Datejava.util.Calendar, and org.joda.time.DateTime instances are supported. The class does seem too simple to be of any value - after all it just delegates to some standard Java API methods. However, the real power of the abstraction comes when unit testing time dependent code that you may have.

In your unit tests you now have the ability to swap out the actual system clock instance with a mock or a stub and take control of the 'current time' - at least as far as the class under test is concerned. This can be very useful as time dependent tests that use the system clock directly can often suffer from the following problems:

Long running tests
You may have some code that should change some state once a minute has elapsed. If using the system clock directly you might call Thread.sleep() to let allow the system clock to advance:

  myClass.initialise();
  Thread.sleep(TimeUnit.MINUTES.toMillis(1));
  myClass.changeState();
  assertThat(myClass.getState(), is(/* changed state */));

This will work, but will also result in a test that take no less than one minute to execute. With more tests such delays are compounded and we might end up with a test suite that takes some considerable time to execute. We want tests to be fast so that they are unnoticeable and easy to run. With a mockable clock we could instead write the following and be free from long delays:

  when(mockClock.currentTimeMillis()).thenReturn(
    0L, TimeUnit.MINUTES.toMillis(1));
  myClass.setClock(mockClock);
  myClass.initialise();
  myClass.changeState();
  assertThat(myClass.getState(), is(/* changed state */));

Delays in tests are brittle
You might have another test on a class that should only change state if more than 5 seconds but less than 10 seconds has passed. The problem here is that Thread.sleep() does not guarantee the exact duration of the delay. If your machine is busy the system clock might easily tick by 5 seconds before your thread on which your test is running is rescheduled and your assertion will fail. I have seen this frequently with tests running on continuous-integration machines - tests may run fine for months but if the CI machine is particularly busy, brittle time dependent tests will suddenly start failing. As shown in the previous example - a clock abstraction can removed the need for such delays.

The future
You may wish to test the class for future moments in time and unless you have a time machine - this just isn't practical with the system clock alone. However, you can set a mock or stub clock to whatever time you wish.

lastcommons-test

We really like using JUnit's TemporaryFolder rule for cleanly managing temporary folder and files in our tests. I automatically handles the tear down of the temp fails and removes the need for unsightly path strings in the test code, which in the worst case often end up in a shared constant somewhere. We applied a similar approach to the handling of data files for tests using a DataFolder abstraction. Assuming our standard Maven project layout, implementations allow simple, clean and scoped access to test data. The data folders also allow us to organise test data by test class or even test method without having to worry about the underlying file path. Some examples:

Top level data folder

  public class MyTest {

    @Rule
    public DataFolder dataFolder = new RootDataFolder();

    @Test
    public void myTestMethod() throws IOException {
      File actualFolder = dataFolder.getFolder();
      // Path: ./src/test/data
      ...

Top level data folder with children

  public class MyMp3Test {

    @Rule
    public DataFolder dataFolder
      = new RootDataFolder("mp3", "128K", "clean");

    @Test
    public void myTestMethod() throws IOException {
      File actualFolder = dataFolder.getFolder();
      // Path: ./src/test/data/mp3/128k/clean
      ...

Per-class data folder

  public class MyTest {

    @Rule
    public DataFolder dataFolder = new ClassDataFolder();

    @Test
    public void myTestMethod() throws IOException {
      File actualFolder = dataFolder.getFolder();
      // Path: ./src/test/data/fm/last/project/MyTest
      ...

Per-method data folder

  public class MyTest {

    @Rule
    public DataFolder dataFolder = new MethodDataFolder();

    @Test
    public void myTestMethod() throws IOException {
      File actualFolder = dataFolder.getFolder();
      // Path: ./src/test/data/fm/last/project/MyTest/myTestMethod

Wrap up

Hopefully you may find these libraries or the concepts presented here useful in your own tests.

No comments:

Post a Comment