Saturday, September 20, 2008

Testing the composite pattern - reusable test helpers

I face quite often the need to test a composite pattern variations. And truly was duplicating some similar code around. As tests are now my focus for self-improvement (and always, should I be eaten alive by the tdd purists if I lie), I decided to extract some of the snippets into reusable helper methods.
Here is what I got for the calling of the composite pattern "operation":

The usage:

CompositePatternTestHelper.TestForCompositeOperation<ProcessCoordinator, IProcess>(
                parent => parent.Stop(), // Parent operation
                child => child.Stop(), // Should invoke following on every child
                (parent, child) => parent.Processes.Add(child) // How to add add child to the parent
                );

And for the helper method/class implementation (The most recent version available at http://code.google.com/p/toolsdotnet/source/browse/trunk/Tools.Net/src/Tools.Tests.Helpers/CompositePatternTestHelper.cs):

using System;
using Rhino.Mocks;
 
namespace Tools.Tests.Helpers
{
    public static class CompositePatternTestHelper
    {
        /// <summary>
        /// Helper method to test composite [parent/child] pattern implementation, where calls
        /// to the parent result into calls onto its children.
        /// </summary>
        /// <remarks>Creates the parent object using its default constructor</remarks>
        public static void TestForCompositeOperation<ParentType, ChildType>(Action<ParentType> parentAction, Action<ChildType> childAction, Action<ParentType, ChildType> addChild)
            where ChildType : class
            where ParentType : new()
        {
            // Requires a default ctor to exists
            var parent = new ParentType();
 
            TestForCompositeOperation(parent, parentAction, childAction, addChild);
        }
        /// <summary>
        /// Helper method to test composite [parent/child] pattern implementation, where calls
        /// to the parent result into calls onto its children.
        /// </summary>
        /// <remarks>Uses the passed in instance of a parent</remarks>
        public static void TestForCompositeOperation<ParentType, ChildType>(ParentType parent, 
            Action<ParentType> parentAction, Action<ChildType> childAction, Action<ParentType, ChildType> addChild)
            where ChildType : class
        {
            // Use Rhino.Mocks to create stubs
            var child1 = MockRepository.GenerateStub<ChildType>();
            var child2 = MockRepository.GenerateStub<ChildType>();
            // Setup two children, the arbitrary choice, but should not really matter
            child1.Expect(childAction);
            child1.Expect(childAction);
            // Add children to the parent
            addChild(parent, child1);
            addChild(parent, child1);
            // Call parent action
            parentAction(parent);
            // Assert parent action resulted in the calls to children
            child1.AssertWasCalled(childAction);
            child1.AssertWasCalled(childAction);
        }
    }
}

7 comments:

Anonymous said...

Hi Stan, this is indeed a great post addressing a common pain. I have 2 things to point out though (I know this is a self note, but just to start a design discussion):

1 -
// Requires a default ctor to exists

var parent = new ParentType();

This could be supported by another helper which gets parent as a parameter, because for me most of the cases parent does not have a default ctor. It either gets a child, another parent instance, or the basic interface / abstract class instance to work on.

By refactoring this way we wouldn't be violating SRP principle too, since we wouldn't be intrusive on how consumer creates the parent.

2 - Since you are setting an expectation, wouldn't it be better to use GenerateMock instead of stubs ?

Thanks again, the idea is really great. Certainly goes into my common codebase ! :)

stan said...

Oh man,
do you ever sleep :)? There is a method overload just bellow the one you pointed out :). As for using GenerateStub - I'm just trying the new syntax from Rhino.Mocks as you can see at the bottom of the second method, that is more for AAA (Arrange, Act, Assert). I'm truly on the one boat with Roy Osherov on that, that it is probably a good time to forget Mock, Stub, Test Double.

stan said...

I see, you probably use some offline rss reader, it looks like you commented on something that was my first draft to publish, btw.
Ever considered Google reader? :).

Anonymous said...

Haha :) Actually I am using Google Reader, I don't know why it didn't refresh. I am having lots of bad experience with Google Reader nowadays, and am seriously considering to go back to FeedDemon :) Apologies on that.

On Stubs - Well, for the newbies it matters, but for us who already knows the meaning, we can talk about it can't we ? :)

stan said...

Look here http://ayende.com/Blog/archive/2008/05/16/Rhino-Mocks--Arrange-Act-Assert-Syntax.aspx. That is what I'm teasing around with :).

sidarok said...

:) i knew what you mean, but if you look that post there is no expectation on stubs. I think there was another discussion in another post that to even in the next versions, throw an exception to avoid to set expectations on stubs.

aha, I found it during writing : http://ayende.com/Blog/archive/2008/06/29/Rhino-Mocks-3.5-Design-Decisions-To-be-strict-or-not.aspx

stan said...

Good link btw. The point is once again, I'm with Roy Osherove on this one (http://weblogs.asp.net/rosherove/archive/2008/09/20/goodbye-mocks-farewell-stubs.aspx). So I'm exploring where AAA pushes Ayende at the moment :):). It looks like issues he is facing now with semantics is what Roy has resolved with just more practical view.