Saturday, January 15, 2011

Working (around) with MSpecs

I just started playing with Machine.Specifications (MSpecs). Overall I am very satisfied with the project but I did ran across some issues - dont get me wrong, it works great but it was more like limitations because I was lame and using Express edition.
My specs are simple - I am trying to create a Dependency Injection Container that does absolutely very little, not as powerful as Unity or any other containers. I dont plan to use it anywhere - just am trying to create one for fun. So the specifications are simple (for now).
 
Mapping Interface to Type, MappingInterfaceToType
  » should allow creating instance of type registered
  
  In case of multiple constructors, pick the one with injection attribute, MultipleConstructorScenario
  » should create instance whose Value property is 20
  
  Container should support singleton instances, SingletonScenario
  » should return the same instance for every invocation
  
  Inner container should be supported, SupportForInnerContainer
  » should return registration from parent and itself


Simple right? Those are the output for my specifications. It it still under works but the output itself explains a lot - thats the power of the MSpecs. Business Analysts write requirements, gives specifications - This can be changed - they give us the requirements and we "program" the specifications. I know it appears to be some rewired Unit Testing, but if unit tests can give me a bunch of specifications and tell me what failed, I would be more than happy to use that.


Now lets write a specification from scratch. Look at the following simple C# code.
 public class Singleton { }
        public class Transient
        {
            public Singleton SingletonInstance { get; private set; }
            public Transient(Singleton ins)
            {
                SingletonInstance = ins;
            }
        }

I would like to register the Singleton class as - singleton and the transient class as - transient. So my specification is, instances of transient whose dependency is a singleton should get the same instance. So that becomes my "Subject"


[Subject("instances of transient whose dependency is a singleton should get the same instance.")]
public class InjectionOfSingletonForTransientResolution
{
}

Now to verify that specification, I need to "establish a context" which is that I need my container to be ready for use.
 Establish context = () =>
 {
            _builder = new TypeBuilder();
 };

Now that context is established, I will just write my specification. I create two instances and both my instances should have the same instance of Singleton.
It Should_Use_The_Same_Instance_when_creating_dependent_Components = () =>
{
            var instance1 = _builder.Resolve();
            var instance2 = _builder.Resolve();
            instance1.ShouldNotEqual(instance2);
            instance1.SingletonInstance.ShouldNotBeNull();
            instance2.SingletonInstance.ShouldNotBeNull();
            instance1.SingletonInstance.ShouldEqual(instance2.SingletonInstance);
};

But you know this would not happen just like that. I have my context and I know what it should do. I need to tell it why it should do that - that specification would pass "because I am registering dependency as singleton and instance as transient". So I add my reasons on why (or when) the specification would pass.
Because I_Am_Making_A_Dependency_Registration_As_Singleton_And_TestSubject_As_Transient = () =>
{
            _builder.Register(new Singleton());
            _builder.Register();
};

The whole specification would look like shown below.
[Subject("instances of transient whose dependency is a singleton should get the same instance.")]
    public class InjectionOfSingletonForTransientResolution
    {
        static ITypeBuilder _builder;

        public class Singleton { }
        public class Transient
        {
            public Singleton SingletonInstance { get; private set; }
            public Transient(Singleton ins)
            {
                SingletonInstance = ins;
            }
        }

        Establish context = () =>
        {
            _builder = new TypeBuilder();
        };

        Because I_Am_Making_A_Dependency_Registration_As_Singleton_And_TestSubject_As_Transient = 
        () =>
        {
            _builder.Register(new Singleton());
            _builder.Register();
        };

        It Should_Use_The_Same_Instance_when_creating_dependent_Components = () =>
        {
            var instance1 = _builder.Resolve();
            var instance2 = _builder.Resolve();
            instance1.ShouldNotEqual(instance2);
            instance1.SingletonInstance.ShouldNotBeNull();
            instance2.SingletonInstance.ShouldNotBeNull();
            instance1.SingletonInstance.ShouldEqual(instance2.SingletonInstance);
        };
    }

When I first run this my specification failed because my container does not handle that yet.
 instances of transient whose dependency is a singleton should get the same instance., InjectionOfSingletonForTransientResolution
  » Should Use The Same Instance when creating dependent Components (FAIL)
  Machine.Specifications.SpecificationException: Should be [not null] but is [null]
     at Machine.Specifications.ShouldExtensionMethods.ShouldNotBeNull(Object anObject) in d:\BuildAgent-03\work\38fe83de684fd902\Source\Machine.Specifications\ExtensionMethods.cs:line 181
     at Analytics.Specifications.Container.InjectionOfSingletonForTransientResolution.<.ctor>b__2() in C:\Users\bhargav\documents\visual studio 2010\Projects\Analytics\Analytics.Specifications\Container\TypeBuilderSpecs.cs:line 159
     at Machine.Specifications.Model.Specification.InvokeSpecificationField() in d:\BuildAgent-03\work\38fe83de684fd902\Source\Machine.Specifications\Model\Specification.cs:line 75
     at Machine.Specifications.Model.Specification.Verify() in d:\BuildAgent-03\work\38fe83de684fd902\Source\Machine.Specifications\Model\Specification.cs:line 53

I fix my container and now I see my specification pass.

One thing to remember is what ever you are working off - your context - it should be static - otherwise the compilation would fail. when looking at others examples, I had a similar question - so here I am telling you upfront.

Running specifications without leaving Visual Studio - No test runners are required.


All my specifications are in a separate class library. I would like to run the specifications without leaving Visual Studio (btw, I am using Express). I remember once upon a time I could make a class library as a startup project and somehow linked NUnit gui runner with the project. I could not get it done with VC# 2010 Express anymore. May be I am missing something. Anyway the solution was to add the following Post-Build event

image

Once my specifications project it built successfully, it automatically generates a nice output. You can use all command line arguments that mspec supports, for now I only care if they pass or fail, hence the simple one.

Debugging my specifications. It was painful


I tried all different strategies like using System.Diagnostics.Debugger.Break() and what not. ConsoleRunner that comes with mspec (mspec.exe) was crashing complaining that it encountered an user defined breakpoint in the code - yeah that was my intention. Anyway to work around that, I create a console program and in the console application, I added reference to my specifications library and from the Git Hub source code for the mspec.exe (thanks to OSS) the following C# code helped me overcome my limitation of not being able to use R# or TestDriven.Net.
 class ContainerSpecsRunner
    {
        static void Main(string[] args)
        {
            //Console.WriteLine(typeof(ITest).Assembly.Location);
            Program prog = new Program(new DefaultConsole());
            prog.Run(new[] { typeof(ITest).Assembly.Location });
        }
    }

ITest is a type that was defined in my specifications assembly. That way I need not worry about any arguments or hardcode path of my assembly. By the way, at work you might be killed for doing this, I am just doing it at my personal projects.

Some samples?


Ok the biggest problem I had was that I could not find some real world examples, the GIT HUB structure of the project was confusing as hell. I was hoping to find some examples, but I could not. As much as I like the project, I hate to see so little or almost non-existent guidance for new users. So here are some of my specifications. These are written by me - I am just learning the style of specs so forgive me if they are not what you wanted them to be. I am just trying to help.
public interface ITest{
    }

    public class Test: ITest{
        [Injector]
        public Test()
        {

        }
    }

    [Subject("Mapping Interface to Type")]
    public class MappingInterfaceToType
    {
        static ITypeBuilder _builder;

        Establish context = () =>
        {
            _builder = new TypeBuilder();
        };

        Because of = () =>
        {
            _builder.Register();
        };

        It should_allow_creating_instance_of_type_registered = () =>
        {
            var resolvedObject = _builder.Resolve();
            resolvedObject.ShouldNotBeNull();
            typeof(Test).ShouldEqual(resolvedObject.GetType()); 
        };
    }

    [Subject("In case of multiple constructors, pick the one with injection attribute")]
    public class MultipleConstructorScenario
    {
        public class Test2 : ITest
        {
            public int Value { get; private set; }
            public Test2(ITest demo)
            {
                Value = 10;
            }

            [Injector]
            public Test2()
            {
                Value = 20;
            }
        }

        static ITypeBuilder _builder;
        Establish context = () => {
            _builder = new TypeBuilder();
        };

        Because of = () =>
        {
            _builder.Register();
        };

        It should_create_instance_whose_Value_property_is_20 = ()=>
{
            var instance = _builder.Resolve();
            instance.ShouldBeOfType();
            (instance as Test2).Value.ShouldEqual(20);
        };
    }

    [Subject("Container should support singleton instances")]
    public class SingletonScenario
    {
        static ITypeBuilder _builder;

        Establish context = () =>
        {
            _builder = new TypeBuilder();
        };

        Because instance_Is_Registered = () =>
        {
            _builder.Register(()=>new Test());
        };

        It should_return_the_same_instance_for_every_invocation = () =>
        {
            _builder.Resolve().ShouldEqual(_builder.Resolve());
        };
    }

    [Subject("Inner container should be supported")]
    public class SupportForInnerContainer
    {
        static ITypeBuilder _builder;
        static ITypeBuilder _child;
        Establish context = () =>
        {
            _builder = new TypeBuilder();
            _child = _builder.CreateChildBuilder();
        };

        Because instance_is_registered_with_child_container = () =>
        {
            _builder.Register(new int[] { 0, 1, 2 });
            _child.Register();
        };

        It should_return_registration_from_parent_and_itself = () =>
        {
            var arr = (int[])_child.Resolve();
            arr.SequenceEqual(new[] { 0, 1, 2 }).ShouldBeTrue();
            _child.Resolve().ShouldBeOfType();
        };
    }


Would be nice if it can print my "Because" field names


Just like the console runner prints my "It" fields, it would be nicer if my reasons are printed. I will see if i can do that myself to the project and may contribute a little.


I hope the examples are useful. By no means they are perfect but they can get you started. You can see in the example I detailed in the beginning, I had a whole bunch of Should statements = that is plain wrong. Each specification should define one thing - otherwise it would be a big mess. Please look at this great project and I really am in love with MSpec.

3 comments:

Andy said...

For debugging your specs, I recommend using TestDriven.Net or Resharper's built in MSpec runners. These allow you to right click on an individual test and "Run with Debugger".

Bhargav-------------------------------------------- said...

@Andy - I know about the runners. I thought it was pretty good but I play on my laptop most of the times and i dont have a VS 2010 setup. I use C# Express edition and you know they dont support addins.

POS Equipment said...

Thanks for sharing this informative information. I love your blog! You will be in our prayers and thoughts!