BDD and Parameterized testing
Although I really like Astels style BDD (to me a constrained/enhanced style of TDD) I still use a lot of parameterized testing and though I should give you an example why, using XUnit.net.
Lets say we're testing simple SPECIFICATION style rules, we might write:
[Concerning(typeof(ValidEmailRule<TestEntity>))]
public class When_using_rule_on_a_null_string : SpecificationBase
{
protected TestEntity _testEntity;
private bool _isSatisfied;
protected override void EstablishContext()
{
_testEntity = ContextSetup.CreateTestEntityWithValue(null);
}
protected override void Act()
{
_isSatisfied = new ValidEmailRule<TestEntity>(_testEntity, x => x.Value).IsSatisfied();
}
[Observation]
public void is_satisfied()
{
_isSatisfied.ShouldBeTrue();
}
}
This just tests how the rule handles a null value, but we'd then want to test with all sorts of other values (valid and invalid). To compare lets thus look at how easy it is to test a variety of invalid e-mail address using one of XUnit.net's parameterized testing approaches (see Ben Hall for more options):
[Concerning(typeof(ValidEmailRule<TestEntity>))]
public class When_evaluating_invalid_email_addresses
{
[Theory]
[InlineData("sddas.com")]
[InlineData("sddas@")]
[InlineData("@")]
[InlineData("@blah.com")]
[InlineData("sddas@@blah.com")]
[InlineData("1213231")]
public void is_not_satisfied(string invalidEmailAddress)
{
var testEntity = ContextSetup.CreateTestEntityWithValue(invalidEmailAddress);
var isSatisfied = new ValidEmailRule<TestEntity>(testEntity, x => x.Value).IsSatisfied();
isSatisfied.ShouldBeFalse();
}
}
Now you may disagree with my approach here, this isn't as readable as it could be, but I think you can see why you'd use this approach if you have a lot of values to validate.
You know, you could replace those inline tests with Pex :)
ReplyDeleteIn my session, i'll going to discuss BDD and Pex - keep an eye out for a blog post covering the topic soon ;)
Can you give me a sneak peak, link about how you'd do that.
ReplyDeleteFrom what I saw of the first version(s) of Pex it didn't provide a particularly strong way to specify the contract of my classes...haven't looked for a while though.
While succeeding in creating reusable code, parametrized tests in general that are based on dis-encapsulated metadata don't tend to create code that can be understood at a glance in the way that well-factored imperative code can.
ReplyDeleteI tend to think of this kind of test mechanism as something that only a programmer can love, and would therefore label it as a likely local optima. Not to say that programmers shouldn't locally-optimize, but tests shouldn't be targets of local optimization.
Tests are documents and should be optimized for solubility rather than reuse.
Terseness and radical, metadata-based decomposition don't often lead to solubility. We need to draw a line in the sand of our minds between interesting technological solutions and compelling documentation and optimized learning experiences.
This is, as always, an area where usability should trump interesting programmer stuff.