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.