Pages

Tuesday, April 19, 2011

CSpec

CSpec


It has been a long time since I posted something here. I have no free time to do my personal life and come to think of it there was nothing interesting to write about but the main reason is that I was just lazy :-). Since that time many things had changed for the better, first of all I took MS certification and passed (70-536). This was on my TODO list for at least a year now and there was always something better to do, but finally I got myself together and did it, but to my surprise I'm not an MCP :-( (I can still use the title but officially MS abandoned that title), so that's a slap to the face if you ask me.

The other cool thing is that recently Ive been doing a lot of ruby (started a company and got my hosting in rails) in my free time. Now I don't blog about that as I'm learning it so I can't provide any expert knowledge or cool code tricks, but... Ruby has a very awesome behavioral testing framework (RSpec), that's fluent and expressive. And since most of my time I work in C# (also some Java) that has your standard Unit Testing that's nowhere as awesome as the one that Ruby has, I decided that C# could use one.

Enter CSpec a behavioral, expressive and fluent testing framework for C# :-).

The main problem was that Ruby is a dynamic language and one of the most fluent and expressive so such framework is achievable but C# is static typed at heart and has some functional elements, it was obvious right away that CSpec would never be RSpec but my goal was to get as close as possible.

So in RSpec a sample code would look like:

# bowling_spec.rb
require 'bowling'

describe Bowling, "#score" do
  it "returns 0 for all gutter game" do
    bowling = Bowling.new
    20.times { bowling.hit(0) }
    bowling.score.should == 0
  end
end

Whereas the same code in CSpec looks like:

public class BowlingSpec: CSpecFacade<Bowling>
{
    public BowlingSpec()
     : base(new BowlingSpec())
    {
         //this describes the initial state of properties 
         //after initalization.
         Should(@have => ObjSpec.Score == 0);
    }

    //special delegate
    DescribeAll describe_bowling_score =
            (@it, @do) =>
            {
                @it("returns 0 for all gutter game");
                int game = 20;
                game.Times(()=>{ @do.hit(0) });
                @do.Should(@be => 0);
            };
}

There is just a little more code involved but it looks similar :-). To achieve the Should functionality I had to basically implement a parameter parser so when typing should it does actually matter what you write @be stands for equality test, but @have acts like a null check for ref types and truth check for bools.

To test the code either you need to get the instance of a test runner class and call it on the BowlingSpec or use the shell console and point it to the assembly, this will run tests and print all of the descriptions.

Now I could write on about how I did the framework and what's need to be done, but as I'm putting this thing to codeplex I'l just post the link here.

Feedback and Help is appreciated :-).

Bonus:

Apparently C# compiler has a bug in the parser as my framework pushes delegates and functional programming to it's limits ;-). I stumbled on it when I was trying to test CSpec in CSpec and in one case I had strange exceptions about the Action delegate that represents the @it tag.

private void CreateOperations()
        {
            run_on_type =
               (@it, @do) =>
               {
                   @it("Runs all of the operations contained in a type");
                   @do.RunTestOnType(myClassSpec.GetType());
                   @do.Passed.Should(@be => 3);
                   @do.Failed.Should(@be => 0);
               };
        }

The exception stated that: "Delegate Action does not take 1 arguments", but that's not true as IT takes one string argument :-), and converting the code to:

//MS aparently has a bug in the code.
//without this nothing will work and the DESCRIBE ALL delegate will throw exception.
//In mono it works perfect.
private Action<string> dummy;

private void CreateOperations()
        {
            run_on_type =
               (@it, @do) =>
               {
                   @it("Runs all of the operations contained in a type");
                   @do.RunTestOnType(myClassSpec.GetType());
                   @do.Passed.Should(@be => 3);
                   @do.Failed.Should(@be => 0);
               };
        }

Solved the problem for me, so from my point of view this looks like a bug.

No comments:

 
ranktrackr.net