Ayende @ Rahien

Unnatural acts on source code

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

Michael Hanney
06/13/2008 01:24 AM by
Michael Hanney

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?)

Ayende Rahien
06/13/2008 01:30 AM by
Ayende Rahien

That is the quick & dirty way to do so.

Try to use the auto transaction facility instead

Ivo Ramírez
06/13/2008 09:44 PM by
Ivo Ramírez

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.

Ayende Rahien
06/14/2008 12:35 AM by
Ayende Rahien

Because it makes for very ugly ctors.

Object initializers are much clearer than multi arg ctors.

Comments have been closed on this topic.