I have discovered something tonight - the art of testing through mocking. Yes, I admit it: I've never actually given much thought to the fact that mocking could be remotely interesting due to the fact that a mock is simply just a replacement for something you intend to test, i.e. a database access layer or external devices connected to your computer. I do not like to think of myself as being religious but nevertheless I have never given mocking a try because it just didn't feel right (!). So much for trying to see things from the other side - until now, because:
We have had quite a few exhausting weeks at work. Our source control is coming up to speed and a CI build enviroment is slowly emerging. It just builds everything but we are working disconnected from eachother and nobody can screw up the build for days without getting instant notice. I like it sooo much... We have hired one of my former collegues, who is now an independent consultant, to tell us about Agile development and TDD. Last Wednesday we talked a bit about mocking and I peeped a little about the paradox of testing a simulated version of the real world. However I decided that I would give a mocking framework a try one of these days and I tried it out tonight, so I created a simple scenario:
I want to create a Person object and store it in a database. The person should expose to me whether or not he has been saved or not. Separation of Concerns, I know, I know... I'm just mocking a scenario here... (applause sign on). And - tada - I want to test on both a real object and a mocked one and be able to retrieve a positive response whenever I ask Mr. Per Son: Have you been saved?
I downloaded Rhino Mocks because I had it recommended and it seems to be some sort of de facto standard these days - mainly because it is strongly typed which doesn't seem to be the case with other .NET mocking frameworks. I created the following unittests to mock my reallife Person object:
[Test]
public void CreateDomainObject()
{
Person p = new Person();
Assert.IsNotNull(p, "Person is null");
}
[Test]
public void RealPerson_NotPersisted()
{
Person p = new Person();
Assert.IsFalse(p.IsPersisted());
}
[Test]
public void RealPerson_Persisted()
{
Person p = new Person();
p.Save();
Assert.IsTrue(p.IsPersisted());
}
I then created a Person object to simulate this behaviour:
public class Person
{
private bool persisted;
public Person()
{
persisted = false;
}
public void Save()
{
persisted = true;
}
public bool IsPersisted
{
get { return persisted; }
}
}
...and now things get interesting. A mock of the Person is introduced in the following test:
[Test]
public void Mock_Persisted_Property()
{
IPerson persistedPerson = GetMockedPerson();
Assert.IsTrue(persistedPerson.Persisted);
}
private IPerson GetMockedPerson()
{
MockRepository mocks = new MockRepository();
IPerson person = (IPerson)mocks.CreateMock(typeof(IPerson));
Expect.Call(person.Persisted).Return(true);
mocks.ReplayAll();
return person;
}
The MockRepository makes it possible to take an instance of an IPerson object - that is, an interface describing the methods and properties on the object you intend to test. The domain object does not actually have to inherit the interface so if you do not want to have your domain model "interfaced" for various reasons, you do not have to. The IPerson I created is simply:
public interface IPerson
{
bool Persisted { get; }
}
I do not want to do anything with Save() so I leave it out of the interface. Maybe it is not best practise - I'm a newbie on this so you are obliged to guide me in the right direction if this has proven to be an anti-pattern.
In the Expect thingy I decide that a call to the Persisted property on my Person object should return true. The mocks.ReplayAll() stops recording expectations on my Person object and now I am ready to validate my expectations in my tests.
This makes it possible for me to run the Mock_Persisted_Property() test succesfully - and retrieve a positive value when asking if the Person has ever been saved even if the Save method has not been called. This is truly great because I have had numerous encounters with problems regarding third-party vendors and external hardware and processes which has not been testable for me until now either because testing them would result in extremely slow integration tests or that the tests themselves and the chance of a succesful test suite run would be extremely tightly coupled to a certain state of the hardware being tested (i.e. the hardware device should always be on and responding to requests).
I like this a lot. I have only scratched the documentation but I will definately go deeper into this during the next weeks and months. I have seen the light.- or at least seen the possibilities instead of only focusing on the limitations of mocking ;o)
Regards Kristian
P.S: - The entire source (Visual Studio 2008 solution) can be downloaded from this location
This blog contains reflections and thoughts on my work as a software engineer
tirsdag den 22. april 2008
Hello Rhino World
Indsendt af Kristian Erbou
Etiketter: mocking framework, rhino mock, tdd, testdriven development
Abonner på:
Kommentarer til indlægget (Atom)
Ingen kommentarer:
Send en kommentar