I downloaded JBehave 2.5 and refreshed my memory of the GoL rules:
- Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
- Any live cell with more than three live neighbours dies, as if by overcrowding.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any dead cell with exactly three live neighbours becomes a live cell.
- Given a live cell with fewer than two live neighbors, Then the cell is dead.
- Given a live cell with more than three live neighbors, Then the cell is dead.
- Given a live cell with two or three live neighbors, Then the cell is dead.
- Given a dead cell with exactly three live neighbors, Then the cell is alive.
- Given a live cell with fewer than two live neighbors, When I calculate the next generation, Then the cell is dead.
- Given a live cell with more than three live neighbors, When I calculate the next generation, Then the cell is dead.
- Given a live cell with two or three live neighbors, When I calculate the next generation, Then the cell is dead.
- Given a dead cell with exactly three live neighbors, When I calculate the next generation, Then the cell is alive.
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;Now for the meat of the code, starting with the implementation of RuleEngineSteps:
public class ICanCalculateTheNextGeneration extends Scenario {
public ICanCalculateTheNextGeneration(){
super(new 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")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.
public void theCellShouldBe(String expectedAliveState){
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)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).
Scenario:
Given Rule 1: alive cell with 0 neighbors
When I calculate the next generation
Then the cell should be dead (FAILED)
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.