Search The Blog
About this site

@RoyOsherove

Subscribe!

This site aims to connect all the dots of my online activities - from tools, books blogs and twitter accounts, to upcoming conferences, engagements and user group talks.

from 5whys.com
Twitter: @RoyOsherove
My Book: The Art of Unit Testing
Latest Posts
« Refactoring private methods is like...? | Main | About the ALT.NET mailing lists »
Wednesday
Jan092008

Scala, NRehersal and Fluent Test Interfaces

This post by Ian cooper triggered my interest in Scala, a new object oriented, functional, programming language that is very accessible for java developers.  From the looks of it, it seems very accessible for C# developers as well.

Scouring around related links for a while I came across the Rehersal project, which is a unit testing framework for Scala(which already has a built in SUnit test framework very muich like JUnit), that tries to make things "less cumbersome" for developers, such as giving test names with spaces, etc.

Here's an example of a Rehersal test class:

package examplesimport rehersal._import rehersal.TestCategories._class SkeletonTests extends Tests(UnitTesting){
 test("Example of a test that will fail")( ()=> { fail() })
 test("Example of expected exceptions that will pass", classOf[IndexOutOfBoundsException])( ()=> { throw new IndexOutOfBoundsException() })
 
//and this is how you implement test setup and teardown :
 onceBefore = () => { note("Run once before all tests in this object") } onceAfter = () => { note("Run once after all tests in this object, unless onceBefore failed") } before = () => { note("Run before each test unless the test is a duplicate") } after = () => { note("Run after each test, unless before failed") }
}

 

At first look I thought it looked a lot like lambda expressions in C# 3.5 so I went ahead a did a little implementation of something I call NRehersal, which tries to mimic the same style of the Rehersal framework.

As I was working on it it occured to me that what's really interesting to me is the fact that the tests, if implemented as regular method calls, can be implemented in a more fluent syntax, so I came up with something that looks like this:

public class NRehersalTestSimple : RehersalBase
{public override void TheTests()
    {
        onceBeforeEachTest=delegate
            {
                Console.WriteLine("before each TEST");
            };

        onceBeforeAllTests=delegate
            {
                Console.WriteLine("before all tests");
            };

        TEST("This is a TEST", delegate
        {
            Console.WriteLine("in TEST");
        });

        TEST("This is a TEST with an expected exception")
            .ExpectException<OutOfMemoryException>()
            .Execute(delegate
                         {
                             throw new OutOfMemoryException("whatever");
                         });
    }}

 

Things to note here:

  • There is one big method called 'TheTests" that we override
  • We call "test" for each test case.
  • that means all tests will always run. you can't run just part of the tests by default.
  • setup and teardown are implemented by replacing delegates on the base class
  • you could use lambda syntax instead of the word delegate ( ()=> )

 

Extensibility

But then I got to thinking, how extensibility can be taken care of, for example. what happens if I want to add a repeatable test ability?

This is achieved using the .NET 3.5 features: an Extension method to the interface returned by the test method, and a customized delegate that can be changed at runtime for the test runner itself. here is what it would look like:

  • Create a custom class that inherits from TestData that will implement new features  (a Times(int) method)

internal class RepeatSupportingTestData : TestData
   {
       private int timesToRun = 2;

       public int TimesToRun
       {
           get { return timesToRun; }
           set { timesToRun = value; }
       }

       public RepeatSupportingTestData(TestData data)
           : base(data.Code,data.Name)
       {
       }
   }

 

 

  • Add an extension to the interface named ITestInvocation so you can use this new feature in your tests

static class TestExtensions
{
    public static ITestInvocation Times(this ITestInvocation exp, int time)
    {
        RepeatSupportingTestData data = exp.Data as RepeatSupportingTestData;
        data.TimesToRun = time;
        return exp;
    }
}

  • Replace the delegates for creating TestData and for running a test with your own code

public class NRehersalExtensions : RehersalBase
    {
        private static Action<TestData, RehersalBase> originalSingleTestRunner;
        public NRehersalExtensions()
        {
            //save the original single test runner to be run in a loop later
            originalSingleTestRunner = TestOverrides.SingleTestRunMethod;

            //change the single test runner to our own repeat runner
            TestOverrides.SingleTestRunMethod = RepeatTestMethod;
            //change the test data factory to out own so we can return our own test
            //type that adds the repeat time property.
            TestOverrides.TestDataFactory = CreateRepeatableTestData;
        }

        private static TestData CreateRepeatableTestData(TestData data)
        {
            return new RepeatSupportingTestData(data);
        }

        private static void RepeatTestMethod(TestData test, RehersalBase testClass)
        {
            RepeatSupportingTestData data = test as RepeatSupportingTestData;
            for (int i = 0; i < data.TimesToRun; i++)
            {
                originalSingleTestRunner(data, testClass);
            }

        }

        public override void TheTests()
        {

            TEST("repeat TEST").Times(3)
                .Execute(()=>
                             {
                                 Console.WriteLine("Executing repeat TEST");
                             });

        }
    }

 

You can get the code and zip file here at the google project page.

This is just something I threw together to see what it would fee like. it is still not implementing the various Asserts and expectations, which is a trivial matter of adding these methods to the RehersalBase class.

 

Your thoughts are welcome. Personally I'm not sure what I feel about this syntax, but I like the fluent test interface that can be made with it, and the ease of extensibility.

PrintView Printer Friendly Version

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>