writing the statistics file

In my previous post I'd set up my acceptance test to read an xml statistic file to establish whether the robot reached turn 50 in the acceptance test battle.

I now need to start on the implementation of writing this statistics file and I start with a specification for my as yet non-existent RobotEngine class.
In a very early post I had described how the SlayerRobot calls the InitialiseMethod, passing itself in (to an abstracted interface of ImARobot) and this is where I start:

    public class when_initialising_a_robot_engine
    {
        Establish context = () =>
        {
            statisticsManager = MockRepository.GenerateStub<IManageStatistics>();                                    
            robotEngine = new RobotEngine(statisticsManager);
            robot = MockRepository.GenerateStub<ImARobot>();
        };

        Because of = () => robotEngine.Initialise(robot);

        It should_tell_the_statistics_writer_to_ensure_we_have_clean_statistics = () =>
            statisticsManager.AssertWasCalled(s => s.EnsureWeStartWithCleanStatistics());

        static ImARobotEngine robotEngine;
        static ImARobot robot;
        static IManageStatistics statisticsManager;
    }

I'm creating a stubbed statistics manager against a new interface "IManageStatistics" and I want the robot engine to ensure that we start with some clean statistics - effectively resetting the values.
I implement this very easily:

    public class RobotEngine : ImARobotEngine
    {
        readonly IManageStatistics statisticsManager;

        public RobotEngine(IManageStatistics statisticsManager)
        {
            this.statisticsManager = statisticsManager;
        }

        public void Initialise(ImARobot robot)
        {
            statisticsManager.EnsureWeStartWithCleanStatistics();
        }
    }

I can now move onto the statistics manger specification:

    public class when_told_to_ensure_that_we_start_with_clean_statistics
    {
        Establish context = () =>
        {
            statisticsWriter = MockRepository.GenerateStub<IWriteStatistics>();
            statisticsManager = new StatisticsManager(statisticsWriter);
        };

        Because of = () => statisticsManager.EnsureWeStartWithCleanStatistics();

        It should_tell_the_statistics_writer_to_write_some_statistics = () =>
            statisticsWriter.AssertWasCalled(w => w.WriteStatistics(Arg<XElement>.Is.NotNull));

        static IManageStatistics statisticsManager;
        static IWriteStatistics statisticsWriter;
    }

I've abstracted the writing of the file to a call where the statistics manager will tell the writer to write an XElement it has built up. The implementation is trivial, so I'll move onto the next set of specifications - creating the XElement.
Here's the xml file I want to replicate:

xmlStatistics

    public class when_told_to_ensure_that_we_start_with_clean_statistics
    {
        Establish context = () =>
        {
            statisticsWriter = MockRepository.GenerateStub<IWriteStatistics>();
            statisticsWriter.Stub(w => w.WriteStatistics(null))
                            .IgnoreArguments()
                            .Do(RecordStatisticsPassedToWriter);
            statisticsPassedToWriter = null;

            statisticsManager = new StatisticsManager(statisticsWriter);
        };

        Because of = () => statisticsManager.EnsureWeStartWithCleanStatistics();

        It should_tell_the_statistics_writer_to_write_some_statistics = () =>
            statisticsWriter.AssertWasCalled(w => w.WriteStatistics(Arg<XElement>.Is.NotNull));

        It should_start_statistics_with_a_statistics_node = () =>
            statisticsPassedToWriter.Name.ToString()
                                         .ShouldEqual(StatisticsManager.StatisticsNodeName);

        It should_contain_the_written_time_statistic_with_the_current_date_and_time = () =>
            statisticsPassedToWriter.Element(StatisticsManager.StatisticsWrittenNodeName)
                                    .Value.ShouldEqual(DateTime.Now.ToShortDateString() + " " 
                                                        + DateTime.Now.ToShortTimeString());

        It shouild_contain_reached_turn_fifty_statistic_with_a_false_value = () =>
            statisticsPassedToWriter.Element(StatisticsManager.ReachedTurn50NodeName)
                                    .Value.ShouldEqual("false");

        static Action<XElement> RecordStatisticsPassedToWriter = x =>
                                                    statisticsPassedToWriter = x;

        static IManageStatistics statisticsManager;
        static IWriteStatistics statisticsWriter;
        static XElement statisticsPassedToWriter;
    }

By recording the XElement instance that is sent in the call to the writer I can assert against it in my specifications and I line them up with the xml file.
Here's the implementation:

    public class StatisticsManager : IManageStatistics
    {
        public static readonly string StatisticsNodeName = "statistics";
        public static readonly string ReachedTurn50NodeName = "reachedTurn50";
        public static readonly string StatisticsWrittenNodeName = "statisticsWritten";

        IWriteStatistics statisticsWriter;

        public StatisticsManager(IWriteStatistics statisticsWriter)
        {
            this.statisticsWriter = statisticsWriter;
        }

        public void EnsureWeStartWithCleanStatistics()
        {
            var statistics = new XElement(StatisticsNodeName);
            statistics.Add(new XElement(ReachedTurn50NodeName, "false"));
            statistics.Add(new XElement(StatisticsWrittenNodeName, GetCurrentDateTime()));

            statisticsWriter.WriteStatistics(statistics);
        }

        string GetCurrentDateTime()
        {
            return DateTime.Now.ToString("dd/MM/yyyy HH:mm");
        }
    }

I create a new XElement and fill it with the appropriate child elements and content.
I use readonly static members because I want to use these in my test assembly and I can ensure they don't get embedded into the call-site (if it were a constant).

The next step for statistics is to implement the statistics writer directly without specification:

    public class StatisticsWriter : IWriteStatistics
    {
        public static readonly string StatisticsFileLocation = 
            @"C:\dev\Projects\RoboCodeWars\TeamSlayer\Specifications\AcceptanceSpecs\
                                                battles\results\Statistics.xml";

        public void WriteStatistics(XElement statisticsElement)
        {
            statisticsElement.Save(StatisticsFileLocation);
        }
    }

I'm now ready to hook this into my robot. If you've followed previous posts you may remember I use a service locator to abstract autofac from my SlayerRobot in the adapter layer.
The robot call looked like this (where ServiceLocator is a property on the robot and Initialise() currently returns null and causes the exception my acceptance test is trying to prevent):

        public override void Run()
        {
            ImARobotEngine robotEngine = ServiceLocator.ProvideRobotEngine();            
            robotEngine.Initialise(this);
        }

So I need a specification for my non existent service locator

    public class when_told_to_provide_a_robot_engine
    {
        Establish context = () => serviceLocator = new ServiceLocator();

        Because of = () => robotEngine = serviceLocator.ProvideRobotEngine();

        It should_return_a_valid_robot_engine = () => robotEngine.ShouldNotBeNull();

        static ILocateServices serviceLocator;
        static ImARobotEngine robotEngine;
    }

And the implementation now hooks everything up:

    public class ServiceLocator : ILocateServices
    {
        IContainer _container;

        public ServiceLocator()
        {
            RegisterEverything();
        }

        void RegisterEverything()
        {
            var builder = new ContainerBuilder();

            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
                   .AsImplementedInterfaces();

            _container = builder.Build();
        }

        public ImARobotEngine ProvideRobotEngine()
        {
            return _container.Resolve<ImARobotEngine>();
        }
    }

When I run my acceptance test that runs a battle I now get a test failure where the value of reachedTurn50 is false - not because my robot threw an exception, but because I am explicitly writing clean statistics with the false value.

The next few posts will delve into writing the new statistics when the robot runs and reaches turn 50, hopefully then passing my acceptance test



0 comments: