I am playing around with the XNA Framework and in the process of writing a game, I need to perform in essence a graph traversal. Given a 3D board (of w x h x d cells), I need to from a starting position and "up" direction to go around the board and figure out all reachable locations. So If you have a cube in space and you start on one of the faces, I need to get all cells on the surface of the cube. It gets a bit more complicated once you throw in rules like "you can't traverse through walls" and "if you reach an edge, you in essence go over it and rotate" (sort of like going around the globe, but around a cube... if that makes sense).
Anyway... I am trying to do as much Test Driven Development as possible and this problem got me thinking. It's nothing new to write some code that will iterate through a graph, keeping track of visited cells and figuring out others, but I was trying to figure out how to do this in a testable manner without exposing all kinds of internals about the traversal class. After all, if it's private, it's an implementation detail. But I needed to say "do one pass and show me the node list" type of thing. So I decided to use .Net 2.0 iterator method instead. It allows you to create a function that returns an IEnumerable<T> and the iterate over the results within a foreach loop. Within the method you can use yield return statements to return values as they are generated and the method execution will continue with IEnumerable<T>.MoveNext() method is called. When there are no more results to return, a yield break statement is used to end the iteration process.
Here's what a method that returns nothing looks like.
internal IEnumerable<StarLocationInfo> GetStarLocations()
{
yield break;
}
As I progressed through writing various tests, I could simulate conditions such as "when on a plane, return the current position and four children next to it". In the spirit of TDD it could look something like this:
internal IEnumerable<StarLocationInfo> GetStarLocations()
{
yield return new StarLocationInfo(startPosition);
yield return new StarLocationInfo(startPosition + new Vector3(1, 0, 0));
yield return new StarLocationInfo(startPosition + new Vector3(0, 1, 0));
yield return new StarLocationInfo(startPosition + new Vector3(-1, 0, 0));
yield return new StarLocationInfo(startPosition + new Vector3(0, -1, 0));
yield break;
}
So all of this allowed me to write tests like this:
[Test]
public void OnPlaneFourChildrenAreReturned()
{
GameBoard testBoard = CreatePlane();
GameBoardStarTraverser traverser = new GameBoardStarTraverser(testBoard, new Vector3(2, 2, 0), new Vector3(0, 0, -1));
List<StarLocationInfo> expectedList = new List<StarLocationInfo>(5);
expectedList.Add(new StarLocationInfo(2, 2, 0));
expectedList.Add(new StarLocationInfo(1, 2, 0));
expectedList.Add(new StarLocationInfo(3, 2, 0));
expectedList.Add(new StarLocationInfo(2, 1, 0));
expectedList.Add(new StarLocationInfo(2, 3, 0));
RunTraversalAndVerifyExpectations(traverser, expectedList);
}
where the last function basically runs through the results of traverser.GetStartLocations() with a foreach loop and removes items from the expected list as it gets them from the enumerator. It also makes sure that all of the expected values were recorded.
In the end I ended up with a traversal algorithm that much resembles a typical graph traversal (its breadth first) and all of its details hidden away in a iterative method, yet I can test its behavior by setting the start conditions and essentially inspect its results as they come out. To sum up:
- I was able to use TDD methods to drive the behavior of the algorithm
- I kept the details of the implementation hidden, since all it takes to invoke the "traverser" is a single function call.
- I used a neat feature of .Net 2.0.
Note: this can be done with .Net 1.1 of course, but it's not as neat as one would need to implement both an IEnumerable and IEnumerator interfaces in a couple of classes.