Saturday, November 29, 2008 10:09 PM
Sean Feldman
C# 3.0 + R# = Great Tests Readability
C# 3.0 has introduced lots of great features to make our life easier and syntax sweeter. Lots of people talked about it already, and I am not scooping here anything new. What I do want to demonstrate, is how to make the code easier to understand.
Confession is that I am relying on a tool to help me achieve what I will demonstrate – ReSharper. Not only it’s a great refactoring tool, but an excellent guide to C# 3.0 features (and not only). So lets roll to an example.
The test is trying to confirm that whatever path is being returned by system under test (SUT) is ending with either “\bin”, “\debug”, or “\release”. Normally, this is what I would write:
1: [Test]
2: public void Should_return_the_path_represented_by_system_as_a_BaseDirectory()
3: {
4: var isMatching = result.path.EndsWith(@"\bin", StringComparison.InvariantCultureIgnoreCase)
5: || result.EndsWith(@"\debug", StringComparison.InvariantCultureIgnoreCase)
6: || result.EndsWith(@"\release", StringComparison.InvariantCultureIgnoreCase);
7: Assert.IsTrue(isMatching);
8: }
Way too chatty. The whole EndsWith is more of a distraction, rather than help.
Next step – introduce an Extension Method that would encapsulate testing. One option would be to write a method that takes boolean and asserts.
1: public static void should_be_true(this bool item)
2: {
3: Assert.IsTrue(item);
4: }
Then the code would read a bit more descriptive, but still with lots of ‘noise’.
1: isMatching.should_be_true(result.EndsWith(...) || (...) || (...));
Not nice :) Another way is to have an extension method accepting a Func<T, bool> and leverage method group
1: public static void should_be<T>(this T item, Func<T, bool> evalueationWith)
2: {
3: Assert.IsTrue(evalueationWith(item));
4: }
To escape the ‘noise’ I do what have described in the previous post (Test Helper with Fluent Interface).
1: private class True
2: {
3: public static bool for_the_given_path(string path)
4: {
5: return path.EndsWith(@"\bin", StringComparison.InvariantCultureIgnoreCase)
6: || path.EndsWith(@"\debug", StringComparison.InvariantCultureIgnoreCase)
7: || path.EndsWith(@"\release", StringComparison.InvariantCultureIgnoreCase);
8: }
9: }
Bringing the test to the form it becomes cleaner.
1: [Test]
2: public void Should_return_the_path_represented_by_system_as_a_BaseDirectory()
3: {
4: result.should_be(s => True.for_the_given_path(s));
5: }
With some hints from R#…
It becomes “now I get it”, as my team mate Terry Thibodeau says, to hint me that finally the test becomes readable and simple to be considered good.
1: [Observation]
2: public void Should_return_the_path_represented_by_system_as_a_BaseDirectory()
3: {
4: result.should_be(True.for_the_given_path);
5: }
Develop code, not bugs!
Filed under: TDD