Sunday, January 27, 2008

Sudoku Part 3: Defining The Generator Behavior


In part 1 (Defining The Solver Behavior) we examined how Behavior Driven Development could assist us in defining the specifications of a small portion of our application. Today we're going to take the same approach, but instead we're going to tackle the Generation portion of a Sudoku puzzle instead of the solving of the puzzle.

We're going to resume our conversations with our domain expert, and see what types of behaviors this new piece of functionality should include.

Needed Behavior

Just like last time, we'll have simple questions and simple answers which will later drive how we create and define our behavior based unit tests.

  • What is the result when a new puzzle is generated?
    • No column should have duplicate values
    • No row should have duplicate values
    • No region should have duplicate values
    • The puzzle should be uniquely solvable.
Really, that's it. We don't care too much about how the puzzle is generated, or what it looks like (at least at this point), so long as the puzzle itself can be solved.

Notice, that making the puzzle solvable would actually be quite a tricky requirement if we had not already built a Sudoku solver in part 2 (Implementing A Solver). Lucky for us, since we have a fully tested solver which we have proven works we can plug that implementation in to our behavior tests for the generator in order to verify the output. Note that really any implementation of an ISolver will suffice for our purposes, since our behavior tests are actually written against the interface and not any particular implementation.

The BDD Style Specifications

We're going to write our test class (or specification) in much the same way we did before. We try to match the discussion with the domain expert as close as we can, and then write relatively simple tests which we can use to ensure our implementations provide the correct behavior.


[TestClass]
public abstract class When_A_New_Puzzle_Is_Generated
{
protected Puzzle puzzle;
private IGenerator generator;
private ISolver solver;

protected When_A_New_Puzzle_Is_Generated(IGenerator generator)
{
this.generator = generator;
this.solver = new RecursiveSolver();
}

[TestInitialize]
public void Initialize()
{
puzzle = generator.Generate();
}

[TestMethod]
public void No_Column_Should_Have_Duplicate_Values()
{
foreach (Column col in puzzle.Columns)
{
List<Value> foundValues = new List<Value>();
foreach (Piece piece in col)
{
if (piece.AssignedValue.HasValue)
{
Assert.IsFalse(foundValues.Contains(piece.AssignedValue.Value));
foundValues.Add(piece.AssignedValue.Value);
}
}
}
}

[TestMethod]
public void No_Row_Should_Have_Duplicate_Values()
{
foreach (Row row in puzzle.Rows)
{
List<Value> foundValues = new List<Value>();
foreach (Piece piece in row)
{
if (piece.AssignedValue.HasValue)
{
Assert.IsFalse(foundValues.Contains(piece.AssignedValue.Value));
foundValues.Add(piece.AssignedValue.Value);
}
}
}
}

[TestMethod]
public void No_Region_Should_Have_Duplicate_Values()
{
foreach (Region region in puzzle.Regions)
{
List<Value> foundValues = new List<Value>();
foreach (Piece piece in region)
{
if (piece.AssignedValue.HasValue)
{
Assert.IsFalse(foundValues.Contains(piece.AssignedValue.Value));
foundValues.Add(piece.AssignedValue.Value);
}
}
}
}

[TestMethod]
public void The_Puzzle_Should_Be_Uniquely_Solvable()
{
Assert.IsNotNull(solver.Solve(puzzle));
}
}

No comments:

Blogger Syntax Highliter