more iterative context specification code

In this series I've been exploring two user stories and writing context specification driven code - I continue on from the last post where I had reworked some specifications. You can catch up on the series here.

I now want to add the observations for obtaining the new balance date from the view, so start with a new observation (new code shown in italics):

[Observations]
public class when_saving_a_new_balance_but_balance_input_is_invalid : BalancePresenterSpecification
{
    private context c = () => balanceView.Stub(v => v.NewBalanceIsValid()).Return(false);

    because b = () => sut.SaveCurrentBalance();

    [Observation]
    public void should_not_obtain_new_balance_amount_from_the_view()
    {
        balanceView.was_never_told_to(v => v.GetNewBalanceAmount());
    }

    [Observation]
    public void should_not_obtain_new_balance_date_from_the_view()
    {
        balanceView.was_never_told_to(v => v.GetNewBalanceDate());
    }
}

Here, I am observing that the new balance date is not obtained from the balance view in this context where the input is invalid. This test passes straight away... the main problem with negative assertions such as this, so in the SaveCurrentBalance method I put in some temporary code on the first line to call the GetNewBalanceDate method.
This allows me to validate that the test is correct and doesn't produce a false positive. I get a Rhino Mocks ExpectationViolationException so all is good and I remove that temporary code.

Now for the context where the balance input is valid (new code in italics):

[Observations]
public class when_saving_a_new_balance_and_balance_input_is_valid : BalancePresenterSpecification
{
    private context c = () => balanceView.Stub(v => v.NewBalanceIsValid()).Return(true);

    because b = () => sut.SaveCurrentBalance();

    [Observation]
    public void should_obtain_new_balance_amount_from_the_view()
    {
        balanceView.was_told_to(v => v.GetNewBalanceAmount());
    }

    [Observation]
    public void should_obtain_new_balance_date_from_the_view()
    {
        balanceView.was_told_to(v => v.GetNewBalanceDate());
    }
}

Here, I'm observing that when the context is valid the new balance date was obtained from the view. This test fails, which is great, so I now implement the real code:

public void SaveCurrentBalance()
{
    bool newBalanceIsValid = balanceView.NewBalanceIsValid();
    if (newBalanceIsValid)
    {
        amount = balanceView.GetNewBalanceAmount();
        date = balanceView.GetNewBalanceDate();
    }
}

I now have some code working to retrieve the data, but there are a couple of things I don't like about this. Firstly the retrieval of data is happening through method calls as opposed to properties and I really do think as it is merely data I am obtaining that they should be property "get" calls. Secondly the syntax doesn't read right: "the balance view was told to get balance amount"; this is not correct because it is the presenter that is getting the balance amount.

Now I'm not proposing that this library force my hand in implementation details such as whether to use a property method call, but I think it does add huge value by providing clarity on a shaky syntax between the two objects (presenter and the view).

So how do I go about changing this? My personal opinion is that it is important to keep the code I've just written in place - jumping in and removing bits of specification code and production code may negate some of my context-first process and leave me with holes in my code coverage. Instead I'll complement each incorrect one before then removing the old code.
I start with the interface on the view and have two new properties (new code in italics):

public interface IBalanceView
{
    void SetBalance(Balance balance);
    bool NewBalanceIsValid();
    Double GetNewBalanceAmount();
    DateTime GetNewBalanceDate();
    Double NewBalanceAmount { get; }
    DateTime NewBalanceDate { get; }
}

I can now use these in some new observations in my contexts (note, I've omitted the context and becaused delegates and the previous observations for brevity)

[Observations]
public class when_saving_a_new_balance_and_balance_input_is_valid : BalancePresenterSpecification
{
    ...(code omitted)...

    [Observation]
    public void should_obtain_new_balance_amount_from_the_view()
    {
        balanceView.was_told_to(v => v.GetNewBalanceAmount());
        balanceView.AssertWasCalled(v => { var ignored = v.NewBalanceAmount; });
    }
}

I've left the old line in place for now, but underneath it I now have an assertion that the NewBalanceAmount property was read - using an assignment in the anonymous delegate.

My test predictably fails so I now implement this code (new code in italics):

public void SaveCurrentBalance()
{
    Double amount;

    bool newBalanceIsValid = balanceView.NewBalanceIsValid();
    if (newBalanceIsValid)
    {
        amount = balanceView.NewBalanceAmount;
        balanceView.GetNewBalanceAmount();
        balanceView.GetNewBalanceDate();
    }
}

The tests pass and I can now start to remove the code that I no longer need. I comment out the line above that calls GetNewBalanceAmount() and then re-run my tests - this way I can locate which tests are affected by proposed removal. One them fails - "should_obtain_new_balance_amount_from_the_view" and this is because I had left both assertions in. I can now remove the invalid one and re-run the tests and they pass. Now I want to remove the interface contract for this on the view so I comment the line out on the interface and build the project - there is one specification that fails so I clean that up:

[Observations]
public class when_saving_a_new_balance_but_balance_input_is_invalid : BalancePresenterSpecification
{
    ...(code omitted)...

    because b = () => sut.SaveCurrentBalance();

    [Observation]
    public void should_not_obtain_new_balance_amount_from_the_view()
    {
        balanceView.AssertWasNotCalled(v => { var amount = v.NewBalanceAmount; } );
    }
}

I follow the same process for the GetBalanceDate() changes and make all the amendments step-by-step as I did above and once finished I have a clean interface, clean specifications and my tests pass.



0 comments: