iobservable<ievent<t>> with robocode

I've been working on a robocode robot and in my last post worked on the robot engine having successfully enabled subscription to an observable from the rx framework.
This post continues with that work and enables a subscription to turn 50 to pass an acceptance test I set up in an earlier post.

I'm going back to the Initialise() call on the robot engine and add in some code on my specification set up (new code is highlighted below):

    public class with_a_robot_engine
    {
        Establish context = () =>
        {
            statisticsManager = MockRepository.GenerateStub<IManageStatistics>();
            observer = MockRepository.GenerateStub<ImAnObserver>();               <---
            robotEngine = new RobotEngine(statisticsManager, observer);           <---
            robot = MockRepository.GenerateStub<ImARobot>();
        };

        protected static ImARobotEngine robotEngine;
        protected static ImARobot robot;
        protected static IManageStatistics statisticsManager;
        protected static ImAnObserver observer;                                   <---
    }

I'm creating a stub of a new interface "ImAnObserver" and have added a parameter on the robot engine constructor.
Later on I envsion this being an IEnumerable<ImAnObserver> but with YAGNI, I only need the one instance for now.
I can now add a new assertion at the bottom of my specification:

    public class when_initialising_a_robot_engine : with_a_robot_engine
    {
        Because of = () => robotEngine.Initialise(robot);

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

        It should_ensure_that_we_have_something_to_observe_the_start_of_each_turn = () =>
            robotEngine.AtTheStartOfEachTurn.ShouldNotBeNull();

        It should_ask_the_observer_to_observe = () =>
            observer.AssertWasCalled(o => o.Observe(robotEngine));
    }

I'm now asserting that this observer was told to observe, and I add that method to the interface.
This specification fails, so I can implement this in the robot engine with a few additional lines:

    public class RobotEngine : ImARobotEngine
    {
        readonly IManageStatistics statisticsManager;
        readonly ImAnObserver observer;

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

        public void Initialise(ImARobot robot)
        {
            statisticsManager.EnsureWeStartWithCleanStatistics();
            CreateObservableForTheStartOfEachTurnFromTheRobot(robot);
            observer.Observe(this);
        }

        void CreateObservableForTheStartOfEachTurnFromTheRobot(ImARobot robot)
        {
            AtTheStartOfEachTurn = Observable.FromEvent<StatusAtTheStartOfATurn>(
                                             ev => robot.StartOfEveryTurn += ev,
                                             ev => robot.StartOfEveryTurn -= ev);            
        }

        public IObservable<IEvent<StatusAtTheStartOfATurn>> AtTheStartOfEachTurn 
                                                            { get; private set; }
    }

I've now hooked in my observer and can move onto creating a specification for a concrete version. I've already got an IOC container that resolves all types to their implemented interfaces, so I just need the type to be available for it to roll in at runtime.
The new BattleReachedAReasonableTurnObserverSpecs.cs specification is:

    public class when_at_the_start_of_turn_fifty
    {
        Establish context = () =>
        {
            statusAtTheStartOfTheTurn = new StatusAtTheStartOfATurn { TurnNumber = 50 };
            statusAtTheStartOfATurnEvent = MockRepository.GenerateStub
                                                      <IEvent<StatusAtTheStartOfATurn>>();
            statusAtTheStartOfATurnEvent.Stub(s => s.EventArgs)
                                        .Return(statusAtTheStartOfTheTurn);
            startOfTheTurnObservable = Observable.Return(statusAtTheStartOfATurnEvent);

            robotEngine = MockRepository.GenerateStub<ImARobotEngine>();
            robotEngine.Stub(e => e.AtTheStartOfEachTurn).Return(startOfTheTurnObservable);
            observer = new BattleReachedAReasonableTurnObserver();
        };

        Because of = () => observer.Observe(robotEngine);

        It should_subscribe_to_the_observable_and_recognise_turn_fifty = () =>
            observer.ReachedTurnFifty.ShouldBeTrue();

        static StatusAtTheStartOfATurn statusAtTheStartOfTheTurn;
        static IEvent<StatusAtTheStartOfATurn> statusAtTheStartOfATurnEvent;
        static IObservable<IEvent<StatusAtTheStartOfATurn>> startOfTheTurnObservable;
        static ImARobotEngine robotEngine;
        static BattleReachedAReasonableTurnObserver observer;
    }

In this specification I'm creating a new StatusAtTheStartOfATurn event argument with turn fifty and am returning this on a stubbed IEvent<StatusAtTheStartOfATurn> instance.
I then create a new observable that will return this stub and set up a RobotEngine stub to return this observable.
My assertion is performed on a public property of the concrete observer where ReachedTurnFifty should be true.
The spec fails, so I now provide the implementation in basic form:

    public class BattleReachedAReasonableTurnObserver : ImAnObserver
    {
        public void Observe(ImARobotEngine robotEngine)
        {
            robotEngine.AtTheStartOfEachTurn.Subscribe(start => ReachedTurnFifty = true);
        }

        public bool ReachedTurnFifty { get; private set; }
    }

This passes my specification, so I now want to put some edge cases in for this:

    public class when_at_the_start_of_turn_fifty : with_an_observer
    {
        Establish context = () =>
        {
            statusAtTheStartOfTheTurn = new StatusAtTheStartOfATurn {TurnNumber = 50};
            statusAtTheStartOfATurnEvent.Stub(s => s.EventArgs)
                                        .Return(statusAtTheStartOfTheTurn);
            startOfTheTurnObservable = Observable.Return(statusAtTheStartOfATurnEvent);
            robotEngine.Stub(e => e.AtTheStartOfEachTurn).Return(startOfTheTurnObservable);
        };

        It should_subscribe_to_the_observable_and_recognise_turn_fifty = () =>
            observer.ReachedTurnFifty.ShouldBeTrue();
    }

    public class when_at_the_start_of_turn_fourty_nine : with_an_observer
    {
        Establish context = () =>
        {
            statusAtTheStartOfTheTurn = new StatusAtTheStartOfATurn { TurnNumber = 49 };
            statusAtTheStartOfATurnEvent.Stub(s => s.EventArgs)
                                        .Return(statusAtTheStartOfTheTurn);
            startOfTheTurnObservable = Observable.Return(statusAtTheStartOfATurnEvent);
            robotEngine.Stub(e => e.AtTheStartOfEachTurn).Return(startOfTheTurnObservable);
        };

        It should_subscribe_to_the_observable_but_not_have_recognised_turn_fifty = () =>
            observer.ReachedTurnFifty.ShouldBeFalse();
    }

    public class when_starting_at_turn_fifty_one_and_fifty_never_happened : with_an_observer
    {
        Establish context = () =>
        {
            statusAtTheStartOfTheTurn = new StatusAtTheStartOfATurn { TurnNumber = 51 };
            statusAtTheStartOfATurnEvent.Stub(s => s.EventArgs)
                                        .Return(statusAtTheStartOfTheTurn);
            startOfTheTurnObservable = Observable.Return(statusAtTheStartOfATurnEvent);
            robotEngine.Stub(e => e.AtTheStartOfEachTurn).Return(startOfTheTurnObservable);
        };

        It should_subscribe_to_the_observable_but_not_have_recognised_turn_fifty = () =>
            observer.ReachedTurnFifty.ShouldBeFalse();
    }

    public class with_an_observer
    {
        Establish context = () =>
        {
            statusAtTheStartOfATurnEvent = MockRepository.GenerateStub
                                                <IEvent<StatusAtTheStartOfATurn>>();
            robotEngine = MockRepository.GenerateStub<ImARobotEngine>();
            observer = new BattleReachedAReasonableTurnObserver();
        };

        Because of = () => observer.Observe(robotEngine);

        protected static StatusAtTheStartOfATurn statusAtTheStartOfTheTurn;
        protected static IEvent<StatusAtTheStartOfATurn> statusAtTheStartOfATurnEvent;
        protected static IObservable<IEvent<StatusAtTheStartOfATurn>> startOfTheTurnObservable;
        protected static ImARobotEngine robotEngine;
        protected static BattleReachedAReasonableTurnObserver observer;
    }

This changes the implementation to:

    public class BattleReachedAReasonableTurnObserver : ImAnObserver
    {
        public void Observe(ImARobotEngine robotEngine)
        {
            robotEngine.AtTheStartOfEachTurn.Where(s => s.EventArgs.TurnNumber == 50)
                                            .Subscribe(s => ReachedTurnFifty = true);
        }

        public bool ReachedTurnFifty { get; private set; }
    }

I now have an observer that reacts to turn fifty and I will now be able to write my statistics out to a file. My plan in the future is to have a statistics state that is updated by various observers and finally written at the end of the battle. But for now with the YAGNI principle in mind, I'm just going to inject the statistics manager into the observer. I add to the set up:

    statisticsManager = MockRepository.GenerateStub<IManageStatistics>();
    observer = new BattleReachedAReasonableTurnObserver(statisticsManager);

I can then use this stub to assert against:

    public class when_at_the_start_of_turn_fifty : with_an_observer
    {
        Establish context = () =>
        {
            statusAtTheStartOfTheTurn = new StatusAtTheStartOfATurn {TurnNumber = 50};
            statusAtTheStartOfATurnEvent.Stub(s => s.EventArgs).Return(statusAtTheStartOfTheTurn);
            startOfTheTurnObservable = Observable.Return(statusAtTheStartOfATurnEvent);
            robotEngine.Stub(e => e.AtTheStartOfEachTurn).Return(startOfTheTurnObservable);
        };

        It should_subscribe_to_the_observable_and_recognise_turn_fifty = () =>
            observer.ReachedTurnFifty.ShouldBeTrue();

        It should_ask_the_statistics_manager_to_write_the_statistics = () =>
            statisticsManager.AssertWasCalled(m => m.WriteStatistics(true));
    }

And I get this to pass with:

    public class BattleReachedAReasonableTurnObserver : ImAnObserver
    {
        readonly IManageStatistics statisticsManager;

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

        public void Observe(ImARobotEngine robotEngine)
        {
            robotEngine.AtTheStartOfEachTurn.Where(s => s.EventArgs.TurnNumber == 50)
                       .Subscribe(s =>
                               {
                                   ReachedTurnFifty = true;
                                   statisticsManager.WriteStatistics(ReachedTurnFifty);
                               });
        }

        public bool ReachedTurnFifty { get; private set; }
    }

Finally I write a specification for the statistics manager and refactor the original specification which was detailed in a previous post. I'm using Machine.Specification behaviours to share common assertions between the two specifications. The new specification is at the bottom:

    public class when_told_to_ensure_that_we_start_with_clean_statistics : 
                                                     with_a_statistics_manager
    {
        Because of = () => statisticsManager.EnsureWeStartWithCleanStatistics();

        Behaves_like<a_statistics_manager_that_writes_statistics> 
                              a_statistics_manager_that_writes_statistics;

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

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

            statisticsManager = new StatisticsManager(statisticsWriter);
        };

        static Action<XElement> RecordStatisticsPassedToWriter = x => 
                                                  statisticsPassedToWriter = x;
    
        protected static XElement statisticsPassedToWriter;
        protected static IManageStatistics statisticsManager;
        protected static IWriteStatistics statisticsWriter;
    }

    [Behaviors]
    public class a_statistics_manager_that_writes_statistics
    {
        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());

        protected static IWriteStatistics statisticsWriter;
        protected static XElement statisticsPassedToWriter;
    }

    public class when_asked_to_write_statistics : with_a_statistics_manager
    {
        Establish context = () => reachedTurnFifty = true;

        Because of = () => statisticsManager.WriteStatistics(reachedTurnFifty);

        Behaves_like<a_statistics_manager_that_writes_statistics> 
                              a_statistics_manager_that_writes_statistics;

        It should_contain_reached_turn_fifty_statistic_with_a_true_value = () =>
            statisticsPassedToWriter.Element(StatisticsManager.ReachedTurn50NodeName)
                                    .Value.ShouldEqual("true");

        static bool reachedTurnFifty;
    }

When asked to write statistics, I'm ensuring it writes out true to the xml file and my behaviours add the additional It delegates. Here's what that looks like in the test runner:

behaviours

The implementation of the statistics manager is now changed to make these specifications pass:

    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()
        {
            XElement statistics = GetStatisticsElementWithReachedTurnFiftyValueOf(false);
            statisticsWriter.WriteStatistics(statistics);
        }

        public void WriteStatistics(bool reachedTurnFifty)
        {
            XElement statistics = GetStatisticsElementWithReachedTurnFiftyValueOf(
                                                                          reachedTurnFifty);
            statisticsWriter.WriteStatistics(statistics);
        }

        XElement GetStatisticsElementWithReachedTurnFiftyValueOf(bool reachedTurnFifty)
        {
            var statistics = new XElement(StatisticsNodeName);
            statistics.Add(new XElement(ReachedTurn50NodeName, reachedTurnFifty.ToString()
                                                                               .ToLower()));
            statistics.Add(new XElement(StatisticsWrittenNodeName, GetCurrentDateTime()));
            return statistics;
        }

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

So I'm now finished and ready to run my acceptance test. As a reminder here is the acceptance test where it reads an external file once the battle is complete:

    public class in_a_one_round_battle_that_the_slayer_robot_takes_part_in : acceptance_test
    {
        Establish context = () => battleName = "in_a_one_round_battle";

        It should_not_disable_the_robot_at_the_start_of_battle = () =>
            robotStatistics.ReachedTurn50.ShouldBeTrue();
    }

When I run it - the acceptance test is successful and I'm all done on this piece of work, ensuring my robot is never disabled at the start of a battle.
success



0 comments: