Written January 27, 2009 at 06:08 MST Tagged c sharp and programming
The other day I placed a library up on assembla that I use as a place to store my current set of conventions on writing unit tests. My current flavour of testing focuses on the use of BDD to emphasize context and behaviour of a particular SUT (System under test) that I am exercising.
This post is not about detailing my current thinking on BDD, more to demonstrate some ways that you can use the very small framework in conjunction with MBUnit to remove a lot of cruft from your tests. To that end, the following are some sample tests written to demonstrate different aspects of the library:
1) Adding two numbers (without a because block):
public class when_adding_2_numbers_together : observations_for_a_static_sut
{
[Observation]
public void should_result_in_the_sum_of_the_2_numbers()
{
(2 + 2).should_be_equal_to(4);
}
}
Notice that the class simply inherits from a class named observations_for_a_static_sut. This is the base class you will use when you are either testing a static class, or (as in this case) you are simply writing a quick inline test.
2) Adding two numbers with a because block (the because block is meant to focus your eyes to the actual behaviour you are exercising:
public class when_adding_2_numbers_together : observations_for_a_static_sut
{
static int result;
because b = () =>
{
result = 2 + 2;
};
[Observation]
public void should_result_in_the_sum_of_the_2_numbers()
{
result.should_be_equal_to(4);
}
}
[Concern(typeof (DatabaseGateway))]
public class when_retrieving_a_set_of_rows_based_on_a_query : observations_for_a_sut_without_a_contract<DatabaseGateway>
{
static IEnumerable<DataRow> result;
static IQuery query;
static IDatabaseConnectionFactory connection_factory;
static IDatabaseConnection connection;
static IDbCommand command;
static IDataReader reader;
context c = () =>
{
connection_factory = an<IDatabaseConnectionFactory>();
query = an<IQuery>();
connection = an<IDatabaseConnection>();
command = an<IDbCommand>();
reader = an<IDataReader>();
connection_factory.Stub(x => x.create()).Return(connection);
connection.Stub(x => x.create_to_run(query)).Return(command);
command.Stub(x => x.ExecuteReader()).Return(reader);
};
because b = () =>
{
result = sut.get_rows_matching(query);
result.force_traversal();
};
[Observation]
public void should_leverage_db_infrastructure_to_return_a_set_of_rows_from_the_db()
{
result.should_not_be_null();
}
[Observation]
public void should_dispose_the_appropriate_items()
{
connection.was_told_to(x => x.Dispose());
}
public override DatabaseGateway create_sut()
{
return new DatabaseGateway(connection_factory);
}
}
context c = () =>
{
connection_factory = an<IDatabaseConnectionFactory>();
query = an<IQuery>();
connection = an<IDatabaseConnection>();
command = an<IDbCommand>();
reader = an<IDataReader>();
connection_factory.Stub(x => x.create()).Return(connection);
connection.Stub(x => x.create_to_run(query)).Return(command);
command.Stub(x => x.ExecuteReader()).Return(reader);
};
In this block I define all of the dependencies that are going to come into play during the test. The “an” method, is a generic method that lives on the base class, which basically creates a mock of the contract in question. It carries on to setup return values for method calls that will be leveraged during the course of the SUT doing its work. Unfortunately, it is hard to identify which mocks are direct dependencies of the sut and which are collaborators. It is only when you look at the factory method for the system under test:
public override DatabaseGateway create_sut()
{
return new DatabaseGateway(connection_factory);
}
Here it is evident that the direct dependency of the DatabaseGateway is the connection_factory (IDatabaseConnectionFactory). The because block highlights the behaviour being tested:
because b = () =>
{
result = sut.get_rows_matching(query);
result.force_traversal();
};
public class DatabaseGateway
{
IDatabaseConnectionFactory connection_factory;
public DatabaseGateway(IDatabaseConnectionFactory connection_factory)
{
this.connection_factory = connection_factory;
}
public IEnumerable<DataRow> get_rows_matching(IQuery query)
{
var table = new DataTable();
using (var connection = connection_factory.create())
using (var command = connection.create_to_run(query))
using (var reader = command.ExecuteReader())
{
table.Load(reader);
}
foreach (DataRow row in table.Rows) yield return row;
}
}
[Concern(typeof (DatabaseGateway))]
public class when_retrieving_a_set_of_rows_based_on_a_query : observations_for_a_sut_without_a_contract<DatabaseGateway>
{
static IEnumerable<DataRow> result;
context c = () =>
{
the_dependency<IDatabaseConnectionFactory>().Stub(x => x.create()).Return(the_dependency<IDatabaseConnection>());
the_dependency<IDatabaseConnection>().Stub(x => x.create_to_run(the_dependency<IQuery>())).Return(the_dependency<IDbCommand>());
the_dependency<IDbCommand>().Stub(x => x.ExecuteReader()).Return(the_dependency<IDataReader>());
};
because b = () =>
{
result = sut.get_rows_matching(the_dependency<IQuery>());
result.force_traversal();
};
[Observation]
public void should_leverage_db_infrastructure_to_return_a_set_of_rows_from_the_db()
{
result.should_not_be_null();
}
[Observation]
public void should_dispose_the_appropriate_items()
{
the_dependency<IDatabaseConnection>().was_told_to(x => x.Dispose());
}
}
The factory method has been removed along with the fields to reference the dependencies that come into play. For readability, I think this approach loses out, so this last way is the way I would finish up with:
[Concern(typeof (DatabaseGateway))]
public class when_retrieving_a_set_of_rows_based_on_a_query : observations_for_a_sut_without_a_contract<DatabaseGateway>
{
static IEnumerable<DataRow> result;
static IQuery query;
static IDatabaseConnectionFactory connection_factory;
static IDatabaseConnection connection;
static IDbCommand command;
static IDataReader reader;
context c = () =>
{
connection_factory = the_dependency<IDatabaseConnectionFactory>();
query = an<IQuery>();
connection = an<IDatabaseConnection>();
command = an<IDbCommand>();
reader = an<IDataReader>();
connection_factory.Stub(x => x.create()).Return(connection);
connection.Stub(x => x.create_to_run(query)).Return(command);
command.Stub(x => x.ExecuteReader()).Return(reader);
};
because b = () =>
{
result = sut.get_rows_matching(query);
result.force_traversal();
};
[Observation]
public void should_leverage_db_infrastructure_to_return_a_set_of_rows_from_the_db()
{
result.should_not_be_null();
}
[Observation]
public void should_dispose_the_appropriate_items()
{
connection.was_told_to(x => x.Dispose());
}
}
Develop With Passion!!