Pain reduction: creating ductile tests
Take a look at this test:
[Test] public void When_asking_for_latest_webcast_will_not_consider_webcasts_published_in_the_future() { var webcast = new Webcast { Name = "test", PublishDate = DateTime.Now.AddDays(-2) }; With.Transaction(() => webcastRepository.Save(webcast)); var webcast2 = new Webcast { Name = "test", PublishDate = DateTime.Now.AddDays(2) }; With.Transaction(() => webcastRepository.Save(webcast2)); Assert.AreEqual(webcast.Id, webcastRepository.GetLatest().Id); }
It looks like a valid test, doesn't it? It has a huge problem. It is brittle.
I just added a new non null property, and all the tests broke. I started to add the new property value to the test, before I realize what I was doing. I run into a friction point, and I was trying to cover it with code. Next time I would add such a property, I would run into the same problem.
This is unacceptable.
The standard solution for this is to create a factory for this, or use an Object Mother. This was never something that I was fond of. I always need more flexibility than I can usually get from it, and I hate building builders, that is so boring.
Turn out, I can eat the cake and keep it.
I created the following test class:
[ActiveRecord("Webcasts")] public class TestableWebcast : Webcast { public TestabbleWebcast() { Name = "Test name"; Description = "Test description"; } }
And now the test change to:
[Test] public void When_asking_for_latest_webcast_will_not_consider_webcasts_published_in_the_future() { var webcast = new TestableWebcast { PublishDate = DateTime.Now.AddDays(-2) }; With.Transaction(() => webcastRepository.Save(webcast)); var webcast2 = new TestableWebcast { PublishDate = DateTime.Now.AddDays(2) }; With.Transaction(() => webcastRepository.Save(webcast2)); Assert.AreEqual(webcast.Id, webcastRepository.GetLatest().Id); }
I get to keep the nice object initializer syntax, and I even get more clarity, since I can now specify only the properties that I am actually interested in.
The only annoying thing is that I have to define the TestableWebcast as an entity as well, but I can live with it.
Comments
Apologies for the off topic nature of this comment, but I find this very interesting..
With.Transaction(() => webcastRepository.Save(webcast));
I did not know that was how to create a transaction using Rhino.Commons.
Thanks for the example. (It is Rhino.Commons.With.Transaction, yes?)
That is the quick & dirty way to do so.
Try to use the auto transaction facility instead
I think that's a good solution for creating the objects the test needs. But in this particular case, If Description is a non null property, why Webcast can exist without a value on it? why not to put the description as a constructor argument?
You allways will have to put the code for the new required propery (in the test or the testeable class constructor). I prefer to realize it in compilation time.
Because it makes for very ugly ctors.
Object initializers are much clearer than multi arg ctors.
Comment preview