Thermostat CLI

The last weeks I’ve been working on a command line interface (CLI) for Thermostat. Now the first bunch of commands are available. It’s not much yet, but the groundwork for the really useful ones is laid:

Command line interface for Thermostat

3 Commands can be seen in the above screenshot:

  • thermostat service --start (to start the thermostat agent and storage)
  • thermostat list-vms (to list the currently monitored VMs)
  • thermostat help (to print out a list of available commands and get usage information

This command line interface will make Theromstat useful in settings where the already available Swing GUI is not feasible, like when running over SSH connections on a remote machine, or for automated/scripted monitoring.

Next step will be to add some really useful commands, like getting some actual statistics and analysis about the monitored VMs, find memory leaks, deadlocks, and so on.

Advertisements

How to test drive a main method

Test driven development is all nice and dandy, but there are some areas that most people find notoriously difficult to test, and often dismiss them as not worth it. The main() method, entry point for any application, is one of them. The problem with main methods is that they combine at least two of the patterns that make testing difficult: static method calls and many constructor calls. Let me illustrate it with a snippet of code from Thermostat:

public static void main(String[] args) {
    CommandContextFactory cmdCtxFactory = CommandContextFactory.getInstance();
    CommandRegistry registry = cmdCtxFactory.getCommandRegistry();
    ServiceLoader cmds = ServiceLoader.load(Command.class);
    registry.registerCommands(cmds);
    if (hasNoArguments()) {
        runHelpCommand();
    } else {
        runCommandFromArguments();
    }
}

This is a rather common way to implement a main method: setup a bunch of things by calling factories and constructors, then launch whatever needs to be launched.

The first thing that needs to be done to make this testable is to reduce the problematic static method and constructor calls by moving them into a separate class. How does this look?

public static void main(String[] args) {
    new Launcher().run(args);
}

This way, we can now test the bulk of the functionality using fairly normal testing techniques such as mocking, injection, etc. We still want to test the actual main method though (even though many will now cry out that this is so trival that it’s not worth even thinking about it… but wait, the solution is not so difficult either).

What would need to be tested here? Obviously, there is no state change, only interactions. Therefore, the test would need to be a verification of interaction of the main class with the Launcher class. More specifically, it would need to verify that the run() method is called on the newly constructed Launcher with the correct arguments (and not, say, null). However, we need to somehow get rid of the construction itself and open a way to inject the Launcher instance. That’s how I solved it:

public class Thermostat {

    private static Launcher launcher = new Launcher();

    public static void main(String[] args) {
        launcher.run(args);
    }

    static void setLauncher(Launcher launcher) {
        Thermostat.launcher = launcher;
    }
}

This way, we can override the launcher to be tested with a mock launcher, and verify the correct interaction:

public class ThermostatTest {

    @Test
    public void testThermostatMain() {
        Launcher launcher = Mockito.mock(Launcher.class);
        Thermostat.setLauncher(launcher);
        Thermostat.main(new String[] { "test1", "test2" });
        PowerMockito.verifyNew(Launcher.class).withNoArguments();
        Mockito.verify(launcher).run(new String[] { "test1", "test2" });
     }
}

The verifyNew() call verifies that we actually create a new instance of Launcher in the static initializer of Thermostat (the main class), and the last call verifies that run() is called on our mock launcher with the correct arguments.

And if you are interested in how the rest of the launching code now looks like, and how we test it (in fairly normal unit testing style), take a look at the Launcher and LauncherTest classes.

If you have other solutions to the problem of unit testing main methods, I would be very interested in hearing from them in the comments!