I am going to propose a guideline for the use of C# 3.0 Extension Methods: Don’t Use Them to Rename Existing Functionality in an API Just Because You Don’t Like A Function’s Name.
The idea of extension methods is that you can improve an otherwise non-extensible object’s public API by ‘extending’ its Interface to include additional methods that the original class designer hadn’t considered. Used judiciously, this technique can improve the usability of the object and make interacting with it much more intuitive. Common uses are in the areas of aggregating what would otherwise be calls to multiple separate methods into a single ‘meta-method’ that wraps several more atomic methods, providing ‘converter’ methods that handle type conversions, and wiring up external validation logic to an object.
But all too often lately I am seeing these methods used simply to provide alternate preferred names to existing methods already present in the original class.
Unit Test Frameworks should remain Pure
This is probably most-commonly seen in (of all places) Unit Test frameworks. For a long time there was (general) agreement among both designers and users of Unit Test frameworks about the ‘common’ way to state assertions. Or at least I thought there was general agreement. Apparently, it seems that this ‘agreement’ may have just been a consequence of people not having an easy way to deviate from the commonly-accepted norms.
Today in all kinds of sample code in various blog posts all over the Internet I routinely see things like…
Assert.AreEqual(expected, actual);
Assert.Equal(expected, actual);
Assert.That(expected, actual).AreEqual();
Assert.That(actual).ShouldEqual(expected);
Even if reasonable people can debate the ‘best’ way to state an equality assertion in a unit test framework, I firmly believe that the place for this debate is between the team maintaining the unit test framework project and its user-base, NOT between different adopters of each Unit Test Framework by randomly inventing extension methods that provide the exact same capability as already present in the framework.
Technology isn’t evil, but its implementation sure can be
This isn’t to say that I don’t like fluent interfaces and don’t like extension methods. These are just techniques and technologies like any other that can be used either for good or evil at the whim of the developer who wields them.
But when used to arbitrarily restate behavior that already exists on a class in an alternate syntax just for the sake of a very limited (and entirely debatable) improvement in readability, I think reasonable people have to agree that this is leading us down a road towards what I call API pollution where three and four methods on a single class all do the exact same thing but use a slightly different syntax to accomplish it.
Assert.That(actual).Should().Nearly().Equal(expected).Except().When().Varying().By().LessThan(3);
…is the inevitable end-result of this trend and I for one don’t want to try to be a consumer of such a framework, no matter how ‘readable’ it was thought to be.
Hi Steve,
What about the concept of BDD vs. TDD? I would like to hear you thoughts on BDD, do you believe that BDD is designed to get you to think correctly about what you are testing? Would you consider this methodology?
Just curious :).
Thanks.
@Ed:
I think that its entirely likely that many of the “shoulds” and “ought-to’s” that I am seeing out there are indeed the result of (reasonably) well-meaning BDD-focused individuals as my own experience suggests that it tends to be the BDD-ers that are most obssessed with the language used to describe their tests (and assertions).
I actually think that BDD as a thought process has a lot to offer in re: the way it makes you focus on the behavior of your softare rather than its ‘outputs’. And as a guide to help developers focus on testing ‘what matters’ rather than over-spefifying brittle tests of things that don’t, I am inclined to give a nod to BDD as being helpful there.
But I also firmly believe that one can successfully practice BDD by stating test methods in terms of desired behaviors rather than convoluted assertions. Assertions like .ShouldEqual(), .ShouldBe(), and others aren’t really that useful to me when compared against the API pollution that I was alluding to here. A test method called ‘Should_Prevent_User_from_Logging_On()’ is useful to me as a BDD-friendly syntax so that I can see what’s passing/failing in a result report. But Assert.That(actual).ShouldBe(expected) is starting to trend towards API self-flaggelation in my book and adds little value to the assertion/failure process (IMHO).
My experience with lovers of such language contortions is the same as my experience with many of the DDD-minded set out there: many tend to accept BDD (or DDD) as some kind of ‘religion’ that must be fervently adhered to in all ways, no matter the cost. When people start quoting scripture to me (even if its Evans instead of Matthew or Luke) I tend to tune them out 🙂
Sorry, can’t agree with you. There’s a number of reasons to use a fluent assertion API.
1. Extending (adding assertions) doesn’t cause name overload issues. What should I name the static class? What class do I find the “AllInstancesShouldNotBeNull” assertion on? With extension methods I just have to add a using and call “Assert.That(foo).ShouldNotHaveNullItems()”. When adding assertions I don’t have to care about the class name, as generally the user won’t be exposed to it.
2. It helps intellisense. All assertions start with “Assert.That(foo)”. Then intellisense will let me know what assertions are available for the specific type I’m asserting on.
Traditional assertion APIs are clumsy to use in comparison to the fluent APIs. So much so, that I don’t think a unit test framework developed today would include any assertions that are not fluent in nature.
@wekempf:
I do concede your point that this has a place; I am really just arguing for some consistency across syntax so that when unfamiliar people view the code, its understandable.
I’m all in favor of ‘fluent’ interfaces but only so long as they are actually ALSO ‘comprehensible’ intefaces. IMO fluency is about ease of SPEAKING something and comprehension is about ease of READING something and we all know that code gets read many more times than it gets written. So long as ‘ease of coding’ doesn’t lead to ‘difficulty in reading’, I’m all for the introduction of these things.
But I really feel strongly that ‘extension-method-enabled API pollution’ needs to be recognized as a very real threat to the approachability and readability (and by this I really mean COMPREHENSION) of code — a balance needs to be struck here and that’s really all I’m aruging for (some reasonable restraint) 😀
Thanks for the comment~!
I agree, but for a different reason: these APIs generally take an OO approach to a functional problem.
What we’re talking about is a C# specification monad, striving to do for the individual what LINQ does for the many. Pattern matching is about as functional as it gets; constructing an object tree (IConstraint) goes against the grain.
Imagine, instead of Assert.That() having 2 parameters, it were to return something like IConstrainable. This would allow us to write assertions of arbitrary complexity, something like:
Assert.That(text).IsEmailAddress().WithLength(1, 200);
or
Assert.That(customer).IsPreferred();
Now, as we move away from simply renaming well-known constructs, extension methods start to make things simpler. Contrast:
Assert.IsTrue(n % 2 == 0);
with
Assert.That(n).IsEven();
– or –
Assert.IsTrue(n >= 0 && n <= 100);
with
Assert.That(n).IsPercentage();
Within a framework like this, the “basic” operations are needed to maintain a consistent level of abstraction:
Assert.That(n).IsEven().IsInRange(0, 42).IsNotEqualTo(21);
Just some things to think about.
(Full disclosure: I am working on just such a project)
BTW first time reading the blog, am really enjoying the content.