Monday, March 29, 2010

My Venture into JBehave via Conway's Game of Life

Having used Conway's Game of Life (CGoL) at several CodeRetreats* to practice Test Driven Design (TDD), I thought I would revisit it when I took my first steps into Behavior Driven Development (BDD) with JBehave.

I downloaded JBehave 2.5 and refreshed my memory of the GoL rules:
  1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
  2. Any live cell with more than three live neighbours dies, as if by overcrowding.
  3. Any live cell with two or three live neighbours lives on to the next generation.
  4. Any dead cell with exactly three live neighbours becomes a live cell.
What I love about using GoL for learning BDD is that the rules are practically already written as Given-When-Then scenarios. Just minor tweaks (including changing to the US spelling of neighbor) gave me:
  1. Given a live cell with fewer than two live neighbors, Then the cell is dead.
  2. Given a live cell with more than three live neighbors, Then the cell is dead.
  3. Given a live cell with two or three live neighbors, Then the cell is dead.
  4. Given a dead cell with exactly three live neighbors, Then the cell is alive.
All that's missing is a "When". All 4 rules have the same "When", which is "When I calculate the next generation". Taking that into account, I get:
  1. Given a live cell with fewer than two live neighbors, When I calculate the next generation, Then the cell is dead.
  2. Given a live cell with more than three live neighbors, When I calculate the next generation, Then the cell is dead.
  3. Given a live cell with two or three live neighbors, When I calculate the next generation, Then the cell is dead.
  4. Given a dead cell with exactly three live neighbors, When I calculate the next generation, Then the cell is alive.
Since any given rule could only have 9 different cases (0-8 alive neighbors) I decided that, rather than deal with the logic of "less than"/"fewer than"/"exactly", I would spell out every specific condition that meets a rule.

JBehave expects scenarios to have separate lines for Given, When, and Then. Rule #1 can be completely expressed as:

Scenario:
Given Rule 1: alive cell with 0 neighbors
When I calculate the next generation
Then the cell should be dead

Scenario
Given Rule 1: alive cell with 0 neighbors
When I calculate the next generation
Then the cell should be dead

I've written the variables in the scenario in bold here. That will become more clear later. I inserted "Rule 1" into the given just to make it easier to know what rule I'm on when a scenario fails.

I wrote scenarios for every condition for all 4 rules (it came out to 18) and put them in a file named "i_can_calculate_the_next_generation". JBehave requires scenarios be named all lower case with words separated by underscores. You can see all the scenarios and all the source code in my github repo for this project.

(If you're a Windows developer and you don't like the idea of files without extensions, I found a blog post that showed a customization that allowed JBehave to read ".scenario" files instead of files with no extension, but I decided not to bother with that for this first experiment.)

Once I have my scenarios written, it's time to code. JBehave requires that the class corresponding to the scenarios be the same words, but camel-case, and without underscores, so I created ICanCalculateTheNextGeneration.java

The entire class is:
import org.jbehave.scenario.Scenario;

public class ICanCalculateTheNextGeneration extends Scenario {
public ICanCalculateTheNextGeneration(){
super(new RuleEngineSteps());
}
}
Now for the meat of the code, starting with the implementation of RuleEngineSteps:

@Given("Rule $ruleNum: $aliveVal cell with $aliveCount neighbors")
public void aliveOrDeadCellWithPossiblySomeAliveNeighbors(int ruleNum, String initialLiveState, int numberOfAliveNeighbors){
ruleEngine = new ConwayRuleEngine();
cell = new Cell(initialLiveState.equals("alive"), numberOfAliveNeighbors);
}

@When ("I calculate the next generation")
public void iCalculateTheNextGeneration() {
ruleEngine.CalculateTheNextGeneration(cell);
}

@Then ("the cell should be $resultingAliveState")
public void theCellShouldBe(String expectedAliveState){
String actualAliveState = "alive";
if (!cell.isAlive()){
actualAliveState = "dead";
}
Assert.assertEquals(expectedAliveState, actualAliveState);
}


Look for a moment at how variables are used between the scenarios and the code. Remember I had: "Then the cell should be dead"? Look at the corresponding @Then in the code:
@Then ("the cell should be $resultingAliveState")
public void theCellShouldBe(String expectedAliveState){
Whatever I want as variable in the scenario, for example dead above, I name a variable starting with "$" in the attribute line and use the same name (without "$") as the parameter to the method.

Cell.java is basically only setters and getters. ConwayRuleEngine.java is where the business logic lives (Excuse the pun). Initially, CalculateTheNextGeneration() will do nothing.

When I run ICanCalculateTheNextGeneration.java as a JUnit test in Eclipse. I get a red bar. In the console I see:
(i_can_calculate_the_next_generation)
Scenario:
Given Rule 1: alive cell with 0 neighbors
When I calculate the next generation
Then the cell should be dead (FAILED)
and other failures. All that's left is the implementation of the business logic, which I'll leave as an exercise for you. (Or you can look at my code).

Once I got my code working, I refactored it. I've tried to reduce duplication and make the code very clean. I'd appreciate any comments you have.

Finally, I invite you to check out the Agile Skills Project, a non-commercial, community-based project which aims to establish a common baseline of the skills an Agile developer needs to have, skills possibly including TDD and BDD.

---------------------------
*A plug for CodeRetreat if you're not familiar with it: Patrick Welsh and Corey Haines have created a kind of one-day coding dojo that gives software developers who want to improve their craft a chance to practice agile skills like Pair Programming and TDD. We do 40 minute iterations on Conway's Game of Life, have mini-retrospectives, throw out our code before the next iteration, and have a retrospective of the whole event at the end of the day. CodeRetreats have been held in Java and Ruby (that I know of) and in several countries. If you haven't been to one, I encourage you to go to one.



1 comment:

  1. Nice, I think GoL is a great concept for experimenting with new techniques and tools like this.

    Thanks for the comment on my JBehave post; I've actually come up with a new way for cleaner test names and scenario names: http://wp.me/pEMsT-3P

    ReplyDelete