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.