Monday, September 24, 2007

Mocking & BDD

First of all I wanna say, if you find a bug try to catch it with a test before you fix it. When you're done, see to that the test passes. This can be hard but I think it is a great way to get better at writing tests; you'll see what you let slip the last time and then (hopfully) take it into accout the next time you implement and write tests. No, of couse it should be write tests & then implement.

I recently came across stubbing & mocking and I must say that it's great. Before I used to think that there are situations you just can't test, even if you want to. At one peticular time I was writing this file reader that needed testing, but since it used a low level C api it seemed impossible to test. Or rather, fit into our testing system. I could of course put loads of test data in our repositary, but that would really increase our testtimes. And, even more important, chances are that I then end up just testing the C api. Well you never know, you might discover some bugs but that's not really what you wanna do, is it?

For me this was a very common problem, I tried to have the tests generate the right sort of data. The problem is that these generations are complex at best and even if you manage to get it right it makes the tests hard to read as hell. Once I actually had the program I currently was developing genereate data and then manually opened it in Excel to manipulate it. Then I saved the different data with smart names like 'data_with_none_existing_article_entry.dat' and so on...

As you can imaging this is very errorprone and you'll eventually end up testing your functions against data that you don't know is right in the first place. So this is not the way to do it, so how do you do it then? The answer is that you mock.

Mocking means that you create an object. Then you tell this object to return whatever you want to have from it. Or here we have to introduce the term 'stubbing'. When you mock you expect the method you mock to be called, if it's not called exactly the number of times you specify an error will be raised. When you stub an object you just supply a couple of methods and if they happends to be called they return the specified value.

Sounds easy? Well it is. Say you developing a class that keeps track of all the grades in a school class. You're storing all the data in a database and then just do some calculations before you show it for the user. There is no need to test the database and it accessors, that belongs to another testcase possibly even to another programmer in another company. You mock the database. This can be done in a number of ways but there exists good mocking frameworks for most languages. So let me show.

def test_calc_median_grade
grades = [2, 2, 3, 3, 4, 4 , 5]
pupil = mock(' A ordinary pupil')
pupil.should_recieve(:grades).and_return( grades )
assert pupil.calc_median_grade == 3
end

By the way, if I do it in a more behavior driven way it would be like
describe Pupil, "grades" do

it "should have a correcly calculated median" do
grades = ....
pupil.calc_median_grade.should eql(3)
end

end

It's hard to explain why the behavior driven way is so powerful, it just is. Try it for a while and you'll see. I too was very sceptical a first.

But now back to the file reader. How do you do that? The trick is to stub /mock the 'open' function. To do this you just have to go on like I showed before but at this peticular time I was developing in python and didn't seem to find a good mocking framework for it. In the end I decided to write my own stubbed object and that's actually very easy (at least in none-typed-languages such as python). So this is what I did:
class Reader:
def open(self, foo = None):
return self
def readlines(self):
return ["foo", "bar"]
def close(self):
pass

class SomeTestCase(testcase):
def test_module_depending_on_filereader(self):
module.Reader = Reader()
# call what ever function you wanna call
# assert what you wan't to assert.

No comments: