In my last post, I discussed being able to implement the entire application-under-test in one line. This technique is useful at more granular levels as well. In fact, it offers a nice alternative to the idea that tests should be wet (not-DRY). Object-orientation turns out to be a nice solution to duplication in tests.
Refactor this:
[TestFixture]
public class MyObjectTests
{
// field declarations omitted for brevity
[SetUp]
public void Setup()
{
_dataStore = new StubDataStore();
_networkConnection = new StubNetworkConnection();
_collaborator = new Collaborator();
_serializer = new Serializer();
}
private MyObject CreateSut()
{
return new MyObject(_dataStore, _networkConnection, _collaborator, _serializer);
}
private static void CustomAssertEquals(IDictionary<string, string> expected,
IDictionary<string, string> actual)
{
// custom assertion
}
[Test]
public void ShouldValidateAdditionalNewFeature()
{
var sut = CreateSut();
sut.LoadDataStore();
sut.BroadcastStateOnNetwork();
CustomAssertEquals(_dataStore.Data, _networkConnection.Data);
}
}
Into this:
public class MyObjectScenario
{
// field declarations omitted for brevity
public MyObjectScenario()
{
_dataStore = new StubDataStore();
_networkConnection = new StubNetworkConnection();
_collaborator = new Collaborator();
_serializer = new Serializer();
_sut = new MyObject(_dataStore, _networkConnection, _collaborator, _serializer);
}
public void AssertNetworkMatchesDataStore()
{
// custom assertion
}
public void LoadDataStore()
{
_sut.LoadDataStore();
}
public void BroadcastStateOnNetwork()
{
_sut.BroadcastStateOnNetwork();
}
}
[TestFixture]
public class MyObjectTests
{
[Test]
public void DataBroadcastOnNetworkShouldMatchDataStore()
{
var sut = new MyObjectScenario();
sut.LoadDataStore();
sut.BroadcastStateOnNetwork();
sut.AssertNetworkMatchesDataStore();
}
}
Now each test case in the TestFixture is succinct, independent (there are no instance variables in the TestFixture), and is very readable. Additionally, the test cases are DRY and the assertions print out very meaningful error messages which are reusable across TestFixtures. Notice that I am not using a mocking framework for the out-of-process dependencies (network, data store) as this allows me to DRY up the behavior I would otherwise have to redundantly put in each setup or test case. Lastly, these scenarios can be chained up to create the whole application-under-test in my previous post, with little duplication.
It is beneficial to keep your tests DRY. Tests are code too and reuse is just as helpful in tests as it is in production code.
0 comments:
Post a Comment