wrapping up the battle runner for robocode

In my last post I had written the parsing of the results file and I have a few things left to do to wrap this piece of development.

The first thing is a concrete version of the IBattleResultsFileReader where I abstracted the reading of the file and producing a string array.
This is a very simple implementation and I can drive this without testing as it is a trivial implementation using the BCL. Historically I may have been zealous in my test driven process and written an integration test, but from experience I find that if you have abstracted out to this level where the implementation is very trivial there is little to be gained from running integration tests on that code - it can be seen to work through observation. If I care about very high code coverage I may have to then exclude that class though.
The resulting code is straightforward:

namespace TeamSlayer.Battles
{
    public class BattleResultsFileReader : IBattleResultFileReader
    {
        public string[] ReadResultFrom(Uri file)
        {
            return File.ReadAllLines(file.LocalPath);
        }
    }
}

Finally I need to integrate the parsing and reading of the file into the BattleRunner, so I revisit the specification for that class (only the relevant code is shown below):

    public class when_the_battle_runner_runs_a_battle
    {
        Establish context = () =>
        {
            ...
            theExpectedResults = new List<BattleResult>();
            battleResultParser = MockRepository.GenerateStub<IBattleResultParser>();
            battleResultParser.Stub(p => p.ReadResultsFrom(null)).IgnoreArguments()
                              .Return(theExpectedResults);

            battleRunner = new BattleRunner(processRunner, battleResultParser);
            ...
        };

        Because of = () => results = battleRunner.Run(battle);

        It should_tell_the_battle_parser_to_parse_the_results_for_the_battle = () =>
            battleResultParser.AssertWasCalled(p => p.ReadResultsFrom(battle));

        It should_return_the_results_from_the_parser = () =>
            results.ShouldEqual(theExpectedResults);

        static IBattleResultParser battleResultParser;
        static IEnumerable<BattleResult> theExpectedResults;
    }

I've replaced the original observation that it should return non null results with one that expects the results from the battle result parser to be returned. I also ensure that the battle result parser is called.

Here's an interesting one though - when pairing with a colleague recently we had the above situation where I was asserting that the call is made and that the value returned from the stub is the one returned from the class under test. The discussion then centred around whether the first was implied by the second observation - the fact that the result returned is the same as the one from the stub, you surely don't need the first observation right?

In this case that is perfectly true, you could get away with just having the second, however I prefer to be explicit as they are two different observations, it just happens for the moment to be a straight pass through.

What happens if I choose to only have the second observation and at a later date I manipulate the specification and the BattleRunner so that it no longer returns the battle results but does something else with them instead? Do I then at that stage re-evaluate the specification, realise that I had this missing observation that checks for the the call to the parser and put it in at that later date?

This is too risky for me, I'd rather have both observations in from the start leaving it abundantly clear for future revision what is happening in the interaction between the two classes.

Here is the final implementation of the BattleRunner:

namespace TeamSlayer.Battles
{
    public class BattleRunner
    {
        readonly IProcessRunner processRunner;
        readonly IBattleResultParser battleResultParser;

        public BattleRunner(IProcessRunner processRunner, IBattleResultParser battleResultParser)
        {
            this.processRunner = processRunner;
            this.battleResultParser = battleResultParser;
        }

        public IEnumerable<BattleResult> Run(Battle battle)
        {
            var startInformation = new ProcessStartInfo();
            startInformation.FileName = BattleConstants.BattleFilesLocation + "run_battle.bat";
            startInformation.WorkingDirectory = BattleConstants.RobocodeWorkingDirectory;
            startInformation.Arguments = string.Format("-battle {0} -results {1}",
                                                       battle.BattleFile,
                                                       battle.BattleResultFile);

            processRunner.RunProcess(startInformation, 60);

            return battleResultParser.ReadResultsFrom(battle);
        }
    }
}



1 comments:

  1. Neil Mosafi July 23, 2010 at 11:13 PM

    Nice post. You might be interested in System.IO.Abstractions - http://systemioabstractions.codeplex.com/