Tuesday, 18 September 2007

KataMinesweeper acceptance tests.

I was inspired by the SaoPauloDojo to have a look at KataMinesweeper. The code they came up with is published amongst the files on their googlegroup, and I am referring to the one called 08-MineSweeper.py. Of course, it being in portuguese I am obviously missing many subtleties :-)

At the SaoPauloDojo meeting they clearly decided to start with the most interesting part of the problem, ie counting mines, and didn't get to the part that deals with input and output strings. I think this is a shame because they have jumped straight to writing tests that assume the minefield data will be a list of lists. I am not convinced this is the best data structure, and all the tests they have written are very coupled to this design decision. For example:

class TesteAceitacaoCampoMinado(unittest.TestCase):
def testeCampo3x3Vazio(self):
campoMinado = CampoMinado([[0,0,0],[0,0,0],[0,0,0]])
campoMinado.preencheTabuleiro([['.','.','.'],['.','.','.'],['.','.','.']])
self.assertEquals([[0,0,0],[0,0,0],[0,0,0]], campoMinado.matriz)

def testeCampo3x3BombaNoMeio(self):
campoMinado = CampoMinado([[0,0,0],[0,0,0],[0,0,0]])
campoMinado.preencheTabuleiro([['.','.','.'],['.','*','.'],['.','.','.']])
self.assertEquals([[1,1,1],[1,'*',1],[1,1,1]], campoMinado.matriz)

def testeCampo2x3ComUmaBomba(self):
campoMinado = CampoMinado([[0,0,0],[0,0,0]])
campoMinado.preencheTabuleiro([['.','.','.'],['*','.','.']])
self.assertEquals([[1,1,0],['*',1,0]], campoMinado.matriz)


I think that tests at the acceptance test level should be as independent as possible of these kinds of design decisions. Not only that, the acceptance tests should be readable to a non-coding domain expert. I would prefer to see tests that look more like this

   
def testSmallEmptyGrid(self):
input = """\
3 3
...
...
...
"""
expected = """\
Field #1:
000
000
000
"""
self.failUnlessEqual(expected, minesweeper.count_mines(input.splitlines()).next())

But then of course one of the first problems you have to tackle is how to handle the I/O rather than the perhaps more interesting problem of how to count the mines.

In the problem description the acceptance tests are already set up as plain text input and output, so for me it would be very natural to tackle the problem using the tool TextTest to drive development instead of unit tests. This is a technique that Geoff and I have been working on recently, we presented a workshop together at XP2007 and Geoff by himself at agile2007 exploring this idea. By using this technique you isolate your acceptance tests from the current implementation of the code and I think this makes you more agile. You can refactor your code as much as you like so long as the external acceptance tests still pass. I'm working on an article which will showcase this technique that hopefully I will get around to finishing soon.

I'm also planning to tackle this Kata together with my local dojo group - the Gothenburg Ruby User Group (GRUG) tomorrow evening. We are planning to use Ruby, RSpec and BDD, so it should be interesting to see what we come up with and how decoupled our tests are from the internal design of the code.