This project is read-only.

doc v 0.3 this document and other not covered here can be found in Cspec source under doc

CSpec : Quick Start

Cspec is a testing framework inspired by ruby RSpec, and more behavioral driven approach.

The main goal of this project is to have a very fluent testing framework, that can be expressive and replicate the real language as close as it can.

It uses a combination of extension methods, lambda parsing and custom delegates to replicate fluent language and behavioral pattern.

CSpec : Overview

Cspec as a framework consists of two separate components, the first one is the CSpec itself and the second one

is a TestRunner console. One can use the extension features of CSpec without the TestRunner or

even write it's own test runner using CSpec but the full feature set

is used when testing with the default test runner.

To be able to run tests on a assembly or type, the first thing we need to do

is to create a Spec class of the original type, then define it's behavior and then

run it in test runner.

CSpec : Basics

The most important feature of CSpec lays in the Tags.

A Tag is special lambda variable that is parsed and then executed

in the context of the variable.

 

Most of tags are executed through single Extension method :

public static T Should<T, N>(this T obj, Expression<Func<T, N>> operation)
public static T Should<T, N>(this T obj, Expression<Func<T, N>> operation, N operand)

This extension method needs to be called with specified tags to behave in a certain expected way, if wrong

tags will be passed it will fail.

 

Example usage:

 

obj.MyMethod("Hello").Should(@be => 1);
 

This indicates that Method return value should be equal to 1.

There are more tags like:

@be:  It's used to indicate that a type or return type should be equal to something.

@have:  It's used on booleans and object. When it's called on objects properties it does null checks on those properties. When called on booleans it check if some return type or value is true.

@not_be:  negation of @be tag.

@contain: It's used on collections and arrays to check for a specified item. @rcontain: It's used on collections and arrays to check for a specified item but using reflection matching instead of reference.

@raise: [Class must implement Interface] It's used on methods to instrut that a certain set of parameter data should throw a specified exception.

@not_raise: [Class must implement Interface] It's used on methods to instrut that a certain set of parameter data should not throw a specified exception.

@be_close:  It's used on numbers and represents the logical rule actual == expected +/- delta

Tags are not strong typed as they are name variables that get parsed on run time. Although they are the preferred way of writing expectations there

are also strong typed tags (under namespace CSpec.Extensions.Tags) that are C# methods with arguments but hey break the fluent design of behavioral testing, but users still might use them.

 

Example:

Lets create a sample component, that will be a calculator with public interface

First the interface:

 

    /// <summary>
    /// A very simple calc interface
    /// with value carry over.
    /// </summary>
    public interface ICalc
    {
        /// <summary>
        /// Adds a value to the total.
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        int Sum(int a);
        /// <summary>
        /// Substracts a value from total.
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        int Sub(int a);
        /// <summary>
        /// Takes a total to a power of a given value.
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        int Pow(int a);
        /// <summary>
        /// Divides the total by a given value.
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        int Div(int a);

        /// <summary>
        /// Gets/Sets the total value.
        /// </summary>
        int Total { get; set; }

        /// <summary>
        /// Rests the total.
        /// </summary>
        void Reset();
        
    }

 

Then let's implement the interface:

 

    /// <summary>
    /// Calculator project to test CSpec on 
    /// a (semi) real project, rater then 
    /// just testing methods.
    /// </summary>
    public class Calc : ICalc
    {
        /// <summary>
        /// Initalisation constructor
        /// resets the Total value.
        /// </summary>
        public Calc()
        {
            Total = 0;
        }

        /// <summary>
        /// Adds a value to the total
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        public int Sum(int a)
        {
            Total += a;
            return Total;
        }

        /// <summary>
        /// Substracts a value from total
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        public int Sub(int a)
        {
            Total -= a;
            return Total;
        }

        /// <summary>
        /// Takes a total to a power of a given value
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        public int Pow(int a)
        {
            Total = (int)Math.Pow(Total, a);
            return Total;
        }

        /*
         * There are many things that can go wrong with the div 
         * method in this context.
         * 
         * So we will check if our crappy metthod will throw exceptions.
         */

        /// <summary>
        /// Divides the total by a given value.
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        public int Div(int a)
        {
            Total = Total / a;
            return Total;
        }

        /// <summary>
        /// Gets the total value
        /// </summary>
        public int Total
        {
            get;
            set;
        }

        /// <summary>
        /// Rests the total
        /// </summary>
        public void Reset()
        {
            Total = 0;
        }
    }

 

Now that we have our type defined we need to create a new project (not obligatory but good practice)

called CalcSpec, and we need to reference our original component.

The Speced classes use a pattern called Facade to define behaviors and operations that the oryginal component should

follow.

Lets create a Spec class like so:

 

    /// <summary>
    /// Spec type to a Calculator project
    /// </summary>
    /// <remarks>
    /// You don't need to use an interface type to use
    /// CSpec regullar Calc class would be suficient,
    /// but when using the interface type, you gent additional
    /// tags like @raise, @not_raise.
    /// 
    /// Why is it so?
    /// 
    /// Only interface Types give the ability to do at as they can
    /// be Proxied, thus advanced dynamic operations are possible
    /// in this context we can do (pseudo) backwards lookahead.
    /// </remarks>
    public class CalcSpec : CSpecFacade<ICalc>
    {
        /// <summary>
        /// Initializes the parametric constructor of Cspec
        /// and passes the constructor of the Specd class
        /// </summary>
        /// <remarks>
        /// You can use the default constructor if the creation of the parametric one is a chore
        /// (when your class doesn't have simple constructors but require tons of operations to be created),
        /// and just call InitializeFacade
        /// </remarks>
        public CalcSpec()
            : base(new Calc())
        {
        }

        /// <summary>
        /// Clean the Total after each operaton.
        /// </summary>
        protected override void  BeforeOperation()
        {
 	        ObjSpec.Total = 0;
        }

        //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 = null;

        /// <summary>
        /// Simple addition testing
        /// </summary>
        DescribeAll describe_sum =
            (@it, @do) =>
            {
                @it("Adds a value of 5 to total");
                @do.Sum(5).Should(@be => 5);
            };


        /// <summary>
        /// Simple substraction testing
        /// </summary>
        DescribeAll describe_sub =
            (@it, @do) =>
            {
                @it("Substracts a value of 5 from total");
                @do.Sub(5).Should(@be => -5);
            };

        /// <summary>
        /// Simple power testing
        /// </summary>
        /// <remarks>
        /// There is a catch here AHA!, the result of 
        /// calling POW needs to be converted to int,
        /// in this context this is simple but you have to watch for this.
        /// </remarks>
        DescribeAll describe_pow =
            (@it, @do) =>
            {
                @it("Takes the total to a power of 5");
                @do.Total = 5;
                @do.Pow(5).Should(@be => (int)Math.Pow(5,5));
            };

        /// <summary>
        /// Testing of Div, and expecting exceptions
        /// </summary>
        DescribeAll describe_div =
            (@it, @do) =>
            {
                @it("Divides the total by a value of 5");
                @it("In this context Divide should be ok");
                @do.Div(5).Should(@not_raise => typeof(DivideByZeroException));

                @it("Divides the total by a value of 0");
                @it("In this context Divide should be wrong and throw an exception");
                @do.Div(0).Should(@raise => typeof(DivideByZeroException));
            };

        /// <summary>
        /// Testing the value that needs to fit some range.
        /// </summary>
        DescribeAll describe_range =
            (@it, @do) =>
            {
                Random rnd = new Random();
                var value = rnd.Next(1, 5);

                @it("Adds a random value from 1 to 5, current value: " + value);
                @it("In this case the total should be in a specified range");

                @do.Sum(value);
                @do.Total.Should(@be_close => 1, 5); 
            };
    }

 

The inherited CSpecFacade contains special deletates that describe the operations, those operations will be used by

test runner and evaluated.

 

Each operation has two parameters @it, @do.

@it contains a description of the operation

@do is the Spec(ed) class

 

CSpec : Testing

After everything is se run the CSpec.Console and point it to Speced class

and the test will commence.

CSpec.Console.exe runner MyAssemblyContainingSpec.dll -a

For help with the Console type:

CSpec.Console.exe help

Cspec : Ending

This basic guide should get you started on CSpec it does not cover the framework

and it's features in a lot of detail so the best way to know more would

be to look at the class documentation and code comments

as they contain very detailed information.

Last edited Aug 7, 2011 at 10:41 AM by online55, version 7

Comments

No comments yet.