Effective Tests: Test Doubles

On May 16, 2011, in Uncategorized, by derekgreer

In our last installment, we concluded our Test-First example which demonstrated the Test Driven Development process through the creation of a Tic-tac-toe component. When writing automated tests using either a Test-First or classic unit testing approach, it often becomes necessary to verify and/or exercise control over the interactions of a component with its collaborators. In this article, I’ll introduce a family of strategies for addressing these needs, known collectively as Test Doubles. The examples within this article will be presented using the Java programming language.

Doubles

The term “Test Double” was popularized by Gerard Meszaros in his book xUnit Test Patterns. Similar to the role of the “stunt double” in the movie industry in which a leading actor or actress is replaced in scenes requiring a more specialized level of training and/or physical ability, Test Doubles likewise play a substitute role in the orchestration of a System Under Test.

Test Doubles serve two primary roles within automated tests. First, they facilitate the ability to isolate portions of behavior being designed and/or tested from undesired influences of collaborating components. Second, they facilitate the ability to verify the collaboration of one component with another.

Isolating Behavior

There are two primary motivations for isolating the behavior being designed from influences of dependencies: Control and Feedback.

It is often necessary to exercise control over the behavior provided by dependencies of a System Under Test in order to effect a deterministic outcome or eliminate unwanted side-effects. When a real dependency can’t be adequately manipulated for these purposes, test doubles can provide control over how a dependency responds to consuming components.

A second motivation for isolating behavior is to aid in identifying the source of regressions within a system. By isolating a component completely from the behavior of its dependencies, the source of a failing test can more readily be identified when a regression of behavior is introduced.

Identifying Regression Sources

While test isolation aids in identifying the source of regressions, Extreme Programming (XP) offers an alternative process.

As discussed briefly in the series introduction, XP categories tests as Programmer Tests and Customer Tests rather than the categories of Unit, Integration or Acceptance Tests. One characteristic of Programmer Tests, which differs from classic unit testing, is the lack of emphasis on test isolation. Programmer tests are often written in the form of Component Tests which test subsystems within an application rather than designing/testing the individual units comprising the overall system. One issue this presents is a decreased ability to identify the source of a newly introduced regression based on a failing test due to the fact that the regression may have occurred in any one of the components exercised during the test. Another consequence of this approach is a potential increase in the number of tests which may fail due to a single regression being introduced. Since a single class may be used by multiple subsystems, a regression in behavior of a single class can potentially break the tests for every component which consumes that class.

The strategy used for identifying sources of regressions within a system when writing Programmer Tests is to rely upon knowledge of the last change made within the system. This becomes a non-issue when using emergent design strategies like Test-Driven Development since the addition or modification of behavior within a system tends to happen in very small steps. The XP practice of Pair-Programming also helps to mitigate such issues due to an increase in the number of participants during the design process. Practices such as Continuous Integration and associated check-in guidelines (e.g. The Check-in Dance) also help to mitigate issues with identifying sources of regression. The topic of Programmer Tests will be discussed as a separate topic later in the series.

 

Verifying Collaboration

To maximize maintainability, we should strive to keep our tests as decoupled from implementation details as possible. Unfortunately, the behavior of a component being designed can’t always be verified through the component’s public interface alone. In such cases, test doubles aid in verifying the indirect outputs of a System Under Test. By replacing a real dependency with one of several test double strategies, the interactions of a component with the double can be verified by the test.

 

Test Double Types

While a number of variations on test double patterns exist, the following presents the five primary types of test doubles: Stubs, Fakes, Dummies, Spies and Mocks.

Stubs

When writing specifications, the System Under Test often collaborates with dependencies which need to be supplied as part of the setup or interaction stages of a specification. In some cases, the verification of a component’s behavior depends upon providing specific indirect inputs which can’t be controlled by using real dependencies. Test doubles which serve as substitutes for controlling the indirect input to a System Under Test are known as Test Stubs.

The following example illustrates the use of a Test Stub within the context of a package shipment rate calculator specification. In this example, a feature is specified for a shipment application to allow customers to inquire about rates based upon a set of shipment details (e.g. weight, contents, desired delivery time, etc.) and a base rate structure (flat rate, delivery time-based rate, etc.).

In the following listing, a RateCalculator has a dependency upon an abstract BaseRateStructure implementation which is used to calculate the actual rate:

public class RateCalculator {

	private BaseRateStructure baseRateStructure;
	private ShipmentDetails shipmentDetails;

	public RateCalculator(BaseRateStructure baseRateStructure, ShipmentDetails shipmentDetails) {
		this.baseRateStructure = baseRateStructure;
		this.shipmentDetails = shipmentDetails;
	}

	public BigDecimal CalculateRateFor(ShipmentDetails shipmentDetails) {
		BigDecimal rate =  baseRateStructure.calculateRateFor(shipmentDetails);

		// other processing ...
		
		return rate;
	}
}

The following shows the BaseRateStructure contract which defines a method that accepts shipment details and returns a rate:

public abstract class BaseRateStructure {
	public abstract BigDecimal calculateRateFor(ShipmentDetails shipmentDetails);
}

To ensure a deterministic outcome, the specification used to drive the feature’s development can substitute a BaseRateStrctureStub which will always return the configured value:

public class RateCalculatorSpecifications {

	public static class when_calculating_a_shipment_rate extends ContextSpecification {

		static Reference<BigDecimal> rate = new Reference<BigDecimal>(BigDecimal.ZERO);
		static ShipmentDetails shipmentDetails;
		static RateCalculator calculator;

		Establish context = new Establish() {
			public void execute() {
				shipmentDetails = new ShipmentDetails();
				BaseRateStructure baseRateStructureStub = new BaseRateStructureStub(10.0);
				calculator = new RateCalculator(baseRateStructureStub, shipmentDetails);
			}
		};

		Because of = new Because() {
			protected void execute() {
				rate.setValue(calculator.CalculateRateFor(shipmentDetails));
			}
		};

		It should_return_the_expected_rate = assertThat(rate).isEqualTo(new BigDecimal(10.0));
	}
}

For this specificaiton, the BaseRateStructureStub merely accepts a value as a constructor parameter and returns the value when the calculateRateFor() method is called:

public class BaseRateStructureStub extends BaseRateStructure {

	BigDecimal value;

	public BaseRateStructureStub(double value) {
		this.value = new BigDecimal(value);
	}

	public BigDecimal calculateRateFor(ShipmentDetails shipmentDetails) {
		return value;
	}
}

 

Fakes

While it isn’t always necessary to control the indirect inputs of collaborating dependencies to ensure a deterministic outcome, some real components may have other undesired side-effects which make their use prohibitive. For example, components which rely upon an external data store for persistence concerns can significant impact the speed of a test suite which tends to discourage frequent regression testing during development. In cases such as these, a lighter-weight version of the real dependency can be substituted which provides the behavior needed by the specification without the undesired side-effects. Test doubles which provide a simplified implementation of a real dependency for these purposes are referred to as Fakes.

In the following example, a feature is specified for an application serving as a third-party distributor for the sale of tickets to a local community arts theatre to display the itemized commission amount on the receipt. The theatre provides a Web service which handles the payment processing and ticket distribution process, but does not provide a test environment for vendors to use for integration testing purposes. To test the third-party application’s behavior without incurring the side-effects of using the real Web service, a Fake service can be substituted in its place.

Consider that the theatre’s service interface is as follows:

public abstract class TheatreService {

	public abstract TheatreReceipt ProcessOrder(TicketOrder ticketOrder);
	
	public abstract CancellationReceipt CancelOrder(int orderId);

	// other methods ...
}

To provide the expected behavior without the undesired side-effects, a fake version of the service can be implemented:

public class TheatreServiceFake extends TheatreService {

	// private field declarations used in light implementation ...
	
	public TheatreReceipt ProcessOrder(TicketOrder ticketOrder) {

		// light implementation details ...

		TheatreReceipt receipt = createReceipt();
		return new TheatreReceipt();
	}

	public CancellationReceipt CancelOrder(int orderId) {

		// light implementation details ...

		CancellationReceipt receipt = createCancellationReceipt();
		return receipt;
	}

	// private methods …

}

The fake service may then be supplied to a PaymentProcessor class within the set up phase of the specification:

public class PaymentProcessorSpecifications {
	public static class when_processing_a_ticket_sale extends ContextSpecification {

		static Reference<BigDecimal> receipt = new Reference<BigDecimal>(BigDecimal.ZERO);
		static PaymentProcessor processor;

		Establish context = new Establish() {
			protected void execute() {
				processor = new PaymentProcessor(new TheatreServiceFake());			
			}
		};

		Because of = new Because() {
			protected void execute() {
				receipt.setValue(processor.ProcessOrder(new Order(1)).getCommission());
			}
		};

		It should_return_a_receipt_with_itemized_commission =
				assertThat(receipt).isEqualTo(new BigDecimal(1.00));
	}
}

 

Dummies

There are times when a dependency is required in order to instantiate the System Under Test, but which isn’t required for the behavior being designed. If use of the real dependency is prohibitive in such a case, a Test Double with no behavior can be used. Test Doubles which serve only to provide mandatory instances of dependencies are referred to as Test Dummies.

The following example illustrates the use of a Test Dummy within the context of a specification for a ShipmentManifest class. The specification concerns verification of the class’ behavior when adding new packages, but no message exchange is conducted between the manifest and the package during execution of the addPackage() method.

public class ShipmentManifestSpecifications {
	public static class when_adding_packages_to_the_shipment_manifest extends ContextSpecification {

		static private ShipmentManifest manifest;

		Establish context = new Establish() {
			protected void execute() {
				manifest = new ShipmentManifest();
			}
		};

		Because of = new Because() {
			protected void execute() {
				manifest.addPackage(new DummyPackage());
			}
		};

		It should_update_the_total_package_count = new It() {
			protected void execute() {
				assert manifest.getPackageCount() == 1;
			}
		};	   
	}
}

 

Test Spies

In some cases, a feature requires collaborative behavior between the System Under Test and its dependencies which can’t be verified through its public interface. One approach to verifying such behavior is to substitute the associated dependency with a test double which stores information about the messages received from the System Under Test. Test doubles which record information about the indirect outputs from the System Under Test for later verification by the specification are referred to as Test Spies.

In the following example, a feature is specified for an online car sales application to keep an audit trail of all car searches. This information will be used later to help inform purchases made at auction sales based upon which makes, models and price ranges are the most highly sought in the area.

The following listing contains the specification which installs the Test Spy during the context setup phase and examines the state of the Test Spy in the observation stage:

public class SearchServiceSpecifications {
	public static class when_a_customer_searches_for_an_automobile extends ContextSpecification {

		static AuditServiceSpy auditServiceSpy;
		static SearchService searchService;

		Establish context = new Establish() {
			protected void execute() {
				auditServiceSpy = new AuditServiceSpy();
				searchService = new SearchService(auditServiceSpy);
			}
		};

		Because of = new Because() {
			protected void execute() {
				searchService.search(new MakeSearch("Ford"));
			}
		};

		It should_report_the_search_to_the_audit_service = new It() {
			protected void execute() {
				assert auditServiceSpy.WasSearchCalledOnce() == true : "Expected true, but was false.";
			}
		};
	}
}

For this specification, the Test Spy is implemented to simply increment a private field each time the recordSearch() method is called, allowing the specification to then call the WasSearchCalledOnce() method in an observation to verify the expected behavior:

public class AuditServiceSpy extends AuditService{
	private int calls;

	public boolean WasSearchCalledOnce() {
		return calls == 1;
	}

	public void recordSearch(Search criteria) {
		calls++;
	}
}

 

Mocks

Another technique for verifying the interaction of a System Under Test with its dependencies is to create a test double which encapsulates the desired verification within the test double itself. Test Doubles which validate the interaction between a System Under Test and the test double are referred to as Mocks.

Mock validation falls into two categories: Mock Stories and Mock Observations.

Mock Stories

Mock Stories are a scripted set of expected interactions between the Mock and the System Under Test. Using this strategy, the exact set of interactions are accounted for within the Mock object. Upon executing the specification, any deviation from the script results in an exception.

Mock Observations

Mock Observations are discrete verifications of individual interactions between the Mock and the System Under Test. Using this strategy, the interactions pertinent to the specification context are verified during the observation stage of the specification.

Mock Observations and Test Spies

The use of Mock Observations in practice looks very similar to the use of Test Spies. The distinction between the two is whether a method is called on the Mock to assert that a particular interaction occurred or whether state is retrieved from the Test Spy to assert that a particular interaction occurred.

To illustrate the concept of Mock objects, the following shows the previous example implemented using a Mock Observation instead of a Test Spy.

In the following listing, a second specification is added to the previous SearchServiceSpecifications class which replaces the use of the Test Spy with a Mock:

public class SearchServiceSpecifications {
	...   

	public static class when_a_customer_searches_for_an_automobile_2 extends ContextSpecification {
		static AuditServiceMock auditServiceMock;
		static SearchService searchService;

		Establish context = new Establish() {
			protected void execute() {
				auditServiceMock = new AuditServiceMock();
				searchService = new SearchService(auditServiceMock);
			}
		};

		Because of = new Because() {
			protected void execute() {
				searchService.search(new MakeSearch("Ford"));
			}
		};

		It should_report_the_search_to_the_audit_service = new It() {
			protected void execute() {
				auditServiceMock.verifySearchWasCalledOnce();
			}
		};
	}
}

The Mock implementation is similar to the Test Spy, but encapsulates the assert call within the verifySearchWasCalledOnce() method rather than returning the recorded state for the specification to assert:

public class AuditServiceMock extends AuditService {
	private int calls;

	public void verifySearchWasCalledOnce() {
		assert calls == 1;
	}

	public void recordSearch(Search criteria) {
		super.recordSearch(criteria);
		calls++;
	}
}

While both the Mock Observation and Mock Story approaches can be implemented using custom Mock classes, it is generally easier to leverage a Mocking Framework.

Mocking Frameworks

A Mocking Framework is testing library written to facilitate the creation of Test Doubles with programmable expectations. Rather than writing a custom Mock object for each unique testing scenario, Mock frameworks allow the developer to specify the expected interactions within the context setup phase of the specification.

To illustrate the use of a Mocking Framework, the following listing presents the previous example implemented using the Java Mockito framework rather than a custom Mock object:

public class SearchServiceSpecifications {
	... 

	public static class when_a_customer_searches_for_an_automobile_3 extends ContextSpecification {
		static AuditService auditServiceMock;
		static SearchService searchService;

		Establish context = new Establish() {
			protected void execute() {
				auditServiceMock = mock(AuditService.class);
				searchService = new SearchService(auditServiceMock);
			}
		};

		Because of = new Because() {
			protected void execute() {
				searchService.search(new MakeSearch("Ford"));
			}
		};

		It should_report_the_search_to_the_audit_service = new It() {
			protected void execute() {
				verify(auditServiceMock).recordSearch(any(Search.class));
			}
		};
	}
}

In this example, the observation stage of the specification uses Mockito’s static verify() method to assert that the recordSearch() method was called with any instance of the Search class.

In many circumstances, messages are exchanged between a System Under Test and its dependencies. For this reason, Mock objects often need to return stub values when called by the System Under Test. As a consequence, most mocking frameworks can be used to also create Test Doubles whose role is only to serve as a Test Stub. Mocking frameworks which facilitate Mock Observations can also be used to easily create Test Dummies.

Conclusion

In this article, the five primary types of Test Doubles were presented: Stubs, Fakes, Dummies, Spies, and Mocks. Next time, we’ll discuss strategies for using Test Doubles effectively.

Tagged with:  

In the last installment of our series, we continued our Test-First example by addressing issues filed by the QA team. While I thought we had covered the reported defects pretty well, I wanted to do a little smoke testing against the full application to ensure we hadn’t missed anything. It’s probably good that I did, because I ended up finding one last case that didn’t met the original requirements.

In the course of my testing, I discovered that there was an additional way for a player to beat the game by setting up multiple winning paths. Here’s the steps I took:

tic-tac-toe-ms

In the depicted steps, my first move was to choose the right edge of the board. This happens to be a strategy the articles I consulted with advised against and for which no counter strategy was provided. By choosing a second position which avoided triggering the game’s existing defensive strategies, I was able to set up multiple winning paths by countering the next two choices by the game. The game should be able to counter this strategy by blocking at intersections, so let’s fix this one last issue.

First, let’s start with a new test which describes the behavior we want:

   [TestClass]
   public class When_the_player_has_two_paths_which_intersect_at_an_available_position
   {
       [TestMethod]
       public void it_should_select_the_intersecting_position()
       {
       }
   }

 

Next, let’s setup the context and assertion that reflects the way I was able to beat the game:

   [TestClass]
   public class When_the_player_has_two_paths_which_intersect_at_an_available_position
   {
       [TestMethod]
       public void it_should_select_the_intersecting_position()
       {
           GameAdvisor gameAdvisor = new GameAdvisor();
           var selection = gameAdvisor.WithLayout("OXO\0\0XX\0\0").SelectBestMoveForPlayer('O');
           Assert.AreEqual(8, selection);
       }
   }

 

Now, let’s run our test suite:

 
When_the_player_has_two_paths_which_intersect_at_an_available_position Failed it_should_select_the_intersecting_position Assert.AreEqual failed. Expected:<8>. Actual:<9>.

 

Let’s get this to pass quickly by returning the expected position for this exact layout:

       class PositionSelector : IPositionSelector
       {
           …

           public int SelectBestMoveForPlayer(char player)
           {
               if (_layout == "OXO\0\0XX\0\0")
                   return 8;

               return GetPositionThreateningPlayer(player) ??
                      GetNextWinningMoveForPlayer(player) ??
                      Enumerable.Range(1, 9).First(position => _layout[position - 1] == Game.EmptyValue);
           }

           ...
       }

 

 

 

To remove the duplication of the layout, let’s start taking small steps toward a final solution. First, let’s create a new DefensiveStrategy for dealing with positions that might allow the opponent to set up multiple winning paths:

       class PositionSelector : IPositionSelector
       {
           …

           int? GetPositionThreateningPlayer(char player)
           {
               return new DefensiveStrategy[]
                          {
                              PathCompletionStrategy,
                              SimpleBlockStrategy,
                              FirstMoveCounterCenterStrategy,
                              SecondMoveDiagonalCounterStrategy,
                              MultiPathCounterStrategy
                          }
                   .Select(strategy => strategy(player)).FirstOrDefault(p => p.HasValue);
           }

           int? MultiPathCounterStrategy(char player)
           {
               if (_layout == "OXO\0\0XX\0\0")
                   return 8;

               return null;
           }


           ...
       }

 

Next, let’s work through the steps we’ll need to arrive at this value. First, we’ll need to get a list of all the available paths for the opponent:

           int? MultiPathCounterStrategy(char player)
           {

               char opponentValue = GetOpponentValue(player);

               List<int[]> opponentPaths = GetAvailablePathsFor(opponentValue);

               if (_layout == "OXO\0\0XX\0\0")
                   return 8;

               return null;
           }

 

Next, we want to filter this list down to the paths the opponent has already started:

           int? MultiPathCounterStrategy(char player)
           {

               char opponentValue = GetOpponentValue(player);

               List<int[]> opponentPaths = GetAvailablePathsFor(opponentValue);

               IEnumerable startedPaths =
                   opponentPaths.Where(path => new string(path.Select(p => _layout[p - 1]).ToArray())
                                                   .Count(value => value == opponentValue) >= 1);

               if (_layout == "OXO\0\0XX\0\0")
                   return 8;

               return null;
           }

 

Lastly, we need to compare all the paths to each other, find the ones that have positions in common, and pick the position in common for the first pair. Since we’ll be calling this after our other strategy for dealing with multiple winning paths as the result of the opponent choosing opposite corners, this should only ever find one pair. This logic seems a little more complicated, so I’m going to write it in LINQ rather than using extension methods this time:

           int? MultiPathCounterStrategy(char player)
           {

               char opponentValue = GetOpponentValue(player);

               List<int[]> opponentPaths = GetAvailablePathsFor(opponentValue);

               IEnumerable startedPaths =
                   opponentPaths.Where(path => new string(path.Select(p => _layout[p - 1]).ToArray())
                                                   .Count(value => value == opponentValue) >= 1);

               return (from path in startedPaths
                       from position in path
                       from otherPath in startedPaths.SkipWhile(otherPath => otherPath == path)
                       from otherPosition in otherPath
                       where otherPosition == position
                       where _layout[position - 1] == Game.EmptyValue
                       select new int?(position)).FirstOrDefault();
           }

 

Let’s run the tests again and see what happens:

 

 

It passes! We can now release the new version of our component to be integrated into the next build.

 

Conclusion

We’ve finally come to the conclusion of our Test-First example. Along the way, we followed the Test-Driven Development practice of writing a failing test first, making the test pass as quickly as possible, and refactoring to remove duplication and to clarify intent. When we wrote a failing test, we made sure each test was properly verifying the expected results before attempting to add new behavior to the system. To get our tests passing quickly, we used obvious implementations when we were confident about our approach and felt we could achieve it quickly, but used fake implementations when we weren’t as confident or felt the implementation might take some time. When we wrote a new test to capture new requirements that were already present in the system, we temporarily disabled the behavior of the system to ensure our tests were validating the expected behavior correctly. Throughout our effort, we weren’t afraid to take small steps and strove to do simple things.

While we made some mistakes along the way and discovered opportunities for further improvement, using the Test Driven Development approach aided in our ability to produce working, maintainable software that matters.

Next time, we’ll discuss concepts and strategies for writing tests for collaborating components.

Tagged with:  

In part 8 of our series, we continued with our Test-First example by addressing issues filed by the UI team. To conclude our example, we’ll finish the remaining issues this time.

Here’s what we have left:

Issue Description Owner
Defect The player can always win by choosing positions 1, 5, 2, and 8. The game should prevent the player from winning. QA Team
Defect The game throws an InvalidOperationException when choosing positions 1, 2, 5, and 9. QA Team
Defect The game makes a move after the player wins. QA Team
Defect After letting the game win by choosing positions 4, 7, 8, and 6, choosing the last position of 3 throws an InvalidOperationException. QA Team
Defect When trying to let the game win by choosing positions 1, 7, and 8, the game chose positions 4, 5, and 9 instead of completing the winning sequence 4, 5, 6. QA Team

Let’s get started with the first one:

Issue Description Owner
Defect The player can always win by choosing positions 1, 5, 2, and 8. The game should prevent the player from winning. QA Team

This sounds like our game isn’t blocking correctly. After some analysis, the problem appears to be that certain strategies can lead to multiple winning choices which aren’t handled by our blocking strategy. Our game was designed to block when the player’s next move could result in a win, but it wasn’t designed to guard against moves that might lead to multiple winning paths.

After doing some research, I discovered several websites that discuss the defensive strategies a player should take when playing Tic-tac-toe. While the sites I found spell out each step in detail, I think I’ve condensed the rules we need down to the following:

  • When selecting your first position, always choose a corner or the center position. When the opponent goes first and has chosen either a corner or the center, choose the alternate of the two choices.
  • If the player’s second move aligns two of their corners diagonally, choose an edge
  • When you don’t need to block, prefer corners to edges.

In theory, adding these strategies to our game would mean that a player would never be able to win, so I confirmed that this was indeed what the customer intended by the original requirements. Based on that information, let’s get started.

We already have a context for describing the behavior that is expected when the game goes first, so let’s review our existing test:

[TestClass] public class When_the_game_goes_first { [TestMethod] public void it_should_put_an_X_in_one_of_the_available_positions() { var game = new Game(); game.GoFirst(); Assert.IsTrue(Enumerable.Range(1, 9).Any(position => game.GetPosition(position).Equals('X'))); } }

This specification says that the game should put an ‘X’ in one of the available positions. What we now want it to do is to put an ‘X’ in center or one of the corner positions. Therefore, we’ll change the name of the test. This test was also written before we created our GameAdvisor, so let’s change the System Under Test to that as well:

[TestClass] public class When_the_game_goes_first { [TestMethod] public void it_should_put_an_X_in_a_corner_or_the_center() { var gameAdvisor = new GameAdvisor(); int selection = gameAdvisor.WithLayout("\0\0\0\0\0\0\0\0\0").SelectBestMoveForPlayer('X'); Assert.IsTrue(new[]{1, 3, 5, 7, 9}.Any(position => position == selection)); } }

 

 

Our test passes, but let’s make sure it’s actually verifying the behavior correctly by changing the GameAdvisor to always return the second position:

public int SelectBestMoveForPlayer(char player) { return 2; return GetPositionThreateningPlayer(player) ?? GetNextWinningMoveForPlayer(player); }

 
When_the_game_goes_first Failed it_should_put_an_X_in_a_corner_or_the_center Assert.IsTrue failed.

Our test appears to be working correctly, so we’ll put the code back like it was.

 

 

The practice of breaking a passing test to watch it fail is a useful strategy for ensuring we aren’t writing self-passing tests (i.e. tests that always pass due to a defect in the test implementation), or to verify that the test communicates regression in a clear way.

Should we keep tests which pass unexpectedly? While it’s good to delete tests which describe behavior that is no longer applicable or which is explicitly or implicitly covered by another test, we should keep tests which describe important behavior that is coincidentally facilitated by the system. While our GameAdvisor happens to meet our revised specification, it does so because the order of our recommended paths happened to coincide with this requirement, not because anything required it to do so. Therefore, we should keep this test, both because we want to guard against this behavior changing and because it serves as useful documentation of the system’s expectations.

Next, let’s create a test which describes what the game’s first selection should be if a corner is already occupied:

[TestClass] public class When_the_game_selects_its_first_position_where_a_corner_is_occupied { [TestMethod] public void it_should_choose_the_center() { var gameAdvisor = new GameAdvisor(); int selection = gameAdvisor.WithLayout("X\0\0\0\0\0\0\0\0").SelectBestMoveForPlayer('O'); Assert.AreEqual(5, selection); } }

Running this test results in the following:

 
When_the_game_selects_its_first_position_where_a_corner_is_occupied Failed it_should_choose_the_center Assert.AreEqual failed. Expected:<5>. Actual:<4>.

Since I’m not quite sure how best to proceed, I’m going to take the easy route and modify the SelectBestMoveForPlayer() method to return a 5 when the layout matches the one we’re testing for:

public int SelectBestMoveForPlayer(char player) { if (_layout == "X\0\0\0\0\0\0\0\0") return 5; return GetPositionThreateningPlayer(player) ?? GetNextWinningMoveForPlayer(player); }

 

 

Now let’s work on a real implementation. Since selecting the middle position based on the opponent’s choices is a defensive move, it sounds like the logic for determining this behavior belongs to the GetPositionThreateningPlayer() method. Let’s modify this method to select the middle position if the opponent has a corner and this is the first move for the player being advised:

int? GetPositionThreateningPlayer(char player) { char opponentValue = (player == 'X') ? 'O' : 'X'; string opponentLayout = _layout.Replace(opponentValue, 'T'); List<int[]> availableOpponentPaths = GetAvailablePathsFor(opponentValue); int[] threatingPath = availableOpponentPaths .Where(path => new string(path.Select(p => opponentLayout[p - 1]).ToArray()) .Count(c => c == 'T') == 2).FirstOrDefault(); if (threatingPath != null) { return threatingPath.First(position => opponentLayout[position - 1] == '\0'); } if (_layout.Count(position => position == player) == 0 && new[] {0, 2, 6, 8}.Any(position => opponentLayout[position] == 'T')) { return 5; } return null; }

 

 

Now let’s refactor. While I don’t see any real duplication, I think our code would be more descriptive if we encapsulate these two paths to a list of “defensive strategies”:

class PositionSelector : IPositionSelector { ... int? GetPositionThreateningPlayer(char player) { return new DefensiveStrategy[] { SimpleBlockStrategy, FirstMoveCounterCenterStrategy } .Select(strategy => strategy(player)).FirstOrDefault(p => p.HasValue); } int? SimpleBlockStrategy(char player) { char opponentValue = (player == 'X') ? 'O' : 'X'; string opponentLayout = _layout.Replace(opponentValue, 'T'); List<int[]> availableOpponentPaths = GetAvailablePathsFor(opponentValue); int[] threatingPath = availableOpponentPaths .Where(path => new string(path.Select(p => opponentLayout[p - 1]).ToArray()) .Count(c => c == 'T') == 2).FirstOrDefault(); if (threatingPath != null) { return threatingPath.First(position => opponentLayout[position - 1] == '\0'); } return null; } int? FirstMoveCounterCenterStrategy(char player) { int? value = null; char opponentValue = (player == 'X') ? 'O' : 'X'; string opponentLayout = _layout.Replace(opponentValue, 'T'); if (_layout.Count(position => position == player) == 0 && new[] {0, 2, 6, 8}.Any(position => opponentLayout[position] == 'T')) { value = 5; } return value; } ... delegate int? DefensiveStrategy(char player); }

 

 

Now we have a bit of duplication for determining the opponent’s value, so let’s factor that out:

int? SimpleBlockStrategy(char player) { char opponentValue = GetOpponentValue(player); string opponentLayout = _layout.Replace(opponentValue, 'T'); List<int[]> availableOpponentPaths = GetAvailablePathsFor(opponentValue); int[] threatingPath = availableOpponentPaths .Where(path => new string(path.Select(p => opponentLayout[p - 1]).ToArray()) .Count(c => c == 'T') == 2).FirstOrDefault(); if (threatingPath != null) { return threatingPath.First(position => opponentLayout[position - 1] == '\0'); } return null; } int? FirstMoveCounterCenterStrategy(char player) { int? value = null; string opponentLayout = _layout.Replace(GetOpponentValue(player), 'T'); if (_layout.Count(position => position == player) == 0 && new[] {0, 2, 6, 8}.Any(position => opponentLayout[position] == 'T')) { value = 5; } return value; } static char GetOpponentValue(char player) { return (player == 'X') ? 'O' : 'X'; }

 

 

While reviewing this code, I noticed that neither of these methods, nor the GetNextWinningMoveForPlayer() method appear to require the opponentLayout string to be created. This appears to be an artifact left over from a much earlier refactoring that somehow went unnoticed. Let’s go ahead and remove the use of this variable and replace the generic token character ‘T’ we were using with the actual opponent value:

int GetNextWinningMoveForPlayer(char player) { List<int[]> availablePaths = GetAvailablePathsFor(player); int[] bestSlice = availablePaths.OrderByDescending( path => path.Count(p => _layout[p - 1] == player)).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); } ... int? SimpleBlockStrategy(char player) { char opponentValue = GetOpponentValue(player); List<int[]> availableOpponentPaths = GetAvailablePathsFor(opponentValue); int[] threatingPath = availableOpponentPaths .Where(path => new string(path.Select(p => _layout[p - 1]).ToArray()) .Count(c => c == opponentValue) == 2).FirstOrDefault(); if (threatingPath != null) { return threatingPath.First(position => _layout[position - 1] == '\0'); } return null; } int? FirstMoveCounterCenterStrategy(char player) { int? value = null; if (_layout.Count(position => position == player) == 0 && new[] {0, 2, 6, 8}.Any(position => _layout[position] == GetOpponentValue(player))) { value = 5; } return value; }

Let’s move on to the alternate first move strategy: Choosing the corner if the opponent has chosen the center:

[TestClass] public class When_the_game_selects_its_first_position_where_the_center_is_occupied { [TestMethod] public void it_should_choose_a_corner() { var gameAdvisor = new GameAdvisor(); int selection = gameAdvisor.WithLayout("\0\0\0\0X\0\0\0\0").SelectBestMoveForPlayer('O'); Assert.IsTrue(new[] {1, 3, 7, 9}.Any(position => position == selection)); } }

 

 

This test already passes, so let’s make sure we’ve written it correctly:

public int SelectBestMoveForPlayer(char player) { return 1; return GetPositionThreateningPlayer(player) ?? GetNextWinningMoveForPlayer(player); }

 
When_the_game_selects_its_first_position_where_the_center_is_occupied Failed it_should_choose_a_corner Assert.IsTrue failed.

The test looks good, so let’s put things back:

 

 

Our next defensive strategy is to chose an edge position (i.e. a non-corner, non-center position) if the player’s second move aligns two of their corners diagonally. This strategy prevents one of the ways an opponent can set up two non-diagonal winning paths. Here’s our test:

[TestClass] public class When_the_game_selects_its_second_position_where_the_player_chooses_opposite_diagonal_corners { [TestMethod] public void it_should_choose_an_edge() { var gameAdvisor = new GameAdvisor(); int selection = gameAdvisor.WithLayout("\0\0X\0O\0X\0\0").SelectBestMoveForPlayer('O'); Assert.IsTrue(new[] { 2, 4, 6, 8 }.Any(position => position == selection)); } }

 

 

Interestingly, this test also already passes. This must be due to the fact that our GameAdvisor always selects position 4 as its choice if the first row is occupied. It’s possible that the behavior of the GameAdvisor could change in the future in such a way as to allow this condition to be met but not a horizontal alignment in the opposite direction, so let’s change this test to guard against both conditions:

[TestClass] public class When_the_game_selects_its_second_position_where_the_player_chooses_opposite_diagonal_corners { [TestMethod] public void it_should_choose_an_edge() { var gameAdvisor = new GameAdvisor(); new[] { "\0\0X\0O\0X\0\0", "X\0\0\0O\0\0\0X" }.ToList().ForEach(layout => { int selection = gameAdvisor.WithLayout(layout).SelectBestMoveForPlayer('O'); Assert.IsTrue(new[] { 2, 4, 6, 8 }.Any(position => position == selection)); }); } }

 

 

Let’s make sure the test is written correctly for both conditions:

public int SelectBestMoveForPlayer(char player) { if (_layout == "\0\0X\0O\0X\0\0" || _layout == "X\0\0\0O\0\0\0X") return 1; return GetPositionThreateningPlayer(player) ?? GetNextWinningMoveForPlayer(player); }

 
When_the_game_selects_its_second_position_where_the_player_chooses_opposite_diagonal_corners Failed it_should_choose_an_edge Assert.IsTrue failed.

This seems to work, so let’s revert it:

 

 

Our last change for improving our defensive strategy is to modify the game to prefer corners to edges when we don’t need to block. This prevents a situation where an opponent can align one diagonal path and either a horizontal or vertical winning path. Here’s our test:

[TestClass] public class When_a_game_selects_an_offensive_position { [TestMethod] public void it_should_prefer_corners_to_edges() { var gameAdvisor = new GameAdvisor(); int selection = gameAdvisor.WithLayout("\0\0X\0X\0O\0\0").SelectBestMoveForPlayer('O'); Assert.IsTrue(new[] {1, 9}.Any(position => position == selection)); } }

 
When_a_game_selects_an_offensive_position Failed it_should_prefer_corners_to_edges Assert.IsTrue failed.

To make this pass, we should only have to rearrange our winning positions array to put the edges as the last position selected and to move any paths with a first or second edge position to the bottom of the list:

class PositionSelector : IPositionSelector { static readonly int[][] _winningPositions = new[] { new[] {1, 3, 2}, new[] {7, 9, 8}, new[] {1, 7, 4}, new[] {3, 9, 6}, new[] {1, 9, 5}, new[] {3, 7, 5}, new[] {2, 8, 5}, new[] {4, 6, 5} }; … }

Let’s run our test to see what happens:

 
When_a_game_selects_an_offensive_position Failed it_should_choose_an_edge Assert.IsTrue failed.

Our theory seems to have held up, but we ended up breaking our last test. Let’s review the broken test:

[TestClass] public class When_the_game_selects_its_second_position_where_the_player_chooses_opposite_diagonal_corners { [TestMethod] public void it_should_choose_an_edge() { var gameAdvisor = new GameAdvisor(); new[] { "\0\0X\0O\0X\0\0", "X\0\0\0O\0\0\0X" }.ToList().ForEach(layout => { int selection = gameAdvisor.WithLayout(layout).SelectBestMoveForPlayer('O'); Assert.IsTrue(new[] {2, 4, 6, 8}.Any(position => position == selection)); }); } }

Unfortunately, that test represents multiple layouts, so we can’t tell exactly which layout failed. Before proceeding further, let’s see if we can address this problem.

Perhaps the most straightforward way of addressing this issue would be to make two separate tests for each of these conditions, but from a documentation perspective I think it’s more clear to have one test that concerns what to do when the player chooses opposite diagonal corners rather than two describing each of the cases. Viewed in isolation, it may not be as clear that both really represent the same strategy for a different orientations of the board. Let’s stay with our existing approach for this test, but modify it so the exception tells us exactly what scenario is causing an issue. We can achieve this by adding a description to our assertion and aggregating any exception messages together to display once all the cases have been run. We’ll use a exception test helper to cut down on the try/catch noise:

[TestClass] public class When_the_game_selects_its_second_position_where_the_player_chooses_opposite_diagonal_corners { [TestMethod] public void it_should_choose_an_edge() { var gameAdvisor = new GameAdvisor(); var exceptions = new List<string>(); new[] { "\0\0X\0O\0X\0\0", "X\0\0\0O\0\0\0X" }.ToList().ForEach(layout => { int selection = gameAdvisor.WithLayout(layout).SelectBestMoveForPlayer('O'); var exception = Catch.Exception(() => Assert.IsTrue(new[] {2, 4, 6, 8}.Any(position => position == selection), string.Format("edge not selected for layout:{0}", layout))); if(exception != null) exceptions.Add(exception.Message); }); if (exceptions.Count > 0) throw new AssertFailedException(string.Join(Environment.NewLine, exceptions)); } } public static class Catch { public static Exception Exception(Action action) { try { action(); } catch (Exception e) { return e; } return null; } }

Let’s run our tests again:

 
When_the_game_selects_its_second_position_where_the_player_chooses_opposite_diagonal_corners Failed it_should_choose_an_edge Assert.IsTrue failed. edge not selected for layout:\0\0X\0O\0X\0\0 Assert.IsTrue failed. edge not selected for layout:X\0\0\0O\0\0\0X

This produces the result I was looking for, but the test seems a little obscure. Let’s leave it like this for now, but we’ll discuss techniques for cleaning this up later in our series.

Getting back to the main issue, this test is failing because the GameAdvisor was fulfilling this specification by relying upon the ordering of the original winning patterns array. This was a perfectly acceptable strategy at the time, but we’ll need to add new behavior now that we’ve changed how this works internally.

To get the test passing, let’s approach this just like we would a new failing test and implement the quickest solution that gets the test passing:

public int SelectBestMoveForPlayer(char player) { if (_layout == "\0\0X\0O\0X\0\0" || _layout == "X\0\0\0O\0\0\0X") return 4; return GetPositionThreateningPlayer(player) ?? GetNextWinningMoveForPlayer(player); }

 

 

Next, let’s create a new DefensiveStrategy method and move our fake implementation to our new method:

int? GetPositionThreateningPlayer(char player) { return new DefensiveStrategy[] { SimpleBlockStrategy, FirstMoveCounterCenterStrategy, SecondMoveDiagonalCounterStrategy } .Select(strategy => strategy(player)).FirstOrDefault(p => p.HasValue); } int? SecondMoveDiagonalCounterStrategy(char player) { if (_layout == "\0\0X\0O\0X\0\0" || _layout == "X\0\0\0O\0\0\0X") return 4; return null; }

 

 

Now, let’s change our comparison to only check the positions we care about:

int? SecondMoveDiagonalCounterStrategy(char player) { var opponentValue = GetOpponentValue(player); if((_layout[2] == opponentValue && _layout[6] == opponentValue) || (_layout[0] == opponentValue && _layout[8] == opponentValue)) return 4; return null; }

 

 

Lastly, we’ll change the the value of 4 to be the value of the first unoccupied edge, or null if all are occupied:

int? SecondMoveDiagonalCounterStrategy(char player) { var opponentValue = GetOpponentValue(player); if ((_layout[2] == opponentValue && _layout[6] == opponentValue) || (_layout[0] == opponentValue && _layout[8] == opponentValue)) return new[] {2, 4, 6, 8}.FirstOrDefault(position => _layout[position - 1] == '\0'); return null; }

 

 

In theory, our new changes should cover the gaps in our initial blocking strategy, but we don’t actually have a test for the specific defect that was reported. Let’s create a test which describes the specific steps reported in the defect:

// http://github/mygroup/tic-tac-toe/issues/1 [TestClass] public class When_a_player_attempts_to_choose_positions_1_5_2_and_8 { [TestMethod] public void it_should_prevent_the_player_from_winning() { var game = new Game(); var result = (GameResult) (-1); game.GameComplete += (s, e) => result = e.Result; new[] {1, 4, 2, 8}.ToList().ForEach(position => { if (result == (GameResult)(-1)) Catch.Exception(() => game.ChoosePosition(position)); }); Assert.AreNotEqual(GameResult.PlayerWins, result); } }

In this test, we’re choosing each position in sequence until the result changes. Since it’s possible that the game may throw an exception due to our choosing a position already occupied, we’re issuing our ChoosePosition() call within a call to our Catch.Exception() helper.

The test name for this test is a bit more obscure than our previous ones since it describes more of the “how” than the “why”, but since the purpose of this test is to correct the behavior reported by a specific defect, it seems appropriate to name the test after the scenario it’s intended to address. To aid in its documentation, we’ve included a simple comment containing a link to the issue that was filed.

Let’s run the test:

 
When_a_player_attempts_to_choose_positions_1_5_2_and_8 Failed it_should_prevent_the_player_from_winning TestFirstExample.When_player_attempts_to_choose_positions_1_5_2_and_8.it_should_prevent_the_player_from_winning threw exception: System.InvalidOperationException: Sequence contains no matching element

It appears this scenario uncovered an issue we didn’t run into with our previous specifications. Researching the issue, the cause appears to be that the GameAdvisor is throwing an exception in the GetNextWinningMoveForPlayer() method when the First() method is called on an empty bestSlice collection. Let’s fix this:

int GetNextWinningMoveForPlayer(char player) { List<int[]> availablePaths = GetAvailablePathsFor(player); int[] bestSlice = availablePaths.OrderByDescending( path => path.Count(p => _layout[p - 1] == player)).First(); return bestSlice.FirstOrDefault(p => _layout[p - 1] == '\0'); }

Now, let’s run our tests again:

 
When_a_player_attempts_to_choose_positions_1_5_2_and_8 Failed it_should_prevent_the_player_from_winning TestFirstExample.When_player_attempts_to_choose_positions_1_5_2_and_8.it_should_prevent_the_player_from_winning threw exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.

We got passed that exception, but now there’s another one. Further analysis reveals that an exception is being thrown from the Game’s SelectAPositionFor() method when a recommended position of zero is returned from the GameAdvisor. The Game class now only calls the GameAdvisor when there are positions left, so it shouldn’t be returning zero. Stepping through the execution of the GameAdvisor, it turns out that it stops recommending positions once it runs out of meaningful offensive and defensive strategies.

We could correct this within the context of our existing test, but this feels more like missing behavior than just a bug. Since we want our GameAdvisor to continue recommending positions until there are no positions left, let’s write a new test to explicitly specify this new behavior:

[TestClass] public class When_the_game_selects_a_position_where_no_winning_spaces_are_left { [TestMethod] public void it_should_choose_the_first_available_position() { var gameAdvisor = new GameAdvisor(); int selection = gameAdvisor.WithLayout("0XOOX\0XOX").SelectBestMoveForPlayer('X'); Assert.AreEqual(6, selection); } }

 
When_the_game_selects_a_position_when_no_winning_spaces_are_left Failed it_should_choose_the_first_available_position TestFirstExample.When_the_game_selects_a_position_where_no_winning_spaces_are_left.it_should_choose_the_first_available_position threw exception: System.InvalidOperationException: Sequence contains no elements

Let’s make the test fail for the right reason:

public int SelectBestMoveForPlayer(char player) { return GetPositionThreateningPlayer(player) ?? GetNextWinningMoveForPlayer(player) ?? 1; } int? GetNextWinningMoveForPlayer(char player) { List<int[]> availablePaths = GetAvailablePathsFor(player); int? nextPosition = null; if (availablePaths != null) { int[] bestSlice = availablePaths.OrderByDescending(path => path.Count(p => _layout[p - 1] == player)).FirstOrDefault(); if (bestSlice != null) nextPosition = bestSlice.FirstOrDefault(p => _layout[p - 1] == '\0'); } return nextPosition; }

 
When_the_game_selects_a_position_when_no_winning_spaces_are_left Failed it_should_choose_the_first_available_position Assert.AreEqual failed. Expected:<6>. Actual:<1>.

To make the test pass, we should be able to select the first empty position as the default strategy:

public int SelectBestMoveForPlayer(char player) { return GetPositionThreateningPlayer(player) ?? GetNextWinningMoveForPlayer(player) ?? Enumerable.Range(1, 9).First(position => _layout[position - 1] == '\0'); }

Let’s run all our tests again:

 

 

This passes our new test as well as our initial broken test. Now that we’ve make the test pass, let’s refactor.

As with our Game class, let’s substitute our uses of the null character with the Game’s EmptyValue constant:

public class GameAdvisor : IGameAdvisor { ... public int SelectBestMoveForPlayer(char player) { return GetPositionThreateningPlayer(player) ?? GetNextWinningMoveForPlayer(player) ?? Enumerable.Range(1, 9).First(position => _layout[position - 1] == Game.EmptyValue); } int? GetNextWinningMoveForPlayer(char player) { List<int[]> availablePaths = GetAvailablePathsFor(player); int? nextPosition = null; if (availablePaths != null) { int[] bestSlice = availablePaths.OrderByDescending(path => path.Count(p => _layout[p - 1] == player)).FirstOrDefault(); if (bestSlice != null) nextPosition = bestSlice.FirstOrDefault(p => _layout[p - 1] == Game.EmptyValue); } return nextPosition; } ... int? SecondMoveDiagonalCounterStrategy(char player) { char opponentValue = GetOpponentValue(player); if ((_layout[2] == opponentValue && _layout[6] == opponentValue) || (_layout[0] == opponentValue && _layout[8] == opponentValue)) return new[] {2, 4, 6, 8}.FirstOrDefault(position => _layout[position - 1] == Game.EmptyValue); return null; } int? SimpleBlockStrategy(char player) { char opponentValue = GetOpponentValue(player); List<int[]> availableOpponentPaths = GetAvailablePathsFor(opponentValue); int[] threatingPath = availableOpponentPaths .Where(path => new string(path.Select(p => _layout[p - 1]).ToArray()) .Count(c => c == opponentValue) == 2).FirstOrDefault(); if (threatingPath != null) { return threatingPath.First(position => _layout[position - 1] == Game.EmptyValue); } return null; } ... }

For this to compile, we’ll also need to make this value public:

public class Game { … public const char EmptyValue = char.MinValue; … }

 

 

Let’s move on to our next issue:

Issue Description Owner
Defect The game throws an InvalidOperationException when choosing positions 1, 2, 5, and 9. QA Team

In testing the previous version of the game, the exception being thrown originated from the GameAdvisor’s GetNextWinningMoveForPlayer() position. Since we’ve modified this method, the new version may no longer throw this exception. Let’s write our test and see what happens:

// http://github/mygroup/tic-tac-toe/issues/2 [TestClass] public class When_a_player_attempts_to_choose_positions_1_2_5_9 { [TestMethod] public void it_should_not_throw_an_exception() { Exception exception = null; var game = new Game(); var result = (GameResult) (-1); game.GameComplete += (s, e) => result = e.Result; new[] {1, 2, 5, 9}.ToList().ForEach(position => { Exception ex = null; if (result == (GameResult) (-1)) ex = Catch.Exception(() => game.ChoosePosition(position)); if (ex is InvalidOperationException ) exception = ex; }); Assert.IsNotInstanceOfType(exception, typeof (InvalidOperationException)); } }

 

 

As suspected, this error seems to have already been addressed somewhere along the way. Let’s break the test to make sure it’s working:

public void ChoosePosition(int position) { throw new InvalidOperationException(); ... }

 
When_a_player_attempts_to_choose_positions_1_2_5_9 Failed it_should_not_throw_an_exception Assert.IsNotInstanceOfType failed. Wrong Type:. Actual type:.

 

 

Here’s our next defect:

Issue Description Owner
Defect The game makes a move after the player wins QA Team

I seem to recall we ran into this issue while redesigning the game to raise events when a player wins. I suspect this issue no longer exists, but let’s write a test for this defect to confirm:

// http://github/mygroup/tic-tac-toe/issues/3 [TestClass] public class When_the_player_chooses_a_position_which_wins_the_game { [TestMethod] public void it_should_not_select_a_position_for_the_game() { var game = new Game(new GameAdvisorStub(new[] {4, 5, 9, 6})); Enumerable.Range(1, 3).ToList().ForEach(game.ChoosePosition); var lastGameChoice = game.GetLastChoiceBy(Player.Game); Assert.AreNotEqual(6, lastGameChoice); } }

 

 

It looks like this issue is no longer present. Let’s break the test to make sure our test is validating correctly:

public int GetLastChoiceBy(Player player) { return 6; // return _lastPositionDictionary[player]; }

 
When_the_player_chooses_a_position_which_wins_the_game Failed it_should_not_select_a_position_for_the_game Assert.AreNotEqual failed. Expected any value except:<6>. Actual:<6>.

Everything looks correct, so we’ll revert the change:

 

 

After thinking about this issue, it occurred to me that our game probably doesn’t handle the reverse case of a player making a move after the game has been won. This seems like an issue we should address, but to avoid adding anything unnecessarily I checked with the customer and the UI team to see what the expectations were for this scenario. The customer said they wanted the game to tell the user the game was already over in this case, so it was decided that we should raise an exception that could be caught by the UI team. Let’s go ahead and write out test for this case:

[TestClass] public class When_the_player_selects_a_position_after_a_player_has_won { [TestMethod] public void it_should_tell_the_player_the_game_is_over() { Exception exception = null; var game = new Game(new GameAdvisorStub(new[] {1, 2, 3})); new[] { 4, 5, 7}.ToList().ForEach(game.ChoosePosition); exception = Catch.Exception(() => game.ChoosePosition(9)); Assert.AreSame(typeof(GameOverException), exception.GetType()); } }

Here’s the exception we need to make the test compile:

public class GameOverException : Exception { }

Let’s run the test:

 
When_the_player_selects_a_position_after_a_player_has_won Failed it_should_tell_the_player_the_game_is_over Assert.AreSame failed.

Now let’s make it pass. Let’s move our existing someoneWon variable to a field and raise our new exception if someone won or if no positions are left at the entry of our method:

bool _someoneWon; public void ChoosePosition(int position) { if (_someoneWon || !PositionsAreLeft()) { throw new GameOverException(); } if (IsOutOfRange(position)) { throw new InvalidPositionException(string.Format("The position \'{0}\' was invalid.", position)); } if (_layout[position - 1] != EmptyValue) { throw new OccupiedPositionException(string.Format("The position \'{0}\' is already occupied.", position)); } _someoneWon = new Func[] { () => CheckPlayerStrategy(Player.Human, () => _layout[position - 1] = GetTokenFor(Player.Human)), () => CheckPlayerStrategy(Player.Game, () => SelectAPositionFor(Player.Game)) }.Any(winningPlay => winningPlay()); if (!(_someoneWon || PositionsAreLeft())) { InvokeGameComplete(new GameCompleteEventArgs(GameResult.Draw)); } }

 

 

There doesn’t appear to be anything to refactor, so let’s move on. Here’s our next defect:

Issue Description Owner
Defect After letting the game win by choosing positions 4, 7, 8, and 6, choosing the last position of 3 throws an InvalidOperationException. QA Team

// http://github/mygroup/tic-tac-toe/issues/5 [TestClass] public class When_a_player_attempts_to_choose_positions_4_7_8_6 { [TestMethod] public void it_should_not_throw_an_exception() { Exception exception = null; var game = new Game(); var result = (GameResult)(-1); game.GameComplete += (s, e) => result = e.Result; new[] { 4, 7, 8, 6 }.ToList().ForEach(position => { Exception ex = null; if (result == (GameResult)(-1)) ex = Catch.Exception(() => game.ChoosePosition(position)); if (ex is InvalidOperationException) exception = ex; }); Assert.IsNotInstanceOfType(exception, typeof(InvalidOperationException)); } }

 

 

As with the others, let’s make sure the test is working properly:

 
When_a_player_attempts_to_choose_positions_4_7_8_6 Failed it_should_not_throw_an_exception Assert.IsNotInstanceOfType failed. Wrong Type:. Actual type:.

 

 

We’re almost done! Here’s our last defect:

Issue Description Owner
Defect When trying to let the game win by choosing positions 1, 7, and 8, the game chose positions 4, 5, and 9 instead of completing the winning sequence 4, 5, 6. QA Team

This isn’t really a bug so much as a missing feature. Rather than addressing this issue with a test describing this specific set of moves, let’s describe the missing behavior whose expectations are implied by this defect:

[TestClass] public class When_the_game_can_win_with_the_next_move { [TestMethod] public void it_should_select_the_winning_position() { var game = new Game(); var result = (GameResult)(-1); game.GameComplete += (s, e) => result = e.Result; new[] { 1, 7, 8 }.ToList().ForEach(game.ChoosePosition); Assert.AreEqual(GameResult.GameWins, result); } }

 
When_the_game_can_win_with_the_next_move Failed it_should_not_throw_an_exception Assert.AreEqual failed. Expected:<GameWins>. Actual:<-1>.

I think this can be corrected with a new defensive strategy, so let’s take the leap of skipping a fake implementation and go ahead and add the new behavior:

class PositionSelector : IPositionSelector { ... int? GetPositionThreateningPlayer(char player) { return new DefensiveStrategy[] { PathCompletionStrategy, SimpleBlockStrategy, FirstMoveCounterCenterStrategy, SecondMoveDiagonalCounterStrategy } .Select(strategy => strategy(player)).FirstOrDefault(p => p.HasValue); } int? PathCompletionStrategy(char player) { List<int[]> availablePaths = GetAvailablePathsFor(player); int[] winningPath = availablePaths .Where(path => new string(path.Select(p => _layout[p - 1]).ToArray()) .Count(value => value == player) == 2).FirstOrDefault(); if(winningPath != null) return winningPath.FirstOrDefault(p => _layout[p - 1] == Game.EmptyValue); return null; } ... }

 

 

We’ve used this approach in another strategy, so let’s factor out the duplication:

int? PathCompletionStrategy(char player) { int[] winningPath = GetWinningPathForPlayer(player); if (winningPath != null) return winningPath.FirstOrDefault(p => _layout[p - 1] == Game.EmptyValue); return null; } int? SimpleBlockStrategy(char player) { int[] threatingPath = GetWinningPathForPlayer(GetOpponentValue(player)); if (threatingPath != null) { return threatingPath.First(position => _layout[position - 1] == Game.EmptyValue); } return null; } int[] GetWinningPathForPlayer(char player) { List<int[]> availablePaths = GetAvailablePathsFor(player); return availablePaths .Where(path => new string(path.Select(p => _layout[p - 1]).ToArray()) .Count(value => value == player) == 2).FirstOrDefault(); }

 

 

I think we’re finished. As a final step, I’m going to ask the UI team if I can get an unofficial build with our new component integrated and do a little smoke testing before I close out our issues. We’ll discuss the outcome of this endeavor next time.

Tagged with:  

In part 7 of our series, we finished the initial implementation of our Tic-tac-toe component. After we finished, a team in charge of creating a host application was able to get everything integrated (though rumor has it that there was a bit of complaining) and the application made its way to the QA team for some acceptance testing. Surprisingly, there were several issues reported that got assigned to us. Here are the issues we’ve been assigned:

Issue Description Owner
Defect The player can always win by choosing positions 1, 5, 2, and 8. The game should prevent the player from winning. QA Team
Defect The game throws an InvalidOperationException when choosing positions 1, 2, 5, and 9 QA Team
Defect The game makes a move after the player wins QA Team
Defect After letting the game win by choosing positions 4, 7, 8, and 6, choosing the last position of 3 throws an InvalidOperationException QA Team
Defect When trying to let the game win by choosing positions 1, 7, and 8, the game chose positions 4, 5, and 9 instead of completing the winning sequence 4, 5, 6. QA Team
New Feature Add a method to the Game class for retrieving the last position selected by the game. GUI Team
New Feature Please modify the ChoosePosition method to throw exceptions for errors rather than returning strings. Additionally, please provide an event we can subscribe to when one of the players wins or when there is a draw. GUI Team

As you may have discerned by now, following Test-Driven Development doesn’t ensure the code we produce will be free of errors. It does, however, ensure that our code meets the executable specifications we create to guide the application’s design and aids in the creation of code that’s maintainable and relevant (that is, to the extent we adhere to Test-Driven Development methodologies). Of course, the Test-Driven Development process is a framework into which we pour both our requirements and ourselves. The quality of both of these ingredients certainly affects the overall outcome. As we become better at gathering requirements, translating these requirements into executable specifications, identifying simple solutions and factoring out duplication, our yield from the TDD process will increase.

Since our issues have no particular priority assigned, let’s read through them before we get started to see if any make sense to tackle first. Given a couple of the issues pertain to changes to the API, it might be best to address these first to minimize the possibility of writing new tests that could need to be modified later.

The first of these issues pertain to adding a new method. Here’s the issue:

Issue Description Owner
New Feature Add a method to the Game class for retrieving the last position selected by the game. GUI Team

Upon integrating our component, the GUI team discovered there wasn’t an easy way to tell what positions the game was selecting in order to reflect this to the user. They were able to use techniques similar to those we used in the course of implementing our tests, but they didn’t consider this to be a very friendly API. While such an oversight may seem obvious within the context of the entire application, such issues occur when components are development in isolation. Fundamentally, the problem wasn’t with the Test-Driven Development methodologies we were following, but with the scope in which we were applying them. Later in our series, we’ll discuss an alternative to the approach we’ve taken with this effort that can help avoid misalignments such as this. Until then, we’ll address these issues the best we can with our existing approach.

To address this issue, we’ll create a new test that describes the behavior for the requested method:

[TestClass] public class When_retrieving_the_last_selected_position_for_the_game { [TestMethod] public void it_should_return_the_last_position() { } }

As our method of validation, we’ll set up our assertion to verify that some expected position was selected:

[TestClass] public class When_retrieving_the_last_selected_position_for_the_game { [TestMethod] public void it_should_return_the_last_position() { Assert.AreEqual(1, selection); } }

Next, let’s choose what our API is going to look like and then set up the context of our test. Let’s call our new method GetLastChoiceBy(). We can make use of our existing Player enumeration as the parameter type:

[TestClass] public class When_retrieving_the_last_selected_position_for_the_game { [TestMethod] public void it_should_return_the_last_position() { Game game = new Game(new GameAdvisorStub(new [] { 1 })); game.GoFirst(); var selection = game.GetLastChoiceBy(Player.Game); Assert.AreEqual(1, selection); } }

Next, let’s add the new method to our Game class so this will compile:

public class Game { // snip public int GetLastChoiceBy(Player player) { return 0; } }

Now we’re ready to run the tests:

 
When_retrieving_the_last_selected_position_for_the_game Failed it_should_return_the_last_position Assert.AreEqual failed. Expected:<1>. Actual:<0>.

We can make the test pass by just returning a 1:

public int GetLastChoiceBy(Player player) { return 1; }
 

 

Now, we’ll refactor the method to retrieve the value from a new dictionary field which we’ll set in the SelectAPositionFor() method:

public class Game { readonly Dictionary<Player, char> _tokenAssignments = new Dictionary<Player, char>(); ... void SelectAPositionFor(Player player) { int recommendedPosition = _advisor.WithLayout(new string(_layout)).SelectBestMoveForPlayer(GetTokenFor(player)); _layout[recommendedPosition - 1] = GetTokenFor(player); _lastPositionDictionary[player] = recommendedPosition; } public int GetLastChoiceBy(Player player) { return _lastPositionDictionary[player]; } }
 

 

That was fairly simple. On to our next feature request:

Issue Description Owner
New Feature Please modify the ChoosePosition method to throw exceptions for errors rather than returning strings. Additionally, please provide an event we can subscribe to when one of the players wins or when there is a draw. GUI Team

This is slightly embarrassing. While we’ve been striving to guide the design of our public interface from a consumer’s perspective, we seem to have made a poor choice in how the game communicates errors and the concluding status of the game. If our Game class had evolved within the context of the consuming application, perhaps we would have seen these choices in a different light.

Let’s go ahead and get started on the first part of the request which involves changing how errors are reported. First, let’s take inventory of our existing tests which relate to reporting errors. We have two tests which pertain to error conditions:

[TestClass] public class When_the_player_attempts_to_select_an_invalid_position { [TestMethod] public void it_should_tell_the_player_the_position_is_invalid() { var game = new Game(); string message = game.ChoosePosition(10); Assert.AreEqual("That spot is invalid!", message); } }

[TestClass] public class When_the_player_attempts_to_select_an_occupied_position { [TestMethod] public void it_should_tell_the_player_the_position_is_occupied() { var game = new Game(new GameAdvisorStub(new[] { 1, 4, 7 })); game.ChoosePosition(2); string message = game.ChoosePosition(1); Assert.AreEqual("That spot is taken!", message); } }

Starting with the first method, let’s modify it to check that an exception was thrown:

[TestClass] public class When_the_player_attempts_to_select_an_invalid_position { [TestMethod] public void it_should_tell_the_player_the_position_is_invalid() { var game = new Game(); string message = game.ChoosePosition(10); Assert.AreEqual("The position '10' was invalid.", exception.Message); } }

Next, let’s wrap the call to the ChoosePosition() method with a try/catch block. We’ll call our exception an InvalidPositionException:

[TestClass] public class When_the_player_attempts_to_select_an_invalid_position { [TestMethod] public void it_should_tell_the_player_the_position_is_invalid() { var exception = new InvalidPositionException(string.Empty); var game = new Game(); try { game.ChoosePosition(10); } catch (InvalidPositionException ex) { exception = ex; } Assert.AreEqual("The position '10' was invalid.", exception.Message); } }

Next, let’s create our new Exception class:

public class InvalidPositionException : Exception { public InvalidPositionException(string message) : base(message) { } }

Now, let’s run our tests:

 
When_the_player_attempts_to_select_an_invalid_position Failed it_should_tell_the_player_the_position_is_invalid Assert.AreEqual failed. Expected:. Actual:<>.

Since all we need to do is to throw our new exception, we’ll use an Obvious Implementation:

public string ChoosePosition(int position) { if (IsOutOfRange(position)) { throw new InvalidPositionException( string.Format("The position \'{0}\' was invalid.", position)); } if (_layout[position - 1] != '\0') { return "That spot is taken!"; } _layout[position - 1] = GetTokenFor(Player.Human); SelectAPositionFor(Player.Game); if (WinningPlayerIs(Player.Human)) return "Player wins!"; if (WinningPlayerIs(Player.Game)) return "Game wins."; return string.Empty; }
 

 

In this case we haven’t introduced any duplication that I can see, so let’s move on to our next test. We’ll modify it to follow the same pattern as our previous one:

[TestClass] public class When_the_player_attempts_to_select_an_occupied_position { [TestMethod] public void it_should_tell_the_player_the_position_is_occupied() { var exception = new OccupiedPositionException(string.Empty); var game = new Game(new GameAdvisorStub(new[] {1, 4, 7})); game.ChoosePosition(2); try { game.ChoosePosition(1); } catch (OccupiedPositionException ex) { exception = ex; } Assert.AreEqual("The position '1' is already occupied.", exception.Message); } }

Here is our new exception:

public class OccupiedPositionException : Exception { public OccupiedPositionException(string message): base(message) { } }

Here’s the results of our test execution:

 
When_the_player_attempts_to_select_an_occupied_position Failed it_should_tell_the_player_the_position_is_occupied Assert.AreEqual failed. Expected:. Actual:<>.

To provide the implementation, we’ll throw our new exception:

public string ChoosePosition(int position) { if (IsOutOfRange(position)) { throw new InvalidPositionException(string.Format("The position \'{0}\' was invalid.", position)); } if (_layout[position - 1] != '\0') { throw new OccupiedPositionException( string.Format("The position \'{0}\' is already occupied.", position)); } _layout[position - 1] = GetTokenFor(Player.Human); SelectAPositionFor(Player.Game); if (WinningPlayerIs(Player.Human)) return "Player wins!"; if (WinningPlayerIs(Player.Game)) return "Game wins."; return string.Empty; }
 

 

Again, there isn’t any noticeable duplication this time. Our next task is to send notifications to observers when the Game class detects a winner. Here are the existing tests we used for indicating a winner:

[TestClass] public class When_the_player_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_player_as_the_winner() { var game = new Game(new GameAdvisorStub(new[] {1, 2, 3})); game.ChoosePosition(4); game.ChoosePosition(5); string message = game.ChoosePosition(6); Assert.AreEqual("Player wins!", message); } }

[TestClass] public class When_the_game_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_game_as_the_winner() { var game = new Game(); game.ChoosePosition(4); game.ChoosePosition(6); string message = game.ChoosePosition(8); Assert.AreEqual("Game wins.", message); } }

We’ll start with the first one by modifying our Assert call. First, let’s change the value we’re comparing against from a string to a new GameResult enumeration with a value of PlayerWins:

[TestClass] public class When_the_player_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_player_as_the_winner() { var game = new Game(new GameAdvisorStub(new[] {1, 2, 3})); game.ChoosePosition(4); game.ChoosePosition(5); string message = game.ChoosePosition(6); Assert.AreEqual(GameResult.PlayerWins, result); } }

Next, let’s create an instance of our as yet created GameResult enumeration and initialize it’s value to something we aren’t expecting:

[TestClass] public class When_the_player_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_player_as_the_winner() { var game = new Game(new GameAdvisorStub(new[] {1, 2, 3})); var result = (GameResult) (-1); game.ChoosePosition(4); game.ChoosePosition(5); string message = game.ChoosePosition(6); Assert.AreEqual(GameResult.PlayerWins, result); } }

Next, we need to decide how we would like to receive this value. Let’s assume we can subscribe to a GameComplete event. When invoked, we’ll assume the value can be retrieved from a property on the EventArgs supplied with the event:

[TestClass] public class When_the_player_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_player_as_the_winner() { var game = new Game(new GameAdvisorStub(new[] {1, 2, 3})); var result = (GameResult) (-1); game.GameComplete += (s, e) => result = e.Result; game.ChoosePosition(4); game.ChoosePosition(5); game.ChoosePosition(6); Assert.AreEqual(GameResult.PlayerWins, result); } }

Our next steps are to create the new enum type and to add an event to our Game class. First, let’s create the enum:

public enum GameResult { PlayerWins, GameWins, Draw }

I went ahead and added values for the other two possible states: GameWins and Draw. “Aren’t we getting ahead of ourselves”, you might ask? Perhaps, but we already know we have upcoming tests that will require these states and our GameResult represents the state of our game, not its behavior. We’ve been pretty good about not prematurely adding anything thus far, so this seems like a safe enough step to take without sending us down a slippery slope.

Here’s our new Game event:

public class Game { ... public event EventHandler GameComplete; ... }

Now that we’ve created this, we’ll also need to create a GameCompleteEventArgs:

public class GameCompleteEventArgs : EventArgs { public GameResult Result { get; private set; } }

Now we’re ready to compile and run our tests:

 
When_the_player_gets_three_in_a_row Failed it_should_announce_the_player_as_the_winner Assert.AreEqual failed. Expected:. Actual:<-1>.

There are well established patterns for raising events in .Net, so we’ll follow the standard pattern for this:

public string ChoosePosition(int position) { if (IsOutOfRange(position)) { throw new InvalidPositionException(string.Format("The position \'{0}\' was invalid.", position)); } if (_layout[position - 1] != '\0') { throw new OccupiedPositionException(string.Format("The position \'{0}\' is already occupied.", position)); } _layout[position - 1] = GetTokenFor(Player.Human); SelectAPositionFor(Player.Game); if (WinningPlayerIs(Player.Human)) InvokeGameComplete(new GameCompleteEventArgs(GameResult.PlayerWins)); if (WinningPlayerIs(Player.Game)) return "Game wins."; return string.Empty; }

Now we need to create our InvokeGameComplete() method and a GameCompleteEventArgs constructor that initializes the Result property:

public class Game { ... public event EventHandler GameComplete; public void InvokeGameComplete(GameCompleteEventArgs e) { EventHandler handler = GameComplete; if (handler != null) handler(this, e); } ... }
public class GameCompleteEventArgs : EventArgs { public GameCompleteEventArgs(GameResult result) { Result = result; } public GameResult Result { get; private set; } }
 

 

Again, I don’t see any duplication to worry about. Next, we’ll follow similar steps for notifying the game as a winner:

[TestClass] public class When_the_game_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_game_as_the_winner() { var game = new Game(new GameAdvisorStub(new[] {1, 2, 3})); var result = (GameResult) (-1); game.GameComplete += (s, e) => result = e.Result; game.ChoosePosition(4); game.ChoosePosition(6); game.ChoosePosition(8); Assert.AreEqual(GameResult.GameWins, result); } }
 
When_the_game_gets_three_in_a_row Failed it_should_announce_the_game_as_the_winner Assert.AreEqual failed. Expected:. Actual:<-1>.

To make the test pass, we should only need to modify the Game class to raise the event this time:

public string ChoosePosition(int position) { if (IsOutOfRange(position)) { throw new InvalidPositionException(string.Format("The position \'{0}\' was invalid.", position)); } if (_layout[position - 1] != '\0') { throw new OccupiedPositionException(string.Format("The position \'{0}\' is already occupied.", position)); } _layout[position - 1] = GetTokenFor(Player.Human); SelectAPositionFor(Player.Game); if (WinningPlayerIs(Player.Human)) InvokeGameComplete(new GameCompleteEventArgs(GameResult.PlayerWins)); if (WinningPlayerIs(Player.Game)) InvokeGameComplete(new GameCompleteEventArgs(GameResult.GameWins)); return string.Empty; }

Let’s run the tests again:

 
When_the_player_gets_three_in_a_row Failed it_should_announce_the_player_as_the_winner Assert.AreEqual failed. Expected:. Actual:.

Our target test passed, but we broke our previous test. Looking at our implementation, the problem seems to be that both the player and game select positions on the board before we check to see if anyone is a winner. Additionally, we should return from the method once a winner is determined. Let’s fix this:

public string ChoosePosition(int position) { if (IsOutOfRange(position)) { throw new InvalidPositionException(string.Format("The position \'{0}\' was invalid.", position)); } if (_layout[position - 1] != '\0') { throw new OccupiedPositionException(string.Format("The position \'{0}\' is already occupied.", position)); } _layout[position - 1] = GetTokenFor(Player.Human); if (WinningPlayerIs(Player.Human)) { InvokeGameComplete(new GameCompleteEventArgs(GameResult.PlayerWins)); return string.Empty; } SelectAPositionFor(Player.Game); if (WinningPlayerIs(Player.Game)) { InvokeGameComplete(new GameCompleteEventArgs(GameResult.GameWins)); return string.Empty; } return string.Empty; }
 

 

Let’s refactor now. First, let’s remove our unused return type:

public void ChoosePosition(int position) { if (IsOutOfRange(position)) { throw new InvalidPositionException(string.Format("The position \'{0}\' was invalid.", position)); } if (_layout[position - 1] != '\0') { throw new OccupiedPositionException(string.Format("The position \'{0}\' is already occupied.", position)); } _layout[position - 1] = GetTokenFor(Player.Human); if (WinningPlayerIs(Player.Human)) { InvokeGameComplete(new GameCompleteEventArgs(GameResult.PlayerWins)); return; } SelectAPositionFor(Player.Game); if (WinningPlayerIs(Player.Game)) { InvokeGameComplete(new GameCompleteEventArgs(GameResult.GameWins)); return; } }
 

 

Now that we’ve rearranged our code, we have a sequence of steps that are repeated between the player and the game. First we use a strategy for moving the player, then we check to see if the player wins. Let’s distill this down to checking for the first winning play from a collection of player strategies:

public void ChoosePosition(int position) { if (IsOutOfRange(position)) { throw new InvalidPositionException(string.Format("The position \'{0}\' was invalid.", position)); } if (_layout[position - 1] != '\0') { throw new OccupiedPositionException(string.Format("The position \'{0}\' is already occupied.", position)); } new Func<bool>[] { () => CheckPlayerStrategy(Player.Human, () => _layout[position - 1] = GetTokenFor(Player.Human)), () => CheckPlayerStrategy(Player.Game, () => SelectAPositionFor(Player.Game)) }.Any(winningPlay => winningPlay()); }

Here’s our new CheckPlayerStrategy() method:

bool CheckPlayerStrategy(Player player, Action strategy) { strategy(); if (WinningPlayerIs(player)) { var result = (player == Player.Human) ? GameResult.PlayerWins : GameResult.GameWins; InvokeGameComplete(new GameCompleteEventArgs(result)); return true; } return false; }
 

 

Our final step for this issue is to raise an event when there is a draw. Following our normal procession, here’s the test we come up with:

[TestClass] public class When_a_move_results_in_a_draw { [TestMethod] public void it_should_announce_the_game_is_a_draw() { var game = new Game(new GameAdvisorStub(new[] { 2, 3, 4, 9 })); var result = (GameResult)(-1); game.GameComplete += (s, e) => result = e.Result; new[] {1, 5, 6, 7, 8}.ToList().ForEach(game.ChoosePosition); Assert.AreEqual(GameResult.Draw, result); } }
 
When_a_move_results_in_a_draw Failed it_should_announce_the_game_is_a_draw TestFirstExample.When_a_move_results_in_a_draw.it_should_announce_the_game_is_a_draw threw exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.

This test isn’t failing for the right reason, so let’s address this before moving on. After investigating the exception, the issue is that we never accounted for the fact that their won’t be a position to choose when the player chooses the last remaining position. Let’s correct this issue by ensuring there is an empty spot left before selecting a position for the game:

void SelectAPositionFor(Player player) { if (_layout.Any(position => position == '\0')) { int recommendedPosition = _advisor.WithLayout(new string(_layout)).SelectBestMoveForPlayer(GetTokenFor(player)); _layout[recommendedPosition - 1] = GetTokenFor(player); _lastPositionDictionary[player] = recommendedPosition; } }
 
When_a_move_results_in_a_draw Failed it_should_announce_the_game_is_a_draw Failed it_should_announce_the_game_is_a_draw Assert.AreEqual failed. Expected:. Actual:<-1>.

Now our test is failing for the right reason. To make the test pass, we can fire the event unless someone won or unless there’s any empty positions left:

public void ChoosePosition(int position) { if (IsOutOfRange(position)) { throw new InvalidPositionException(string.Format("The position \'{0}\' was invalid.", position)); } if (_layout[position - 1] != '\0') { throw new OccupiedPositionException(string.Format("The position \'{0}\' is already occupied.", position)); } var someoneWon = new Func<bool>[] { () => CheckPlayerStrategy(Player.Human, () => _layout[position - 1] = GetTokenFor(Player.Human)), () => CheckPlayerStrategy(Player.Game, () => SelectAPositionFor(Player.Game)) }.Any(winningPlay => winningPlay()); if (!(someoneWon || _layout.Any(pos => pos == '\0'))) { InvokeGameComplete(new GameCompleteEventArgs(GameResult.Draw)); } }
 

 

Time to refactor. It looks like we have the same comparison for checking that the game is a draw and our new guard for selecting a position for the game. Let’s create a method for these which expresses the meaning of this check:

bool PositionsAreLeft() { return _layout.Any(pos => pos == '\0'); }

Now we can replace the previous calls in the ChoosePosition() and SelectAPositionFor() methods:

public void ChoosePosition(int position) { if (IsOutOfRange(position)) { throw new InvalidPositionException(string.Format("The position \'{0}\' was invalid.", position)); } if (_layout[position - 1] != '\0') { throw new OccupiedPositionException(string.Format("The position \'{0}\' is already occupied.", position)); } bool someoneWon = new Func<[] { () => CheckPlayerStrategy(Player.Human, () => _layout[position - 1] = GetTokenFor(Player.Human)), () => CheckPlayerStrategy(Player.Game, () => SelectAPositionFor(Player.Game)) }.Any(winningPlay => winningPlay()); if (!(someoneWon || PositionsAreLeft())) { InvokeGameComplete(new GameCompleteEventArgs(GameResult.Draw)); } }
void SelectAPositionFor(Player player) { if (PositionsAreLeft()) { int recommendedPosition = _advisor.WithLayout(new string(_layout)).SelectBestMoveForPlayer(GetTokenFor(player)); _layout[recommendedPosition - 1] = GetTokenFor(player); _lastPositionDictionary[player] = recommendedPosition; } }
 

 

One thing that occurred to me while implementing this feature is that using a null character to represent an empty position isn’t particularly clear. Let’s define a constant named EmptyValue which we’ll substitute for our use of the null character:

public class Game { ... const char EmptyValue = char.MinValue; ... public void ChoosePosition(int position) { ... if (_layout[position - 1] != EmptyValue) { throw new OccupiedPositionException(string.Format("The position \'{0}\' is already occupied.", position)); } ... } bool PositionsAreLeft() { return _layout.Any(pos => pos == EmptyValue ); } string GetLayoutFor(Player player) { return new string(_layout.ToList() .Select(c => (c.Equals(GetTokenFor(player))) ? GetTokenFor(player) : EmptyValue ) .ToArray()); } … }
 

 

That wraps up the two issues from the UI team. We’ll stop here and address the issues from the QA team next time.

Tagged with:  

In part 6 of our series, we continued the implementation of our Tic-tac-toe game using a Test-First approach. This time, we’ll finish out our requirements.

Here’s where we left things:

When the player goes first it should put their mark in the selected position it should make the next move When the player gets three in a row it should announce the player as the winner When the game gets three in a row it should announce the game as the winner When the player attempts to select an occupied position it should tell the player the position is occupied When the player attempts to select an invalid position it should tell the player the position is invalid When the game goes first it should put an X in one of the available positions When the player can not win on the next turn it should try to get three in a row When the player can win on the next turn it should block the player

Our last two requirements pertain to making the game try to win. The first requirement concerns the game trying to get three in a row while the second pertains to the game trying to keep the opponent from getting three in a row. Let’s get started on the first test:

[TestClass] public class When_the_player_can_not_win_on_the_next_turn { [TestMethod] public void it_should_try_to_get_three_in_a_row() { } }

Let’s assume we’ll be validating that the game gets three in a row by completing a sequence ending with the bottom right position being selected:

[TestMethod] public void it_should_try_to_get_three_in_a_row() { Assert.AreEqual(9, selection); }

Next, let’s establish a scenario were the bottom right corner should always be the position we would expect the game to choose (as opposed to a scenario where the game might have multiple intelligent moves). The following illustrates a layout where the game has gone first and has already achieved two in a row:

 

tic-tac-toe-game-win

 

Next, we need to determine how we can force the game into the desired state so we can validate the next position selected. We won’t be able to use the same technique as before, so we’ll need to find a new way of manipulating the state. One way would be to just make the Game’s _layout field public and manipulate it directly, but that would break encapsulation. Another way would be to set the _layout field through reflection, but this would result in our test being more tightly coupled to the implementation details of our Game. To make our Game testable, we need to adapt its interface. If our game relied upon a separate class for choosing the positions, we would then have a seam we could use to influence the layout. Hey … once this is in place we’ll have a way to fix our test coupling problem!

For now, let’s comment out the test we’ve been working on and start on a new test describing how the Game class will interact with this new dependency. Let’s think of the dependency as an “advisor” and describe the interaction as receiving a recommendation by the advisor:

[TestClass] public class When_the_game_selects_a_position { [TestMethod] public void it_should_select_the_position_recommended_by_the_advisor() { } }

Next, let’s establish an assertion that validates the selection made by the game. We’ll stick with the same scenario we established earlier, expecting the game to choose the bottom right position:

[TestClass] public class When_the_game_selects_a_position { [TestMethod] public void it_should_select_the_position_recommended_by_the_advisor() { Assert.AreEqual(9, selection); } }

Next, we need a way of determining the last position chosen by the game. As we’ve done in our previous tests, we’ll use the single GetPosition() method and a little bit of LINQ goodness to help us out. To figure out what the last move was, we can get a list of all the game positions before and after its next turn. We can then use the two lists to determine which new position was selected:

[TestClass] public class When_the_game_selects_a_position { [TestMethod] public void it_should_select_the_position_recommended_by_the_advisor() { IEnumerable<int> beforeLayout = (Enumerable.Range(1, 9) .Where(position => game.GetPosition(position).Equals('X')) .Select(position => position)).ToList(); // make move here IEnumerable<int> afterLayout = (Enumerable.Range(1, 9) .Where(position => game.GetPosition(position).Equals('X')) .Select(position => position)).ToList(); int selection = afterLayout.Except(beforeLayout).Single(); Assert.AreEqual(9, selection); } }

Next, let’s establish the Game context along with the call we’re interested in:

[TestClass] public class When_the_game_selects_a_position { [TestMethod] public void it_should_select_the_position_recommended_by_the_advisor() { var game = new Game(advisor); game.GoFirst(); game.ChoosePosition(1); IEnumerable<int> beforeLayout = (Enumerable.Range(1, 9) .Where(position => game.GetPosition(position).Equals('X')) .Select(position => position)).ToList(); game.ChoosePosition(8); IEnumerable<int> afterLayout = (Enumerable.Range(1, 9) .Where(position => game.GetPosition(position).Equals('X')) .Select(position => position)).ToList(); int selection = afterLayout.Except(beforeLayout).Single(); Assert.AreEqual(9, selection); } }

Next, let’s establish our GameAdvisor stub. To get our GameAdvisorStub to recommend the positions we’d like, we’ll pass an array of integers to denote the progression we want the game to use:

[TestMethod] public void it_should_select_the_position_recommended_by_the_advisor() { IGameAdvisor advisor = new GameAdvisorStub(new[] { 3, 6, 9 }); var game = new Game(advisor); game.GoFirst(); game.ChoosePosition(1); IEnumerable<int> beforeLayout = (Enumerable.Range(1, 9) .Where(position => game.GetPosition(position).Equals('X')) .Select(position => position)).ToList(); game.ChoosePosition(8); IEnumerable<int> afterLayout = (Enumerable.Range(1, 9) .Where(position => game.GetPosition(position).Equals('X')) .Select(position => position)).ToList(); int selection = afterLayout.Except(beforeLayout).Single(); Assert.AreEqual(9, selection); }

To get our test to compile, we’ll need to create our new IGameAdvisor interface, GameAdvisorStub class and add a constructor to our existing Game class. Let’s start with the advisor types:

public interface IGameAdvisor { } public class GameAdvisorStub : IGameAdvisor { readonly int[] _positions; public GameAdvisorStub(int[] positions) { _positions = positions; } }

Next, let’s create the new constructor for our Game class which takes an IGameAdvisor. We’ll also supply a default no argument constructor to keep our existing tests compiling:

public class Game { public Game() { } public Game(IGameAdvisor advisor) { } ... }

Everything should now compile. Let’s run our tests:

 

When_the_game_selects_a_position Failed it_should_select_the_position_recommended_by_the_advisor Assert.AreEqual failed. Expected:<9>. Actual:<2>. ...

Before we move on, our test could stand a little cleaning up. The verbosity of the LINQ extension method calls we’re using are obscuring the intent of our test a bit. Let’s write a test helper in the form of an extension method to help clarify the intentions of our test:

public static class GameExtensions { public static int GetSelectionAfter(this Game game, Action action) { IEnumerable<int> beforeLayout = (Enumerable.Range(1, 9) .Where(position => game.GetPosition(position).Equals('X')) .Select(position => position)).ToList(); action(); IEnumerable<int> afterLayout = (Enumerable.Range(1, 9) .Where(position => game.GetPosition(position).Equals('X')) .Select(position => position)).ToList(); return afterLayout.Except(beforeLayout).Single(); } }

Now we can change our test to the following:

[TestClass] public class When_the_game_selects_a_position { [TestMethod] public void it_should_select_the_position_recommended_by_the_advisor() { IGameAdvisor advisor = new GameAdvisorStub(new[] {3, 6, 9}); var game = new Game(advisor); game.GoFirst(); game.ChoosePosition(1); int selection = game.GetSelectionAfter(() => game.ChoosePosition(8)); Assert.AreEqual(9, selection); } }

Let’s run the test again to make sure it still validates correctly:

 

When_the_game_selects_a_position Failed it_should_select_the_position_recommended_by_the_advisor Assert.AreEqual failed. Expected:<9>. Actual:<2>. ...

Good, now let’s work on making the test pass. Something simple we can do to force our test to pass is to play off of a bit of new information we have at our disposal. Since our new test is the only one using the overloaded constructor, we can use the advisor field as a sort of flag to perform some behavior specific to this test. First, let’s assign the parameter to a field:

readonly IGameAdvisor _advisor; public Game(IGameAdvisor advisor) { _advisor = advisor; }

Next, let’s modify the ChoosePosition() method to set the ninth position to an ‘X’ if the _advisor field is set and the player chooses position 8:

public string ChoosePosition(int position) { if( _advisor != null && position == 8 ) { _layout[8] = 'X'; return string.Empty; } if (IsOutOfRange(position)) { return "That spot is invalid!"; } if (_layout[position - 1] != '\0') { return "That spot is taken!"; } _layout[position - 1] = GetTokenFor(Player.Human); SelectAPositionFor(Player.Game); if (WinningPlayerIs(Player.Human)) return "Player wins!"; if (WinningPlayerIs(Player.Game)) return "Game wins."; return string.Empty; }

Now, let’s run our tests:

 

 

Now, let’s refactor. To eliminate our fake implementation, let’s start by modifying the Game’s SelectAPositionFor() method to call our new IGameAdvisor field. Well assume the IGameAdvisor supports a SelectAPositionFor() method which allows us to pass in the token and the current layout as a string:

void SelectAPositionFor(Player player) { int recommendedPosition = _advisor.SelectAPositionFor(GetTokenFor(player), new string(_layout)); _layout[recommendedPosition] = GetTokenFor(player); }

Next, let’s define the new method on our interface:

public interface IGameAdvisor { int SelectAPositionFor(char player, string layout); }

Next, we need to implement the new method on our stub. To have our stub return the expected positions, we’ll keep track of how many times the method has been called and use that as the offset into the array setup in our test:

public class GameAdvisorStub : IGameAdvisor { readonly int[] _positions; int _count; public GameAdvisorStub(int[] positions) { _positions = positions; } public int SelectAPositionFor(char player, string layout) { return _positions[_count++]; } }

Lastly, we can delete our fake implementation and run the tests:

 

When_the_player_goes_first Failed it_should_put_their_choice_in_the_selected_position TestFirstExample.When_the_player_goes_first.establish_context threw exception. System.NullReferenceException: System.NullReferenceException: Object reference not set to an instance of an object.. Failed it_should_make_the_next_move TestFirstExample.When_the_player_goes_first.establish_context threw exception. System.NullReferenceException: System.NullReferenceException: Object reference not set to an instance of an object.. ...

Oh no, we broke a bunch of tests! They all seem to be failing due to a NullReferenceException. Looking further, this is being caused by the IGameAdvisor field not being assigned when using the default constructor. Let’s fix that by changing our default constructor to call the overloaded constructor with a default implementation of the IGameAdvisor interface:

public Game() : this(new GameAdvisor()) { }

Next, we’ll create the GameAdvisor class and provide an implementation that mirrors the former behavior:

class GameAdvisor : IGameAdvisor { public int SelectAPositionFor(char player, string layout) { return Enumerable.Range(1, layout.Length) .First(p => layout[p - 1].Equals('\0')); } }
 

 

Our Game class works the same way as before, but we now have a new seam we can influence the layout selection with.

We can now turn our attention back to the requirements. Let’s go back and un-comment the test we started with, but this time we’ll use it to drive the behavior of our GameAdvisor:

[TestClass] public class When_the_player_can_not_win_on_the_next_turn { [TestMethod] public void it_should_try_to_get_three_in_a_row() { Assert.AreEqual(9, selection); } }

Next, let’s declare an instance of our GameAdvisor class and ask it to select a position for player ‘X’:

[TestClass] public class When_the_player_can_not_win_on_the_next_turn { [TestMethod] public void it_should_try_to_get_three_in_a_row() { IGameAdvisor advisor = new GameAdvisor(); var selection = advisor.SelectAPositionFor('X', "OXXO"); Assert.AreEqual(9, selection); } }

Before moving on, let’s consider our initial API. Given any approach, is this really the API we want to work with? While the SelectAPositionFor() method seems like a good start, the parameters feel more like an afterthought than a part of the request. It reads more like a “Do something, and oh by the way, here’s some data”. I didn’t notice when we called it from the Game class, but looking back, that call was aided by the context of its usage. We don’t have any variables telling us what ‘X’ and “OA…” mean.

One of the advantages of Test-Driven Development is that it forces us to look at our API from a consumer’s perspective. When we build things from the inside out, we often don’t consider how intuitive the components will be to work with by our consumers. Once we’ve started implementation, our perspective can be prejudiced by our understanding of how the system works. TDD helps to address this issue by forcing us to consider how the components will be used. This in turn guides us to adapt the design to how the system is being used rather than the other way around. Let’s see if we can improve upon this a bit:

[TestClass] public class When_the_player_can_not_win_on_the_next_turn { [TestMethod] public void it_should_try_to_get_three_in_a_row() { IGameAdvisor advisor = new GameAdvisor(); var selection = advisor.WithLayout("OXXO").SelectBestMoveForPlayer('X'); Assert.AreEqual(9, selection); } }

That seems to express how I’d like to interact with our advisor more clearly. This breaks our code though, so we’ll need to make some adjustments to our IGameAdvisor interface and GameAdvisor class:

public interface IGameAdvisor { int SelectBestMoveForPlayer(char player); IGameAdvisor WithLayout(string layout); } class GameAdvisor : IGameAdvisor { string _layout; public int SelectBestMoveForPlayer(char player) { return Enumerable.Range(1, _layout.Length) .First(p => _layout[p - 1].Equals('\0')); } public IGameAdvisor WithLayout(string layout) { _layout = layout; return this; } }

That would work, but this implementation would allow us to call the advisor without specifying the layout. Let’s take just a little more time to clear that up by moving the SelectBestMoveForPlayer() method to an inner class to prevent it from being called directly:

public interface IGameAdvisor { IPositionSelector WithLayout(string layout); } public interface IPositionSelector { int SelectBestMoveForPlayer(char player); } class GameAdvisor : IGameAdvisor { public IPositionSelector WithLayout(string layout) { return new PositionSelector(layout); } class PositionSelector : IPositionSelector { readonly string _layout; public PositionSelector(string layout) { _layout = layout; } public int SelectBestMoveForPlayer(char player) { return Enumerable.Range(1, _layout.Length) .First(p => _layout[p - 1].Equals('\0')); } } }

Next, let’s fix up our GameAdvisorStub:

public class GameAdvisorStub : IGameAdvisor { readonly int[] _positions; int _count; public GameAdvisorStub(int[] positions) { _positions = positions; } public IPositionSelector WithLayout(string layout) { return new PositionSelector(layout, _positions, _count++); } class PositionSelector : IPositionSelector { readonly int[] _positions; readonly int _count; public PositionSelector(string layout, int[] positions, int count) { _positions = positions; _count = count; } public int SelectBestMoveForPlayer(char player) { return _positions[_count]; } } }

We also need to fix the SelectAPositionFor() method in our Game class:

void SelectAPositionFor(Player player) { int recommendedPosition = _advisor.WithLayout(new string(_layout)) .SelectBestMoveForPlayer(GetTokenFor(player)); _layout[recommendedPosition - 1] = GetTokenFor(player); }

Now, let’s run our tests and make sure our new test fails for the right reason and that we haven’t broken any of the other behavior:

 

When_the_player_can_not_win_on_the_next_turn Failed it_should_try_to_get_three_in_a_row Assert.AreEqual failed. Expected:<9>. Actual:<2>.

Only our new test fails, which is what we were hoping for. Now, let’s use the Fake It approach to get our test to pass quickly. Since only one of our tests ever call this method with the token ‘X’ and it doesn’t care about which actual position it is, we can change the GameAdvisor’s PositionAdvisor.SelectBestMoveForPlayer() method to always return 9 for player ‘X’:

public int SelectBestMoveForPlayer(char player) { if (player == 'X') return 9; return Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')) + 1; }

Now, let’s refactor to eliminate our duplication. In order to calculate which position should be selected, we’ll need to know what the winning patterns are and which paths in the layout are closest to the winning patterns. My first thought was that we might be able to reuse the winning pattern regular expressions we defined over in our Game class. Let’s go back and look at that again:

readonly string[] _winningPatterns = new[] { "[XO][XO][XO]......", "...[XO][XO][XO]...", "......[XO][XO][XO]", "[XO]..[XO]..[XO]..", ".[XO]..[XO]..[XO].", "..[XO]..[XO]..[XO]", "[XO]...[XO]...[XO]", "..[XO].[XO].[XO]..", };

While these patterns define what the winning paths are, I don’t think this will work for our needs because this only matches winning patterns. What we need is a way of examining each of the eight winning paths within the layout to see which is the closest to winning. Let’s start by define a new regular expression that we can use to filter out the paths that can’t win:

class PositionSelector : IPositionSelector { readonly Regex _availablePathPattern = new Regex(@"[X]{3}"); readonly string _layout; public PositionSelector(string layout) { _layout = layout; } public int SelectBestMoveForPlayer(char player) { if (player == 'X') return 9; return Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')) + 1; } }

This regular expression will match any three characters where each of the characters can be either an ‘X’ or a null. If you’re unfamiliar with regular expressions, the brackets are referred to as Character Classes or Character Sets and allow us to define a group of characters we’re interested in. The curly braces with the number is how we define how many times the pattern should repeat to be a match.

Now, we need to apply this to each of the eight possible paths within our layout. To do so, we’ll need to slice up our layout into the eight possible winning paths. Let’s define an array similar to the one in our Game class, but using the winning positions instead of winning patterns:

class PositionSelector : IPositionSelector { readonly Regex _availablePathPattern = new Regex(@"[X]{3}"); readonly string _layout; static readonly int[][] _winningPositions = new[] { new[] {1, 2, 3}, new[] {4, 5, 6}, new[] {7, 8, 9}, new[] {1, 4, 7}, new[] {2, 5, 8}, new[] {3, 6, 9}, new[] {1, 5, 9}, new[] {3, 5, 7}, }; public PositionSelector(string layout) { _layout = layout; } public int SelectBestMoveForPlayer(char player) { if (player == 'X') return 9; return Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')) + 1; } }

Next, we can loop over each of the _winningPositions, retrieve the slice, compare it to the _availablePathPattern and add the matches to a list of availablePaths:

public int SelectBestMoveForPlayer(char player) { var availablePaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => _layout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availablePaths.Add(winningSlice); } if (player == 'X') return 9; return Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')) + 1; }

Now that we have the available paths, we can sort them in descending order based on how many ‘O’s they already have, find the first available slot in the slice and return that as the position:

public int SelectBestMoveForPlayer(char player) { var availablePaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => _layout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availablePaths.Add(winningSlice); } var bestSlice = availablePaths .OrderByDescending(path => path .Count(p => _layout[p - 1] == 'X')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); }

I think we’re almost done refactoring, but we still have some duplication to eliminate. Both our test and our implementation are using the value of ‘X’ as a constant to represent the player. Let’s fix this by replacing the player’s token to a more neutral value and change our regular expression and sorting call to use the neutral value instead:

readonly Regex _availablePathPattern = new Regex(@"[T]{3}"); public int SelectBestMoveForPlayer(char player) { string layout = _layout.Replace(player, 'T'); var availablePaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => layout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availablePaths.Add(winningSlice); } int[] bestSlice = availablePaths .OrderByDescending(path => path .Count(p => layout[p - 1] == 'T')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); }

Everything should be good to go. Let’s run our tests and see how we did:

 

When_the_player_attempts_to_select_an_occupied_positionFailed it_should_tell_the_player_the_position_is_occupied Assert.AreEqual failed. Expected:<That spot is taken!>. Actual:<>.

Our target test passed, but we broke the test for how the game responds when the player chooses a position that is already occupied. Let’s review the test again:

[TestClass] public class When_the_player_attempts_to_select_an_occupied_position { [TestMethod] public void it_should_tell_the_player_the_position_is_occupied() { var game = new Game(); game.ChoosePosition(2); string message = game.ChoosePosition(1); Assert.AreEqual("That spot is taken!", message); } }

Because we are choosing the second position in this test, the GameAdvisor avoids recommending positions within the first winning pattern. We could fix this test pretty easily by avoiding positions two or three, but now that we now have a seam to control exactly what positions are selected, let’s use our new GameAdvisorStub to correct this test in a more explicit way:

[TestClass] public class When_the_player_attempts_to_select_an_occupied_position { [TestMethod] public void it_should_tell_the_player_the_position_is_occupied() { var game = new Game(new GameAdvisorStub(new [] {1, 4, 7})); game.ChoosePosition(2); string message = game.ChoosePosition(1); Assert.AreEqual("That spot is taken!", message); } }
 

 

The last requirement concerns how the game reacts when the player is about to win. Here’s our skeleton:

[TestClass] public class When_the_player_can_win_on_the_next_turn { [TestMethod] public void it_should_block_the_player() { } }

As always, we’ll start by deciding what observable outcome we want to depend upon to know the behavior is working correctly. Since we’re expecting the game to block the player, let’s come up with a scenario we know wouldn’t result from the existing behavior of trying to get three in a row. Let’s say the player goes first and has one position left in the center vertical row to win:

 

tic-tac-toe-game-block

 

To validate this scenario, we’ll check that the GameAdvisor chooses the eighth position:

[TestClass] public class When_the_player_can_win_on_the_next_turn { [TestMethod] public void it_should_block_the_player() { Assert.AreEqual(8, selection); } }

Next, let’s setup the rest of the context to declare the instance of our SUT and establish the layout:

[TestClass] public class When_the_player_can_win_on_the_next_turn { [TestMethod] public void it_should_block_the_player() { IGameAdvisor advisor = new GameAdvisor(); int selection = advisor.WithLayout("XOX").SelectBestMoveForPlayer('O'); Assert.AreEqual(8, selection); } }

Now, let’s run our test:

 

When_the_player_can_win_on_the_next_turn Failed it_should_block_the_player Assert.AreEqual failed. Expected:<8>. Actual:<1>.

Now, let’s make the test pass. This time, I’ll pass the test by testing specifically for the layout we’re after:

public int SelectBestMoveForPlayer(char player) { string layout = _layout.Replace(player, 'T'); var availablePaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => layout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availablePaths.Add(winningSlice); } if (layout == "XTX") { return 8; } int[] bestSlice = availablePaths .OrderByDescending(path => path .Count(p => layout[p - 1] == 'T')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); }
 

 

Now, let’s refactor. To get the GameAdvisor to choose the eighth position because it recognizes it’s vulnerable to losing, we’ll need to check how close the player is. To do this, we can find all of the available paths for the player and check if any of them already have two positions occupied. First, we’ll need to know which token the player is using:

public int SelectBestMoveForPlayer(char player) { string layout = _layout.Replace(player, 'T'); var availablePaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => layout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availablePaths.Add(winningSlice); } char opponentValue = (player == 'X') ? 'O' : 'X'; if (layout == "XTX") { return 8; } int[] bestSlice = availablePaths .OrderByDescending(path => path .Count(p => layout[p - 1] == 'T')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); } }

Next, let’s create a new local layout based on the player’s positions:

public int SelectBestMoveForPlayer(char player) { string layout = _layout.Replace(player, 'T'); var availablePaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => layout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availablePaths.Add(winningSlice); } char opponentValue = (player == 'X') ? 'O' : 'X'; string playerLayout = _layout.Replace(opponentValue, 'T'); if (layout == "XTX") { return 8; } int[] bestSlice = availablePaths .OrderByDescending(path => path .Count(p => layout[p - 1] == 'T')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); }

Now, let’s copy the logic we created before and use it to find the available paths for the opponent:

public int SelectBestMoveForPlayer(char player) { string layout = _layout.Replace(player, 'T'); var availablePaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => layout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availablePaths.Add(winningSlice); } char opponentValue = (player == 'X') ? 'O' : 'X'; string opponentLayout = _layout.Replace(opponentValue, 'T'); List<int[]> availableOpponentPaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => opponentLayout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availableOpponentPaths.Add(winningSlice); } if (layout == "XTX") { return 8; } int[] bestSlice = availablePaths .OrderByDescending(path => path .Count(p => layout[p - 1] == 'T')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); }

Lastly, let’s find all the available paths for which the opponent already has two positions filled and remove our fake implementation:

public int SelectBestMoveForPlayer(char player) { string layout = _layout.Replace(player, 'T'); var availablePaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => layout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availablePaths.Add(winningSlice); } char opponentValue = (player == 'X') ? 'O' : 'X'; string opponentLayout = _layout.Replace(opponentValue, 'T'); List<int[]> availableOpponentPaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => opponentLayout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availableOpponentPaths.Add(winningSlice); } int[] threatingPath = availableOpponentPaths .Where(path => new string( path.Select(p => opponentLayout[p - 1]).ToArray()) .Count(c => c == 'T') == 2).FirstOrDefault(); if (threatingPath != null) { return threatingPath .First(position => opponentLayout[position - 1] == '\0'); } int[] bestSlice = availablePaths.OrderByDescending( path => path.Count(p => layout[p - 1] == 'T')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); }

Let’s run our test and see what happens:

 

When_the_player_gets_three_in_a_row Failed it_should_announce_the_player_as_the_winner Assert.AreEqual failed. Expected:<Player wins!>. Actual:<That spot is taken!>.

Our test still passes, but for some reason we broke the test for testing that the player wins when getting three in a row. Let’s have a look:

[TestClass] public class When_the_player_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_player_as_the_winner() { var game = new Game(); game.ChoosePosition(4); game.ChoosePosition(5); string message = game.ChoosePosition(6); Assert.AreEqual("Player wins!", message); } }

We wrote this test to assume we could pick positions without worrying about getting blocked. That behavior has changed, so we’ll need to adapt our test. Again, we can use our new seam to plug in the path we want the Game to follow to stay out of our way:

[TestClass] public class When_the_player_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_player_as_the_winner() { var game = new Game(new GameAdvisorStub(new int[] { 1, 2, 3})); game.ChoosePosition(4); game.ChoosePosition(5); string message = game.ChoosePosition(6); Assert.AreEqual("Player wins!", message); } }
 

 

Now that we’ve fixed that test, let’s continue our refactoring effort. In generalizing our code, we introduced some duplication. Let’s fix this by extracting a method for determining the available paths for a given player:

List<int[]> GetAvailablePathsFor(char player) { string layout = _layout.Replace(player, 'T'); var availablePaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => layout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availablePaths.Add(winningSlice); } return availablePaths; }

Now our SelectBestPlayerFor() method becomes:

public int SelectBestMoveForPlayer(char player) { string layout = _layout.Replace(player, 'T'); var availablePaths = GetAvailablePathsFor(player); char opponentValue = (player == 'X') ? 'O' : 'X'; string opponentLayout = _layout.Replace(opponentValue, 'T'); List<int[]> availableOpponentPaths = GetAvailablePathsFor(opponentValue); int[] threatingPath = availableOpponentPaths .Where(path => new string( path.Select(p => opponentLayout[p - 1]).ToArray()) .Count(c => c == 'T') == 2).FirstOrDefault(); if (threatingPath != null) { return threatingPath .First(position => opponentLayout[position - 1] == '\0'); } int[] bestSlice = availablePaths.OrderByDescending( path => path.Count(p => layout[p - 1] == 'T')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); }
 

 

Next, let’s reorganize some of these operations so we can see how things group together:

public int SelectBestMoveForPlayer(char player) { char opponentValue = (player == 'X') ? 'O' : 'X'; string opponentLayout = _layout.Replace(opponentValue, 'T'); List<int[]> availableOpponentPaths = GetAvailablePathsFor(opponentValue); int[] threatingPath = availableOpponentPaths .Where(path => new string( path.Select(p => opponentLayout[p - 1]).ToArray()) .Count(c => c == 'T') == 2).FirstOrDefault(); if (threatingPath != null) { return threatingPath .First(position => opponentLayout[position - 1] == '\0'); } string layout = _layout.Replace(player, 'T'); List<int[]> availablePaths = GetAvailablePathsFor(player); int[] bestSlice = availablePaths.OrderByDescending( path => path.Count(p => layout[p - 1] == 'T')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); }
 

 

The first section is all about checking for threating opponent paths, but this isn’t very descriptive at the moment. Let’s move all of that into a method that describes exactly what we’re doing:

public int SelectBestMoveForPlayer(char player) { int? threatingPosition = GetPositionThreateningPlayer(player); if (threatingPosition != null) return threatingPosition.Value; string layout = _layout.Replace(player, 'T'); List<int[]> availablePaths = GetAvailablePathsFor(player); int[] bestSlice = availablePaths.OrderByDescending( path => path.Count(p => layout[p - 1] == 'T')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); } int? GetPositionThreateningPlayer(char player) { char opponentValue = (player == 'X') ? 'O' : 'X'; string opponentLayout = _layout.Replace(opponentValue, 'T'); List<int[]> availableOpponentPaths = GetAvailablePathsFor(opponentValue); int[] threatingPath = availableOpponentPaths .Where(path => new string( path.Select(p => opponentLayout[p - 1]).ToArray()) .Count(c => c == 'T') == 2).FirstOrDefault(); if (threatingPath != null) { return threatingPath .First(position => opponentLayout[position - 1] == '\0'); } return null; }
 

 

Next, let’s extract the code for selecting the next winning path position for the player into a separate method:

public int SelectBestMoveForPlayer(char player) { int? threatingPosition = GetPositionThreateningPlayer(player); if (threatingPosition != null) return threatingPosition.Value; return GetNextWinningMoveForPlayer(player); } int GetNextWinningMoveForPlayer(char player) { string layout = _layout.Replace(player, 'T'); List<int[]> availablePaths = GetAvailablePathsFor(player); int[] bestSlice = availablePaths.OrderByDescending( path => path.Count(p => layout[p - 1] == 'T')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); }
 

 

Now, we can reduce our SelectBestMoveForPlayer() method down to one fairly descriptive line:

public int SelectBestMoveForPlayer(char player) { return GetPositionThreateningPlayer(player) ?? GetNextWinningMoveForPlayer(player); }
 

 

We’re done! Here’s our GameAdvisor implementation:

class GameAdvisor : IGameAdvisor { public IPositionSelector WithLayout(string layout) { return new PositionSelector(layout); } class PositionSelector : IPositionSelector { static readonly int[][] _winningPositions = new[] { new[] {1, 2, 3}, new[] {4, 5, 6}, new[] {7, 8, 9}, new[] {1, 4, 7}, new[] {2, 5, 8}, new[] {3, 6, 9}, new[] {1, 5, 9}, new[] {3, 5, 7}, }; readonly Regex _availablePathPattern = new Regex(@"[T]{3}"); readonly string _layout; public PositionSelector(string layout) { _layout = layout; } public int SelectBestMoveForPlayer(char player) { return GetPositionThreateningPlayer(player) ?? GetNextWinningMoveForPlayer(player); } int GetNextWinningMoveForPlayer(char player) { string layout = _layout.Replace(player, 'T'); List<int[]> availablePaths = GetAvailablePathsFor(player); int[] bestSlice = availablePaths.OrderByDescending( path => path.Count(p => layout[p - 1] == 'T')).First(); return bestSlice.First(p => _layout[p - 1] == '\0'); } int? GetPositionThreateningPlayer(char player) { char opponentValue = (player == 'X') ? 'O' : 'X'; string opponentLayout = _layout.Replace(opponentValue, 'T'); List<int[]> availableOpponentPaths = GetAvailablePathsFor(opponentValue); int[] threatingPath = availableOpponentPaths .Where(path => new string( path.Select(p => opponentLayout[p - 1]).ToArray()) .Count(c => c == 'T') == 2).FirstOrDefault(); if (threatingPath != null) { return threatingPath .First(position => opponentLayout[position - 1] == '\0'); } return null; } List<int[]> GetAvailablePathsFor(char player) { string layout = _layout.Replace(player, 'T'); var availablePaths = new List<int[]>(); foreach (var winningSlice in _winningPositions) { var slice = new string(winningSlice.ToList() .Select(p => layout.ElementAt(p - 1)).ToArray()); if (_availablePathPattern.IsMatch(slice)) availablePaths.Add(winningSlice); } return availablePaths; } } }

While we’ve been working on our component, another team has been putting together a host application with a nice user interface. We’re now ready to hand our component over so it can be integrated into the rest of the application. Afterward, the full application will be passed on to a Quality Assurance Team to receive some acceptance testing. Next time we’ll take a look at any issues that come out of the integration and QA testing processes.

Tagged with:  

In part 4 of our series, we discussed the Test-Driven Development philosophy in more detail and started a Test First implementation of a Tic-tac-toe game component.

Here’s the progress we’ve made on our requirements so far:

When the player goes first it should put their mark in the selected position it should make the next move When the player gets three in a row it should announce the player as the winner When the game gets three in a row it should announce the game as the winner When the player attempts to select an occupied position it should tell the player the position is occupied When the player attempts to select an invalid position it should tell the player the position is invalid When the player can not win on the next turn it should try to get three in a row When the player can win on the next turn it should block the player

Also, here is what our Game class implementation looks like so far:

public class Game { readonly char[] _layout = new char[9]; readonly string[] _winningPatterns = new[] { "[XO][XO][XO]......", "...[XO][XO][XO]...", "......[XO][XO][XO]", "[XO]..[XO]..[XO]..", ".[XO]..[XO]..[XO].", "..[XO]..[XO]..[XO]", "[XO]...[XO]...[XO]", "..[XO].[XO].[XO]..", }; public string ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; if (WinningPlayerIs('X')) return "Player wins!"; if (WinningPlayerIs('O')) return "Game wins."; return string.Empty; } bool WinningPlayerIs(char player) { return _winningPatterns .Any(pattern => Regex.IsMatch(GetLayoutFor(player), pattern)); } string GetLayoutFor(char player) { return new string(_layout.ToList() .Select(c => (c.Equals(player)) ? player : '\0') .ToArray()); } public char GetPosition(int position) { return _layout[position - 1]; } }

Picking up from here, let’s create our next test skeleton:

[TestClass] public class When_the_player_attempts_to_select_an_occupied_position { [TestMethod] public void it_should_tell_the_player_the_position_is_occupied() { } }

Again, we’ll start by determining how we want to validate our requirements. Let’s assume we’ll get a message of “That spot is taken!” if we try to choose a position that’s already occupied:

[TestMethod] public void it_should_tell_the_player_the_position_is_occupied() { Assert.AreEqual("That spot is taken!", message); }

Since our game is choosing positions sequentially, something easy we can do is to choose the second position, leaving the first open for the game to select. We can then attempt to choose the first position which should result in an error message. I wonder whether depending on the game to behave this way is going to cause any issues in the future though. Let’s move forward with this strategy for now:

[TestMethod] public void it_should_tell_the_player_the_position_is_occupied() { var game = new Game(); game.ChoosePosition(2); string message = game.ChoosePosition(1); Assert.AreEqual("That spot is taken!", message); }
 

When_the_player_attempts_to_select_an_occupied_position Failed it_should_tell_the_player_the_position_is_occupied Assert.AreEqual failed. Expected:<That spot is taken!>. Actual:<>.

As a reminder, we want to get our test to pass quickly. Since we can do this with an Obvious Implementation of checking if the position already has a value other than null and returning the expected error message, let’s do that this time:

public string ChoosePosition(int position) { if(_layout[position -1] != '\0') { return "That spot is taken!"; } _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; if (WinningPlayerIs('X')) return "Player wins!"; if (WinningPlayerIs('O')) return "Game wins."; return string.Empty; }

It’s time to run the tests again:

 

When_the_player_gets_three_in_a_row Failed it_should_announce_the_player_as_the_winner Assert.AreEqual failed. Expected:<Player wins!>. Actual:<>.

Our target test passed, but our changes broke one of the previous tests. The failing test was the one that checks that the player wins when getting three in a row. For our context setup, we just selected the first three positions without worrying about whether the positions were occupied or not. This was our third test and at that point we weren’t concerned with how the game was going to determine its moves, but it seems this decision wasn’t without some trade-offs. For now, we can just avoid the first three positions, but I’m starting to wonder if another strategy is in order. Perhaps a solution will reveal itself in time. To avoid the conflict, we’ll select positions from the middle row:

[TestMethod] public void it_should_announce_the_player_as_the_winner() { var game = new Game(); game.ChoosePosition(4); game.ChoosePosition(5); string message = game.ChoosePosition(6); Assert.AreEqual("Player wins!", message); }
 

 

We’re green again for now. Let’s move on to our next test:

[TestClass] public class When_the_player_attempts_to_select_an_invalid_position { [TestMethod] public void it_should_tell_the_player_the_position_is_invalid() { } }

Similar to our previous test, let’s assume a message is returned of “That spot is invalid!”:

[TestClass] public class When_the_player_attempts_to_select_an_invalid_position { [TestMethod] public void it_should_tell_the_player_the_position_is_invalid() { Assert.AreEqual("That spot is invalid!", message); } }

Now, let’s establish a context which should result in this behavior:

[TestClass] public class When_the_player_attempts_to_select_an_invalid_position { [TestMethod] public void it_should_tell_the_player_the_position_is_invalid() { var game = new Game(); string message = game.ChoosePosition(10); Assert.AreEqual("That spot is invalid!", message); } }

Time to run the tests:

 

When_the_player_attempts_to_select_an_invalid_position Failed it_should_tell_the_player_the_position_is_invalid TestFirstExample.When_the_player_attempts_to_select_an_invalid_position .it_should_tell_the_player_the_position_is_invalid threw exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.

The test failed, but not for the right reason. Let’s modify the Game class to return an unexpected value to validate our test:

public string ChoosePosition(int position) { if (position == 10) { return string.Empty; } if (_layout[position - 1] != '\0') { return "That spot is taken!"; } _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; if (WinningPlayerIs('X')) return "Player wins!"; if (WinningPlayerIs('O')) return "Game wins."; return string.Empty; }
 

When_the_player_attempts_to_select_an_invalid_position Failed it_should_tell_the_player_the_position_is_invalid Assert.AreEqual failed. Expected:<That spot is invalid!>. Actual:<>.

Now we can work on getting the test to pass. We can modify the Game class to check that the position falls within the allowable range about as quickly as we could use a fake implementation, so let’s just do that:

public string ChoosePosition(int position) { if (position < 1 || position > 9) { return "That spot is invalid!"; } if (_layout[position - 1] != '\0') { return "That spot is taken!"; } _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; if (WinningPlayerIs('X')) return "Player wins!"; if (WinningPlayerIs('O')) return "Game wins."; return string.Empty; }

 

 

 

Now, let’s refactor. While other issues may exist, the only duplication I see right now is that our new error checking duplicates knowledge about the size of the board. Since we need to modify this anyway, let’s go ahead and pull this section out into a separate method which describes what our intentions are:

public string ChoosePosition(int position) { if (IsOutOfRange(position)) { return "That spot is invalid!"; } if (_layout[position - 1] != '\0') { return "That spot is taken!"; } _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; if (WinningPlayerIs('X')) return "Player wins!"; if (WinningPlayerIs('O')) return "Game wins."; return string.Empty; } bool IsOutOfRange(int position) { return position < 1 || position > _layout.Count(); }
 

 

Let’s move on to our next test to describe what happens when the game goes first:

[TestClass] public class When_the_game_goes_first { [TestMethod] public void it_should_put_an_X_in_one_of_the_available_positions() { } }

To check that the game puts an ‘X’ in one of the positions, let’s use another enumerable range to check all of the positions for the expected value:

[TestMethod] public void it_should_put_an_X_in_one_of_the_available_positions() { Assert.IsTrue(Enumerable.Range(1, 9) .Any(position => game.GetPosition(position).Equals('X'))); }

Right now, our game only moves after we’ve chosen a position. We need a way of telling the game to go first, so let’s call a method called GoFirst():

[TestMethod] public void it_should_put_an_X_in_one_of_the_available_positions() { var game = new Game(); game.GoFirst(); Assert.IsTrue(Enumerable.Range(1, 9) .Any(position => game.GetPosition(position).Equals('X'))); }

Next, we’ll need to add our new method:

public class Game { // ... public void GoFirst() { } }

We’re ready to run the tests:

 

When_the_game_goes_first Failed it_should_put_an_X_in_one_of_the_available_positions Assert.IsTrue failed.

At this point we may have some ideas about how we might implement this, but there isn’t an obvious way I can think of that would only take a few seconds to write, so let’s Fake It again:

public void GoFirst() { _layout[0] = 'X'; }
 

 

Refactor time! As a first step, let’s copy the code we’re using in the ChoosePosition() to find the first available position and use it to assign the value ‘X’:

public void GoFirst() { int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'X'; }
 

 

Next, let’s factor out a method to remove the duplication between these two methods:

void SelectAPositionFor(char value) { int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = value; }

Now we can replace the locations in the ChoosePosition() and GoFirst() methods to call our new method:

public string ChoosePosition(int position) { if (IsOutOfRange(position)) { return "That spot is invalid!"; } if (_layout[position - 1] != '\0') { return "That spot is taken!"; } _layout[position - 1] = 'X'; SelectAPositionFor('O'); if (WinningPlayerIs('X')) return "Player wins!"; if (WinningPlayerIs('O')) return "Game wins."; return string.Empty; } public void GoFirst() { SelectAPositionFor('X'); }
 

 

We now have two places where the game determines what token it’s using, so let’s fix this. Let’s add a new method called GetTokenFor() which will determine whether the game is assigned an ‘X’ or an ‘O’. We’ll pass it a string of “game”, but we’ll just hard-code it to assign ‘X’ for now and see where this takes us:

public void GoFirst() { char token = GetTokenFor("game"); SelectAPositionFor(token); } char GetTokenFor(string player) { return 'X'; }
 

 

In order for our GetTokenFor() method to assign a token conditionally, it will need some way of figuring out who’s going first. If we keep track of the assignments in a dictionary, then this should be fairly straight forward:

Dictionary<string, char> _tokenAssignments = new Dictionary<string, char>(); char GetTokenFor(string player) { var nextToken = (_tokenAssignments.Count == 0) ? 'X' : 'O'; if (_tokenAssignments.ContainsKey(player)) return _tokenAssignments[player]; return _tokenAssignments[player] = nextToken; } }
 

 

Next, let’s change the ChoosePosition() method to use our new method instead of the hard-coded assignments:

public string ChoosePosition(int position) { if (IsOutOfRange(position)) { return "That spot is invalid!"; } if (_layout[position - 1] != '\0') { return "That spot is taken!"; } _layout[position - 1] = GetTokenFor("player"); SelectAPositionFor(GetTokenFor("game")); if (WinningPlayerIs('X')) return "Player wins!"; if (WinningPlayerIs('O')) return "Game wins."; return string.Empty; }

 

 

 

These changes have introduced some duplication in the form of magic strings, so let’s get rid of that. We can define an Enum to identify our players rather than using strings:

public enum Player { Human, Game }

Now we can change our dictionary, the GetTokenFor() method parameter type and the calls to GetTokenFor() from the ChoosePosition() and GoFirst() methods to use the new Enum:

readonly Dictionary<Player, char> _tokenAssignments = new Dictionary<Player, char>(); char GetTokenFor(Player player) { char nextToken = (_tokenAssignments.Count == 0) ? 'X' : 'O'; if (_tokenAssignments.ContainsKey(player)) return _tokenAssignments[player]; return _tokenAssignments[player] = nextToken; } public string ChoosePosition(int position) { if (IsOutOfRange(position)) { return "That spot is invalid!"; } if (_layout[position - 1] != '\0') { return "That spot is taken!"; } _layout[position - 1] = GetTokenFor(Player.Human); SelectAPositionFor(GetTokenFor(Player.Game)); if (WinningPlayerIs('X')) return "Player wins!"; if (WinningPlayerIs('O')) return "Game wins."; return string.Empty; } public void GoFirst() { char token = GetTokenFor(Player.Game); SelectAPositionFor(token); }

 

 

 

Now that we know this works, let’s refactor the rest of the methods that are still relying upon character values to identify the player along with their associated calls:

bool WinningPlayerIs(Player player) { return _winningPatterns .Any(pattern => Regex.IsMatch(GetLayoutFor(player), pattern)); } string GetLayoutFor(Player player) { return new string(_layout.ToList() .Select(c => (c.Equals(GetTokenFor(player))) ? GetTokenFor(player) : '\0') .ToArray()); } void SelectAPositionFor(Player player) { int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = GetTokenFor(player); } public void GoFirst() { SelectAPositionFor(Player.Game); } public string ChoosePosition(int position) { if (IsOutOfRange(position)) { return "That spot is invalid!"; } if (_layout[position - 1] != '\0') { return "That spot is taken!"; } _layout[position - 1] = GetTokenFor(Player.Human); SelectAPositionFor(Player.Game); if (WinningPlayerIs(Player.Human)) return "Player wins!"; if (WinningPlayerIs(Player.Game)) return "Game wins."; return string.Empty; }
 

 

Here’s what we have so far:

public class Game { readonly char[] _layout = new char[9]; readonly Dictionary<Player, char> _tokenAssignments = new Dictionary<Player, char>(); readonly string[] _winningPatterns = new[] { "[XO][XO][XO]......", "...[XO][XO][XO]...", "......[XO][XO][XO]", "[XO]..[XO]..[XO]..", ".[XO]..[XO]..[XO].", "..[XO]..[XO]..[XO]", "[XO]...[XO]...[XO]", "..[XO].[XO].[XO]..", }; public string ChoosePosition(int position) { if (IsOutOfRange(position)) { return "That spot is invalid!"; } if (_layout[position - 1] != '\0') { return "That spot is taken!"; } _layout[position - 1] = GetTokenFor(Player.Human); SelectAPositionFor(Player.Game); if (WinningPlayerIs(Player.Human)) return "Player wins!"; if (WinningPlayerIs(Player.Game)) return "Game wins."; return string.Empty; } bool IsOutOfRange(int position) { return position < 1 || position > _layout.Count(); } bool WinningPlayerIs(Player player) { return _winningPatterns .Any(pattern => Regex.IsMatch(GetLayoutFor(player), pattern)); } string GetLayoutFor(Player player) { return new string(_layout.ToList() .Select(c => (c.Equals(GetTokenFor(player))) ? GetTokenFor(player) : '\0') .ToArray()); } public char GetPosition(int position) { return _layout[position - 1]; } public void GoFirst() { SelectAPositionFor(Player.Game); } char GetTokenFor(Player player) { char nextToken = (_tokenAssignments.Count == 0) ? 'X' : 'O'; if (_tokenAssignments.ContainsKey(player)) return _tokenAssignments[player]; return _tokenAssignments[player] = nextToken; } void SelectAPositionFor(Player player) { int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = GetTokenFor(player); } }

We’ve only got two more requirements to go, but we’ll leave things here for now. Next time, we’ll complete our requirements by tackling what looks to be the most interesting portion of our game and perhaps we’ll discover a solution to our coupling woes in the process.

Tagged with:  

Effective Tests: How Faking It Can Help You

On March 29, 2011, in Uncategorized, by derekgreer

In part 4 of our series, I presented a Test-Driven Development primer before beginning our exercise.  One of the techniques I’d like to discuss a little further before we continue is the TDD practice of using fake implementations as a strategy for getting a test to pass. 

While not discounting the benefits of using the Obvious Implementation first when a clear and fast implementation can be achieved, the recommendation to “Fake It (Until You Make It)” participates in several helpful strategies, each with their own unique benefits:

 

Going Green Fast

Faking it serves as one of the strategies for passing the test quickly. This has several benefits:

One, it provides rapid feedback that your test will pass when the expected behavior is met. This can be thought of as a sort of counterpart to "failing for the right reason".

Second, it has psychological benefits for some, which can aid in stress reduction through taking small steps, receiving positive feedback, and providing momentum.

Third, it facilitates a "safety net" which can be used to provide rapid feedback if you go off course during a refactoring effort.

 

Keeping Things Simple

Faking it serves as one of the strategies for writing maintainable software.

Ultimately, we want software that works through the simplest means possible. The "Fake It" strategy, coupled with Refactoring (i.e. eliminating duplication) or Triangulation (writing more tests to prove the need for further generalization), leads to an additive approach to arriving at a solution that accommodates the needs of the specifications in a maintainable way.  Faking It + Refactoring|Triangulation is a disciplined formula for achieving emergent design.

 

Finding Your Way

Faking it serves as a strategy for reducing mental blocks. 

As the ultimate manifestation of “Do the simplest thing that could possibly work", quickly seeing how the test can be made to pass tends to shine a bit of light on what the next step should be. Rather than sitting there wondering how to implement a particular solution, faking it and then turning your attention to the task of eliminating duplication or triangulating the behavior will push you in the right direction.

 

Identifying Gaps

Faking it serves as a strategy for revealing shortcomings in the existing specifications.

Seeing first hand how easy it is to make your tests pass can help highlight how an implementation might be modified in the future without breaking the existing specifications.  Part of the recommended strategy for keeping your code maintainable is to remove unused generalization.  Generalization  which eliminates duplication is needed, but your implementation may include generalization for which the driving need isn’t particularly clear.  Using a fake implementation can help uncover behavior you believe should be explicitly specified, but isn’t required by the current implementation.  Faking it can lead to such questions as: “If I can make it pass by doing anything that produces this value, what might prevent someone from altering what I’m thinking of doing to eliminate this duplication?

 

Conditioning

Lastly, faking it helps to condition you to seeing the simplest path first.  When you frequently jump to the complex, robust, flexible solution, you’ll tend to condition yourself to think that way when approaching problems.  When you frequently do simple things, you’ll tend to condition yourself to seeing the possible simplicity in the solution provided.

 

Conclusion

While we should feel free to use an Obvious Implementation when present, The Test-Driven Development strategy of “Fake It (Until You Make It)” can play a part in several overlapping strategies which help us to write working, maintainable software that matters.

Tagged with:  

Part 3 of our series introduced Test-First Programming and briefly discussed its refinements: Test-Driven Development and Behavior-Driven Development.  This time, we’ll dive into a simple Test First example using the principles of Test-Driven Development along with some of the Behavior-Driven Development concepts introduced last time.

Before beginning our example, we’ll take some time to discuss the goals and approach of Test-Driven Development in a little more detail to establish the methodology we’ll be following.

 

Test-Driven Development Primer

So, what is Test-Driven Development?  The simple answer is that it’s a process of writing tests before writing code, but such a simple answer fails to convey the true spirit of Test-Driven Development.

Even if we’ve adopted a practice of writing tests first, we’re often tempted to start implementing all the functionality we think will be part of a particular feature in a way that appeals to our design sensibilities.  Many of us are conditioned to approach the implementation phase by asking such questions as “What will this feature need to fully work”, “What is the most flexible way of fulfilling this feature”, or perhaps “What future design needs will I have that I can begin leveraging for this feature”.  This line of thinking usually leads us down a road of implementing patterns and algorithms, creating infrastructure code, and configuring third-party libraries and frameworks we’ve already planned to use.  The end result of such efforts may be working code, perhaps even with high test coverage, but while this approach might be considered Test-Inspired Development, it wouldn’t be Test-Driven Development.  

The goal of Test-Driven Development isn’t to ensure we write tests by writing them first, but to produce working software that achieves a targeted set of requirements using simple, maintainable solutions.  To achieve this goal, TDD provides strategies for keeping code working, simple, relevant and free of duplication.

Keep it Working

To keep the code working, TDD encourages development in small steps under the protection and confidence of passing tests which properly validate the desired behavior of the system.  To help minimize the amount of time a test exists in a failing state, TDD sets forth two strategies for quickly getting a test to pass.  The first strategy, to be used when the solution is simple and straight forward, is to use the Obvious Implementation.  The second strategy, to be used when a solution isn’t clear or will take some time to complete, is to Fake It.  The Fake It strategy refers to a technique of satisfying the requirements of a test by merely returning hard-coded values.

Keep it Simple

To keep the code simple,  TDD sets forth several techniques to help guide the developer toward the simplest solution.  First, TDD encourages a willingness to take small steps.  Taking small steps, especially for complex and unclear tasks, helps develop an ability to identify simple solutions.  Second, TDD provides guidelines for when to add or change code.  By changing code only to satisfy a failing test, remove duplication, or to remove unused code, solutions are kept free of complexity.  Third, TDD encourages a willingness to just try the Simplest Thing That Could Possibly Work when the obvious implementation isn’t clear.  Fourth, TDD sets forth a technique called Triangulation (discussed later) for helping to identify needed generalization.

Keep it Relevant

To keep the code relevant, TDD encourages the removal of unused generalization.  While this also helps keep the code simple, simplicity doesn’t mean the code is actually needed.  Developers often create constructs to improve the flexibility and extensibility of an application or as an outlet for their creativity, but this frequently leads to overly complex solutions which are difficult to understand, difficult to maintain and which are often never used.  TDD helps avoid this by keeping us focused on just those things that are needed to satisfy our tests.

Keep it Free of Duplication

To keep the code free of duplication, TDD incorporates continuous refactoring into the development process.  After writing a failing test and making it pass as quickly as possible, TDD encourages the removal of duplication as the heuristic for the discovery and improvement of design.  In TDD, it is through the refactoring process, driven by the goal of removing duplication, that much of an application’s design emerges.

The TDD Process

To help facilitate these goals, Test-Driven Development prescribes a set of steps for us to follow:

  1. Write a test.

  2. Make it compile.

  3. Run it to see that it fails.

  4. Make it run.

  5. Remove duplication.

These steps are often simplified into a process referred to as Red/Green/Refactor.  First we write a failing test, then we make it pass, then we refactor.

With these steps and goals in mind, let’s dive into our example.

 

Requirements

For this example, we’ll be creating a simple Tic-tac-toe game that allows a single player to compete against the game.  Here are the requirements we’ll be working from:

When the player goes first it should put their mark in the selected position it should make the next move When the player gets three in a row it should announce the player as the winner When the game gets three in a row it should announce the game as the winner When the player attempts to select an occupied position it should tell the player the position is occupied When the player attempts to select an invalid position it should tell the player the position is invalid When the game goes first it should put an X in one of the available positions When the player can not win on the next turn it should try to get three in a row When the player can win on the next turn it should block the player

 

Creating the Specifications

While we’ll be striving to follow the Test-Driven Development process, we’ll also try to incorporate some of the Behavior-Driven Development concepts we learned from last time.   There are several frameworks which are designed specifically to facilitate  Behavior-Driven Development, but we’re going to use the Visual Studio Unit Testing Framework  to demonstrate how these concepts can be applied using traditional xUnit frameworks. While we could begin by creating some testing infrastructure code, let’s keep things simple for now and just use some of the improved naming concepts presented in our last article.

Our first step will be to create a new Visual Studio project. I’ll use the Test Project type and name the project TestFirstExample:

 

TestFirstExampleProjectNew

 

Next, let’s create a test class that reflects the context of our first requirement:

using Microsoft.VisualStudio.TestTools.UnitTesting; namespace TestFirstExample { [TestClass] public class WhenThePlayerGoesFirst { } }

 

Next, let’s create a method that expresses the first observable behavior for our context:

using Microsoft.VisualStudio.TestTools.UnitTesting; namespace TestFirstExample { [TestClass] public class WhenThePlayerGoesFirst { [TestMethod] public void ItShouldPutTheirChoiceInTheSelectedPosition() { } } }

 

Since we’ve chosen to follow a more Behavior-Driven Development approach to writing our tests, let’s take a second and reflect on how this reads. Thus far, we’ve used the same wording from our requirements, but the class and method identifiers seem a little difficult to read. Since we’re using code to represent sentences instead of entities and methods, let’s try changing our naming convention to use underscores for separating each word in our identifiers and see how it looks:

using Microsoft.VisualStudio.TestTools.UnitTesting; namespace TestFirstExample { [TestClass] public class When_the_player_goes_first { [TestMethod] public void it_should_put_their_choice_in_the_selected_position() { } } }

 

In C#, this breaks with our normal naming conventions for class and method names, but this seems a little easier to read in my opinion.

 

Naming Conventions

The use of underscores for separating the words within identifiers is a commonly used convention for writing BDD specifications, especially within languages that don’t support the use of spaces. When first encountered, some developers find this unsettling due to the fact that it often represents a deviation from the standard naming convention for the language being used.

For those who feel uncomfortable with this style due to it’s deviation from standard conventions, it may help to think about naming conventions in terms of modeling. Just as we use a domain model to represent a business domain, a data model to represent data persistence concerns, and a view model to represent a user interface, BDD specifications are a kind of model for representing our requirements. As such, they deserve to be viewed within their own context. In this case, specifications are all about modeling requirements, not real-world entities. As such, some have chosen to depart from standard naming conventions when modeling specifications.

Of course, this is a matter of preference. What’s important is to view the modeling of specifications as its own concern and allow this understanding to help guide our modeling efforts.

 

The first step of our test method should be to determine how we want to verify that a player’s choice results in an X being placed in the selected position. To do this, we’ll need a way of evaluating the state of the game. One approach we could take would be to simply ask the game to tell us what value is in a given position. Since a Tic-tac-toe game is comprised of a grid with nine positions, we can refer to each position by number, numbering them from left to right, top to bottom. Let’s assume we can call a method named GetPosition():

[TestMethod] public void it_should_put_their_choice_in_the_selected_position() { Assert.AreEqual('X', game.GetPosition(1)); }

 

Next, we need to create the instance of our Game variable and determine how we’re going to inform it about our choice. A common rule of Tic-tac-toe is to let ‘X’ go first, so we can keep things simple if we adopt this rule. The only input needed in that case is the position, so let’s call a method named ChoosePosition() and pass an integer value of 1 to indicate the top-left position on the board:

[TestMethod] public void it_should_put_their_choice_in_the_selected_position() { var game = new Game(); game.ChoosePosition(1); Assert.AreEqual('X', game.GetPosition(1)); }

 

Of course, this won’t compile since our Game class doesn’t exist yet. Let’s create our class and methods now:

public class Game { public void ChoosePosition(int position) { throw new NotImplementedException(); } public char GetPosition(int position) { throw new NotImplementedException(); } }

 

Everything should be ready to compile. Let’s run our test and see what happens:

 

Test method TestFirstExample.When_the_player_goes_first .it_should_put_their_choice_in_the_selected_position threw exception: System.NotImplementedException: The method or operation is not implemented.

 

As you probably expected, our test fails. Since we’re following the Red/Green/Refactor process, you might think we’re ready to work on getting the test to pass now. Not so fast! The purpose of writing a failing test isn’t failure, but validation. At this point, what we should be interested in is that the behavior of our code will be correctly validated once we write it. To help explain, let’s consider what would happen if we had mistakenly written the test as follows:

[TestClass] public class When_the_player_goes_first { [TestMethod] public void it_should_put_their_choice_in_the_selected_position() { var game = new Game(); game.ChoosePosition(1); Assert.AreNotEqual('X', game.GetPosition(1)); } }

 

Here, we’ve changed Assert.AreEqual() to Assert.AreNotEqual(). How do you think this will change the outcome of the test? If you guessed it would not change the outcome then you’re correct. Our test didn’t fail because of our assert, but rather because an exception halted the test before it even got to the assertion. That doesn’t tell us anything about the validity of how we’ve written our test. Merely getting a test to fail doesn’t serve a useful purpose. Our test needs to fail for the right reason.

In this case, we want our test to fail because the call to the GetPosition() method didn’t return an X. Let’s remove the calls which are causing exceptions to be thrown and change the GetPosition() method to return a value that forces the test to fail due to the assertion:

public class Game { public void ChoosePosition(int position) { } public char GetPosition(int position) { return '\0'; } }

 

Running our test again produces the following output:

 

When_the_player_goes_first Failed it_should_put_their_choice_in_the_selected_position Assert.AreEqual failed. Expected:<X>. Actual:<\0>.

 

We now see that our test is failing due to the assertion properly validating the behavior of our class. Our next step is getting our test to pass quickly. Let’s use the Fake It approach and simply return the expected value of ‘X’:

public class Game { public void ChoosePosition(int position) { } public char GetPosition(int position) { return 'X'; } }

 

 

 

Our first test passes! Our next step is to eliminate any unnecessary generalization and duplication. While we don’t have any generalization to remove, we do have duplication. At first, our duplication may be difficult to spot as we’re typically accustomed to thinking about it in terms of repeated code blocks, but duplication manifests itself in many forms. In this case we have a form of duplication which I like to think of as Mirroring. We’re simply reflecting back to the test what it is expecting to see. This form of duplication is often the result of using the Fake It approach to getting tests to pass quickly, but this isn’t a bad thing. On the contrary, starting with hard-coded values and generalizing based upon a need to remove duplication helps to ensure that we aren’t introducing generalization unnecessarily.

To remove our duplication, let’s keep track of the player’s choice in a layout field which we can set when the ChoosePosition() method is called and then retrieve when the GetPosition() method is called:

public class Game { char _layout; public void ChoosePosition(int position) { _layout = 'X'; } public char GetPosition(int position) { return _layout; } }

 

 

 

You might be wondering why I chose such a naïve solution here. Won’t we need something that can keep track of all the positions on the board to accommodate other scenarios? The answer is yes, but we don’t have tests to drive our need to accommodate other scenarios yet. By only using the simplest constructs needed by our existing tests, we help to ensure we aren’t writing more complex code than necessary.

Another question that could be posed is, if we’re concerned with eliminating waste, doesn’t this process itself end up causing waste? The answer is yes and no. It can be reasoned that doing simple things does lead to waste in small degrees, but the benefit of allowing our design to emerge from this process is that it helps to eliminate large areas of waste.

Another question some may be wondering is whether taking small steps is a rule we have to follow when doing TDD. The answer to that question is no, but it is a useful methodology that helps keep us honest. We can take larger steps when it makes sense to do so, but it’s important to watch that our steps aren’t getting too large. You’ll know your steps are getting too large when you or others start frequently discovering unnecessary code or simpler solutions. By using this approach as a general practice, it helps to condition our ability to identify simple solutions. In his book Test-Driven Development By Example, Kent Beck points out that TDD is not about taking teeny-tiny steps, it’s about being able to take teeny-tiny steps.

Let’s move on to our next requirement. We’ll start by creating a new test method:

[TestMethod] public void it_should_make_the_next_move() { }

 

Again, our first step is to determine how we want to verify the behavior of the requirement. Since this test would be satisfied by the game choosing any position, we can verify the expected behavior occurred by checking all the positions. A clean way to do this is to create the range we want to check using Enumerable.Range() and then use LINQ’s Any() extension method to check each position for the character ‘O’. The Any() method returns true as soon as one of the enumerated elements is satisfied by the predicate:

[TestMethod] public void it_should_make_the_next_move() { Assert.IsTrue(Enumerable.Range(1, 9) .Any(position => game.GetPosition(position) .Equals('O'))); }

 

Next, we need to establish the context for the test. To avoid duplication, let’s move the context setup from our first test into a setup method and modify our tests to use a class field:

[TestClass] public class When_the_player_goes_first { Game _game; [TestInitialize] public void establish_context() { _game = new Game(); _game.ChoosePosition(1); } [TestMethod] public void it_should_put_their_choice_in_the_selected_position() { Assert.AreEqual('X', _game.GetPosition(1)); } [TestMethod] public void it_should_make_the_next_move() { Assert.IsTrue(Enumerable.Range(1, 9) .Any(position => _game.GetPosition(position) .Equals('O'))); } }

 

Now we’re ready to run our test. Since we’ve modified things, let’s run both the tests just to make sure we didn’t break the first one:

 

When_the_player_goes_first Failed it_should_make_the_next_move Assert.IsTrue failed.

 

From the output, we see that we didn’t break our first test and our second test is failing for the right reason. Next, let’s make our new test pass. To do so, the GetPosition() method will need to return an ‘O’ for one of the nine times it’s called. Let’s use the Fake It approach again to get the test to pass quickly. We can change our _layout field to a two character array and hard-code the ChoosePosition() method to assign ‘O’ to the second position:

public class Game { char[] _layout = new char[2]; public void ChoosePosition(int position) { _layout[position - 1] = 'X'; _layout[1] = 'O'; } public char GetPosition(int position) { return _layout[position - 1]; } }

 

 

 

Now that the tests pass, we can move on to factoring out any duplication we’ve introduced. This time, however, we aren’t checking a specific position that’s we’ve hard coded a value to satisfy. On the other hand, we know this solution isn’t going to be flexible enough based on our knowledge about the rules of the game. How should we proceed?

To introduce generalization in cases where duplication either doesn’t exist or isn’t readily apparent, we can use a strategy called Triangulation. Similar to the mathematical process of determining the third point of a triangle given the length and angles of the first two points, this process helps you locate a generalized solution based upon two tests which verify the results in intersecting ways.

To demonstrate, let’s create a second test which validates the behavior of our class in a slightly different way which the current implementation wouldn’t satisfy:

[TestClass] public class When_the_player_selects_the_second_position { [TestMethod] public void it_should_put_their_choice_in_the_second_position() { Game game = new Game(); game.ChoosePosition(2); Assert.AreEqual('X', game.GetPosition(2)); } }

 

 

When_the_player_selects_the_second_position Failed it_should_put_their_choice_in_the_second_position Assert.AreEqual failed. Expected:<X>. Actual:<O>.

 

Our test fails because the method is hard-coded to assign ‘O’ to the second position. In order to make this test pass, we’ll now need to change our code to a more general solution. To replace the hard-coded selection of the second position, we can use LINQ to locate the first unoccupied element of the array and use that position for our assignment:

public class Game { readonly char[] _layout = new char[2]; public void ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; } public char GetPosition(int position) { return _layout[position - 1]; } }

 

 

 

Our new test now passes. Our next step would be to factor out any duplication we might have introduced to satisfy the new test, but it doesn’t look like we introduced any this time.

While the use of Triangulation provides a simple to understand and explicit driver for introducing a generalized solution, it’s use is a somewhat inelegant strategy. Once a generalized solution is introduced, intersecting tests become redundant from a specification perspective. They can be useful in cases when it isn’t clear why a generalized solution exists and you need to ensure the solution isn’t lost through a future refactoring, but the resulting extra tests lead to higher test maintenance costs and can lead to confusion when included in reports or used as documentation by other team members. When possible, prefer removing duplication over triangulation as a generalization strategy. If duplication isn’t present or is of a particularly difficult to spot nature and generalization won’t be addressed by a separate requirement then it may be beneficial to leave redundant tests. To help aid in clarifying the intent of the test, we can use a TestCategoryAttribute as follows:

[TestClass] public class When_the_player_selects_the_second_position { [TestCategory("Triangulation")] [TestMethod] public void it_should_put_their_choice_in_the_second_position() { var game = new Game(); game.ChoosePosition(2); Assert.AreEqual('X', game.GetPosition(2)); } }

 

This helps to identify the reason for the test to other team members in a standard way and provides a mechanism for creating reports which don’t include the redundant test cases when desired. From the command line, our tests can be run without including the triangulation tests using the following:

c:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe /category:'!Triangulation' \
/testcontainer:TestFirstExample.dll

 

For frameworks which provide non-sealed attributes for identifying categories of tests, we can create our own strongly-typed attribute to minimize any additional syntax noise as shown in the following contrived example:

public class TriangulationAttribute : SomeFrameworkCategoryAttribute { public MyAttribute() : base("Triangulation") { } }

 

While perhaps not easily discerned, we might have avoided the use of Triangulation in the above example had we considered our Any() extension method to have been hiding a form of duplication. When we used the Any() method to look for the first occurrence of a position with the value ‘O’, this was logically the same as if we had written the following:

[TestMethod] public void it_should_make_the_next_move() { char[] positions = new char[2]; bool gamePositionFound = false; for(int i = 0; i < 2; i++) { positions[i] = _game.GetPosition(i + 1); } for (int i = 0; i < 9; i++) { if (positions[i].Equals('O')) { gamePositionFound = true; break; } } Assert.IsTrue(gamePositionFound); }

 

Breaking the logic down further, our for loop would have performed the following checks:

if(positions[0].Equals(‘O’)) { … } if(positions[1].Equals(‘O’)) { … }

 

Based on a more explicit expression of what our code was doing, it becomes easier to identify the duplication that was present.

Moving on, our next requirement concerns how we’ll determine when the player has won the game. Let’s create a new test class along with it’s single observation:

[TestClass] public class When_the_player_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_player_as_the_winner() { } }

 

We know that we want to end up with a message that reflects that the player won, so let’s start with this as our assertion:

[TestClass] public class When_the_player_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_player_as_the_winner() { Assert.AreEqual("Player wins.", message); } }

 

Next, we need to determine where our message will come from. Let’s assume our ChoosePosition() method returns a message after the player makes a move. We’ll call the ChoosePosition() method three times and declare our Game instance:

[TestClass] public class When_the_player_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_player_as_the_winner() { Game game = new Game(); game.ChoosePosition(1); game.ChoosePosition(2); string message = game.ChoosePosition(3); Assert.AreEqual("Player wins!", message); } }

 

To get this to compile, we need to change the ChoosePosition() method to return a string:

public string ChoosePosition(int position) { _layout[position - 1] = value; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; return string.Empty; }

 

We’re now ready to run our tests:

 

When_the_player_gets_three_in_a_row Failed it_should_announce_the_player_as_the_winner Test method TestFirstExample.When_the_player_gets_three_in_a_row .it_should_announce_the_player_as_the_winner threw exception: System.InvalidOperationException: Sequence contains no matching element

 

It looks like our test isn’t failing for the right reason yet. The problem is that our array isn’t large enough for our new test scenario. Let’s go ahead and take the larger step of increasing the layout to accommodate the full 9 positions of the game board and run our test again:

readonly char[] _layout = new char[9];

 

 

When_the_player_gets_three_in_a_row Failed it_should_announce_the_player_as_the_winner Assert.AreEqual failed. Expected:<Player wins!>. Actual:<>.

 

Now, let’s make it pass. We can just return the string “Player wins!” to get the test to pass quickly:

public class Game { readonly char[] _layout = new char[9]; public string ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; return "Player wins!"; } public char GetPosition(int position) { return _layout[position - 1]; } }

 

 

 

We now have the string “Player wins!” duplicated, so we’ll fix this by putting in some logic to test that the first three positions contain the value ‘X’:

public class Game { readonly char[] _layout = new char[9]; public string ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; if (new string(_layout.ToArray()).StartsWith("XXX")) return "Player wins!"; return string.Empty; } public char GetPosition(int position) { return _layout[position - 1]; } }

 

 

 

We can now move on to our next requirement which concerns determining when the game wins. Here’s our test skeleton:

[TestClass] public class When_the_game_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_game_as_the_winner() { } }

 

Again, we’ll start by declaring our assertion and then fill in the context of the test. Since we know that the game chooses the first unoccupied space, we can leverage this by choosing positions which leave the first row open:

[TestClass] public class When_the_game_gets_three_in_a_row { [TestMethod] public void it_should_announce_the_game_as_the_winner() { Game game = new Game(); game.ChoosePosition(4); game.ChoosePosition(6); string message = game.ChoosePosition(8); Assert.AreEqual("Game wins.", message); } }

 

 

When_the_game_gets_three_in_a_row Failed it_should_announce_the_game_as_the_winner Assert.AreEqual failed. Expected:<Game wins.>. Actual:<>.

 

To get the test to pass, we can use similar logic as before to check that the layout starts with “OOO”:

public string ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; if (new string(_layout.ToArray()).StartsWith("XXX")) return "Player wins!"; if (new string(_layout.ToArray()).StartsWith("OOO")) return "Game wins."; return string.Empty; }

 

 

 

Now, let’s refactor. In reviewing our code, we see that we have some similarities between the logic checking if the player wins and the logic checking if the game wins. We need a way of generalizing this comparison so that it works for both. One option would be to use regular expressions. If we have an array of winning patterns, we can convert the layout to a string and compare it to each one in a loop. Let’s figure up all the winning patterns real quick:

tic-tac-toe

It looks like we have eight different winning patterns. Flattening this out becomes:

X X X _ _ _ _ _ _, _ _ _ X X X _ _ _, _ _ _ _ _ _ X X X, X _ _ X _ _ X _ _, _ X _ _ X _ _ X _, _ _ X _ _ X _ _ X, X _ _ _ X _ _ _ X, _ _ X _ X _ X _ _

 

Let’s start by creating a new array reflecting the winning patterns for ‘X’:

public class Game { readonly char[] _layout = new char[9]; readonly string[] _winningXPatterns = new[] { "XXX......", "...XXX...", "......XXX", "X..X..X..", ".X..X..X.", "..X..X..X", "X...X...X", "..X.X.X..", }; public string ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; if (new string(_layout.ToArray()).StartsWith("XXX")) return "Player wins!"; if (new string(_layout.ToArray()).StartsWith("OOO")) return "Game wins."; return string.Empty; }

 

Next, we need to create a string representation of our current layout:

public string ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; var layoutAsString = new string(_layout); if (new string(_layout.ToArray()).StartsWith("XXX")) return "Player wins!"; if (new string(_layout.ToArray()).StartsWith("OOO")) return "Game wins."; return string.Empty; }

 

Next, let’s replace the previous comparison checking if the layout starts with “XXX” with a loop that checks each winning pattern to the current layout:

public string ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; var layoutAsString = new string(_layout); foreach (string pattern in _winningXPatterns) { if (Regex.IsMatch(layoutAsString, pattern)) return "Player wins!"; } if (new string(_layout.ToArray()).StartsWith("OOO")) return "Game wins."; return string.Empty; }

 

 

 

Everything is still working. Now, let’s make the same changes for the game comparison:

public class Game { readonly char[] _layout = new char[9]; readonly string[] _winningXPatterns = new[] { "XXX......", "...XXX...", "......XXX", "X..X..X..", ".X..X..X.", "..X..X..X", "X...X...X", "..X.X.X..", }; readonly string[] _winningOPatterns = new[] { "OOO......", "...OOO...", "......OOO", "O..O..O..", ".O..O..O.", "..O..O..O", "O...O...O", "..O.O.O..", }; public string ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; var layoutAsString = new string(_layout); foreach (string pattern in _winningXPatterns) { if (Regex.IsMatch(layoutAsString, pattern)) return "Player wins!"; } foreach (string pattern in _winningOPatterns) { if (Regex.IsMatch(layoutAsString, pattern)) return "Game wins."; } return string.Empty; } public char GetPosition(int position) { return _layout[position - 1]; } }

 

 

 

We’re still green. At this point we’ve actually introduced a little more duplication than we started with, but we’re making small steps that will eventually lead us to a solution. Next, let’s see if we can combine our two arrays to represent the winning position for both players. Since we’re using a regular expression, we can replace the characters with character sets like this:

readonly string[] _winningPatterns = new[] { "[XO][XO][XO]......", "...[XO][XO][XO]...", "......[XO][XO][XO]", "[XO]..[XO]..[XO]..", ".[XO]..[XO]..[XO].", "..[XO]..[XO]..[XO]", "[XO]...[XO]...[XO]", "..[XO].[XO].[XO]..", };

 

We’ll then need to change the comparisons to use the new array. Since we only want to compare one side at a time, we’ll also need to filter out the characters we don’t want:

public string ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; string layoutAsString = new string(_layout).Replace('O', '\0'); foreach (string pattern in _winningPatterns) { if (Regex.IsMatch(layoutAsString, pattern)) return "Player wins!"; } layoutAsString = new string(_layout).Replace('X', '\0'); foreach (string pattern in _winningPatterns) { if (Regex.IsMatch(layoutAsString, pattern)) return "Game wins."; } return string.Empty; }

 

 

 

Now, let’s take care of the looping duplication by creating a method that will perform the comparison based on the given side we’re interested in:

bool WinningPlayerIs(char player) { var layout = new string(_layout); if (player == 'X') layout = layout.Replace('O', '\0'); else layout = layout.Replace('X', '\0'); foreach (string pattern in _winningPatterns) { if (Regex.IsMatch(layout, pattern)) return true; } return false; }

 

We can now replace our comparisons with a call to our new method:

public string ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; if (WinningPlayerIs('X')) return "Player wins!"; if (WinningPlayerIs('O')) return "Game wins."; return string.Empty; }

 

 

 

Now, let’s clean up our new WinningPlayerIs() method. We’re duplicating the call to Replace() based upon which character we’re wanting the layout for, so instead of this let’s use LINQ to create an array with the character we want filtered out:

bool WinningPlayerIs(char player) { var layout = new string(_layout.ToList() .Select(c => (c.Equals(player)) ? player : '\0') .ToArray()); foreach (string pattern in _winningPatterns) { if (Regex.IsMatch(layout, pattern)) return true; } return false; }

 

 

 

That’s more concise, but it could stand to be more descriptive. Rather than adding a comment, let’s just wrap this in an intention-revealing method:

bool WinningPlayerIs(char player) { var layout = GetLayoutFor(player); foreach (string pattern in _winningPatterns) { if (Regex.IsMatch(layout, pattern)) return true; } return false; } string GetLayoutFor(char player) { return new string(_layout.ToList() .Select(c => (c.Equals(player)) ? player : '\0') .ToArray()); }

 

We can also eliminate declaring multiple exit points and simplify the comparison by using LINQ’s Any() extension method:

bool WinningPlayerIs(char player) { var layout = GetLayoutFor(player); return _winningPatterns.Any(pattern => Regex.IsMatch(layout, pattern)); }

 

Let’s go ahead and in-line our call to GetLayoutFor(player):

bool WinningPlayerIs(char player) { return _winningPatterns .Any(pattern => Regex.IsMatch(GetLayoutFor(player), pattern)); }

 

 

 

Here’s what we have so far:

public class Game { readonly char[] _layout = new char[9]; readonly string[] _winningPatterns = new[] { "[XO][XO][XO]......", "...[XO][XO][XO]...", "......[XO][XO][XO]", "[XO]..[XO]..[XO]..", ".[XO]..[XO]..[XO].", "..[XO]..[XO]..[XO]", "[XO]...[XO]...[XO]", "..[XO].[XO].[XO]..", }; public string ChoosePosition(int position) { _layout[position - 1] = 'X'; int firstUnoccupied = Enumerable.Range(0, _layout.Length) .First(p => _layout[p].Equals('\0')); _layout[firstUnoccupied] = 'O'; if (WinningPlayerIs('X')) return "Player wins!"; if (WinningPlayerIs('O')) return "Game wins."; return string.Empty; } bool WinningPlayerIs(char player) { return _winningPatterns .Any(pattern => Regex.IsMatch(GetLayoutFor(player), pattern)); } string GetLayoutFor(char player) { return new string(_layout.ToList() .Select(c => (c.Equals(player)) ? player : '\0') .ToArray()); } public char GetPosition(int position) { return _layout[position - 1]; } }

 

We’ll leave things here for now and continue our effort next time.

Tagged with:  

Effective Tests: Test First

On March 22, 2011, in Uncategorized, by derekgreer

In part 2 of our series, I presented a simple example of a traditional unit test.  In this article, we’ll discuss a completely different way to use unit tests: Test First Programming.

Test-First Programming

In the book Extreme Programming Explained: Embrace Change published in October 1999, Kent Beck introduced what was at the time a rather foreign concept to software development: Writing unit tests to drive software design.  Described as Test-First Programming, the technique involves first writing a test to verify a small increment of desired functionality and then writing code to make the test pass.  This process is then repeated until no additional functionality is desired.

Incremental Design

Another practice introduced alongside Test-First Programming was Incremental Design.  Incremental design is the process of evolving the design of an application as new functionality is developed and better design approaches are recognized.  Due to the fact that software development is an inherently empirical process,  following a defined model where the the software is designed up front generally leads to waste and rework.  Incremental design postpones design decisions until needed, introducing patterns and frameworks as a byproduct of refactoring as well as eliminating any flexibility that is unused.

Test Driven Development

Later, these ideas were  refined into a practice known as Test Driven Development.  Test-Driven Development can be described simply as the ideas of Test-First Programming coupled with Incremental Design.  In a later book entitled Test-Driven Development By Example published in November of 2002, Beck presents a refined process involving the writing of a failing test, taking the simplest steps possible to make the test pass, and removing any duplication introduced.  This process was described succinctly as Red/Green/Refactor, where red and green referred to the colors typically presented by test runners to indicate failing and passing tests respectively.

Behavior-Driven Development

Test Driven Development

 

While the practice of Test-Driven Development utilized unit tests primarily for driving application design, its outgrowth from traditional unit testing practices were still evident in its style,  language, and supporting frameworks. This presented an obstacle for some in understanding and communicating the concepts behind the practice.

In the course of teaching and practicing TDD, Dan North began encountering various strategies for designing tests in a more intent-revealing way which aided in his understanding and ability to convey the practice to others.  As a way to help distinguish the differences between what he saw as an exercise in defining behavior rather than testing, he began describing the practice of Test-Driven Development as Behavior Driven Development.  By shifting the language to the discussion about behavior, he found that many of the questions he frequently encountered in presenting TDD concepts became easier to answer.  North went on to introduce a framework incorporating this shift in thinking called JBehave.  North’s work on JBehave along with his promotion of the ideas behind Behavior-Driven Development served as the inspiration for the creation of many other frameworks including RSpec, Cucumber, NBehave, NSpec, SpecFlow, MSpec and many others.

Behavior-Driven Development Approaches

Two main approaches have emerged from BDD practices which I’ll categorize as the Plain-Text Story approach and the Executable Specification approach.

Plain-Text Stories

With the Plain-Text Story approach, software features are written as a series of plain-text scenarios which the framework associates with executable steps used to drive feature development.  This approach is typically associated with a style referred to Given/When/Then (GWT), named after the grammar set forth by Dan North.  This style is facilitated by popular BDD frameworks such as Cucumber and JBehave.

The following is an example story using Gherkin grammar, the language used by Cucumber and other Plain-Text Story BDD frameworks:

Feature: Addition
   In order to calculate totals
   As a user
   I want to calculate the sum of multiple numbers

Scenario: Add two numbers
   Given I have entered 10 into the calculator
   And I have entered 50 into the calculator
   When I press add
   Then the result should be 60 on the screen

 

Using Cucumber, the “Given” step in this scenario may then be associated to the following step definition in Ruby:

Given /I have entered (\d+) into the calculator/ do |n|
  calc = Calculator.new
  calc.push n.to_i
end

 

Using SpecFlow, the same “Given” step can also be associated to the following step definition in C#:

[Given("I have entered (.*) into the calculator")]
public void GivenIHaveEnteredSomethingIntoTheCalculator(int number)
{
    _calculator = new Calculator();
    _calculator.Enter(number);
}

Executable Specifications

In contrast to the use of plain-text stories, the Executable Specification approach uses source code as the medium for expressing specifications in the language of the business.  Executable Specifications can be written using existing unit-testing frameworks such as JUnit, NUnit, and MSTest, or are written in frameworks designed exclusively to facilitate the Behavior-Driven Development style such as RSpec and MSpec.

One style, referred to as Context/Specification, expresses software features in terms of specifications within a given usage context.  In this style, each unique way the software will be used is expressed as a context along with one or more expected outcomes.

While specialized frameworks have been created to support the Context/Specification style, this approach can also be facilitated using existing xUnit frameworks.  This is generally accomplished by creating a Context base class which uses the Template Method pattern to facilitate the desired language.

The following example demonstrates this technique using the NUnit framework:

    [TestFixture]
    public abstract class Context
    {
        [TestFixtureSetUp]
        public void setup()
        {
            establish_context();
            because_of();
        }

        [TestFixtureTearDown]
        public void teardown()
        {
            cleanup();
        }

        protected virtual void establish_context()
        {
        }

        protected virtual void because_of()
        {
        }

        protected virtual void cleanup()
        {
        }
    }

 

In addition to providing a Contact base class, the NUnit framework’s TestAttribute can also be sub-classed to aid in moving toward specification-oriented language:

 

public class ObservationAttribute : TestAttribute {}

 

By extending the Context base class, NUnit can then be used to write Context/Specification style BDD specifications:

 

public class when_adding_two_numbers : Context
{
    Calculator _calculator;

    protected override void establish_context()
    {
        _calculator = new Calculator();
        _calculator.Enter(2);
        _calculator.PressPlus();
        _calculator.Enter(2);
    }

    protected override void because_of()
    {
        _calculator.PressEnter();
    }

    [Observation]
    public void it_should_return_the_correct_sum()
    {
        Assert.AreEqual(_calculator.Result, 4); 
    }
}

Working, Maintainable Software That Matters

The ideas behind Test-First Programming continue to be refined by the software development community and have lead to still other variations in name and style, including Example Driven-Development, Specification-Driven Development, Need-Driven Development, and Story Test-Driven Development.  Regardless of its label or flavor, the refinements of Test-First Programming can be distilled down to the following goal: The creation of working, maintainable software that matters.  That is to say, the software should be functional, should be easily maintainable and should be relevant to needs of our customers.

Conclusion

This time we discussed Test-First Programming and traced its steps to today’s practice of Behavior-Driven Development.  Next time we’ll discuss Test-Driven Development in more detail and walk through a Test First example using the TDD methodology while incorporating some of the Behavior-Driven Development concepts presented here.

Tagged with:  

Effective Tests: A Unit Test Example

On March 14, 2011, in Uncategorized, by derekgreer

In part 1 of our series, I introduced some common types of automated tests: Unit Tests, Integration Tests, and Acceptance Tests.  In this article, we’ll take a look at a traditional approach to unit testing and discuss a few practices surrounding good test communication.

First Things First

Before getting started, we obviously need something to test, so let’s create a Calculator class.

   public class Calculator
    {
        bool _isDirty;

        string _operation;
        decimal _state;

        public decimal Display { get; private set; }

        public void Enter(decimal number)
        {
            _state = number;
            _isDirty = true;
        }

        public void PressPlus()
        {
            _operation = "+";
            if (_isDirty) Calculate();
        }

        public void PressEquals()
        {
            if (_isDirty) Calculate();
        }

        void Calculate()
        {
            switch (_operation)
            {
                case "+":
                    Display += _state;
                    break;
            }

            _isDirty = false;
        }
    }

To keep things simple, our Calculator class just adds decimals, but that gives us enough to work with for our discussion.

Anatomy of a Unit Test

Now, let’s create a unit test to validate that our Calculator class behaves properly.  I’ll use NUnit for this example:

[TestFixture]
public class CalculatorTests
{
    [Test]
    public void TestPressEquals()
    {
        Calculator calculator = new Calculator();
        calculator.Enter(2m);
        calculator.PressPlus();
        calculator.Enter(2m);
        calculator.PressEquals();
        Assert.AreEqual(4m, calculator.Display);
    }
}

So, what’s going on here?  Starting at the top, we have a class with a TestFixture attribute.  This tells NUnit that this class contains unit tests.  We’ve named our test “CalculatorTests” to denote that this class contains the tests for our Calculator class.  Pretty descriptive, eh?

Next, we’ve defined a method with a Test attribute.  This tells NUnit that this method should be considered a test when it scans our types for tests to execute.  We’ve named our method TestPressEquals() to denote that we’re testing the behavior of the PressEquals method. 

Note

The use of the prefix “Test” is a common convention which originated from a requirement within earlier versions of JUnit.  The first version of NUnit was a direct port of JUnit and therefore shared this requirement until it was rewritten to take advantage of .Net attributes.

Next, we’ve defined an instance of the Calculator class we’re going to be testing.  In unit testing parlance, this is generally called the System Under Test, or the SUT.

Next, we make several calls to the calculator object to instruct it to add two numbers and then call the PressEquals() method.

Our final call is to the NUnit Assert.AreEqual() method.  This method tests the expected value against the actual value and raises an exception if the values are not equal.

If we run the test at this point, we should see our test pass.

 

Hooray!

 

Improving Communication

While our test certainly does the job of validating the Calculator’s behavior, there are a few aspects of our test that we could improve upon.  To start, let’s consider what would be displayed should our test actually ever fail.  To force it to fail, I’ll comment out the line which increments the Display property.  Running the test produces the following:

 

CalculatorTests.TestPressEquals : Failed 
Expected: 4m 
But was:  0m

 

Ok, so CalculatorTests.TestEquals failed because "expected 4m, but was 0m".  Given that we’ve just written this test, this may not seem like much of an issue.  The context of how our Calculator class works as well as how our test verifies it behavior is still fresh in our minds.  But what if this fails six months from now?  Worse, what if it fails and is discovered by someone who wasn’t the author of this test?  Without looking at the test code, the most that could be derived from this message is that the Calculator.PressEquals method failed in some way.  Let’s see if we can improve upon this.

What would be great is if we could have the test runner print out a descriptive message describing what didn’t work.  As it happens, NUnit provides an overload to the TestEquals method which allows us to supply our own message.  Let’s change our test to include a custom message:

        [Test]
        public void TestPressEquals2()
        {
            var calculator = new Calculator();
            calculator.Enter(2);
            calculator.PressPlus();
            calculator.Enter(2);
            calculator.PressEquals();
            Assert.AreEqual(4, calculator.Display,
                "When adding 2 + 2, expected 4 but found {0}.", calculator.Display);
        }

When we run the test again, we get the following:

 

CalculatorTests.TestPressEquals : Failed 
When adding 2 + 2, expected 4 but found 0. 
Expected: 4m 
But was:  0m

 

By supplying  a custom message, our test now provides some context around why the test failed.  As an added benefit, our custom message also adds a bit of description to the purpose of our test when reading the source code.

Unfortunately, these changes have introduced a bit of duplication into our test code.  We now have the expected result, the actual result, and the values being added together repeated throughout out test.  Also, it seems like a good idea to have the assert message automatically reflect any changes that might be made to the values used by the test.  Let’s take a stab at reducing the duplication:

        [Test]
        public void TestPressEquals2()
        {
            var value1 = 2m;
            var value2 = 2m;
            var calculator = new Calculator();
            calculator.Enter(value1);
            calculator.PressPlus();
            calculator.Enter(value2);
            calculator.PressEquals();
            decimal expected = 4m;
            decimal actual = calculator.Display;
            Assert.AreEqual(expected, actual, 
                "When adding {0} + {1}, expected {2} but found {3}.", value1, value2, expected, actual);
        }

Now we’ve eliminated the duplication, but the test doesn’t seem as easy to follow.  Also, our custom message no longer communicates the purpose of the test clearly.  If we revisit this test later, we might as well work through the logic of the test than work through the logic of what this assertion  is going to produce.  Let’s stick to our principles on keeping our code free of duplication for now, but we’ll revisit this topic later in our series.

Aside from there being nothing about our test that allows us to easily pick up on what’s occurring, our code is also looking a little crowded.  We could easily address this by spacing things out a bit and perhaps adding a few comments.

One popular style of organization is a pattern first described by Bill Wake as Arrange, Act, Assert.  With this organization, the test is separated into three sections: one which Arranges the objects to be used as part of the test, one which Acts upon the objects, and one which Asserts that the expected outcomes have occurred.  Since our test mostly follows this sequence already, let’s just space the sections out a bit and add some comments denoting the Arrange, Act and Assert sections:

        [Test]
        public void TestPressEquals()
        {
            // Arrange
            decimal value1 = 2m;
            decimal value2 = 2m;
            decimal expected = 4m;
            var calculator = new Calculator();

            // Act
            calculator.Enter(value1);
            calculator.PressPlus();
            calculator.Enter(value2);
            calculator.PressEquals();
            decimal actual = calculator.Display;

            // Assert
            Assert.AreEqual(expected, actual,
                            "When adding {0} + {1}, expected {2} but found {3}.", value1, value2, expected, actual);
        }

That organizes the flow of the test a bit better, but we’re still missing the clarity that initial custom message was providing.  We could communicate this through a comment as well, but another approach would be to just improve upon the actual test name.

One common naming convention is to create a name reflecting the name of the method being tested, the scenario being tested and the expected behavior.  Let’s change our test method name to reflect this convention:

        [Test]
        public void PressEquals_AddingTwoPlusTwo_ReturnsFour()
        {
            // Arrange
            decimal value1 = 2m;
            decimal value2 = 2m;
            decimal expected = 4m;
            var calculator = new Calculator();

            // Act
            calculator.Enter(value1);
            calculator.PressPlus();
            calculator.Enter(value2);
            calculator.PressEquals();
            decimal actual = calculator.Display;

            // Assert
            Assert.AreEqual(expected, actual,
                            "When adding {0} + {1}, expected {2} but found {3}.", value1, value2, expected, actual);
        }

 

After we’ve adjusted to reading this naming convention, we can now derive the same information that was communicated in that first message.  Someone get a banner printed up, we’re declaring victory!

Conclusion

In this article, we’ve taken a look at a basic approach to writing unit tests and introduced a few techniques for improving unit test communication.  While our resulting test correctly validates the behavior of our class, communicates what it does and provides a descriptive error message when it fails, there’s still room for improvement.  In our next article, we’ll discuss some alternative approaches that will facilitate these goals as well as help us write working, maintainable software that matters.

Tagged with: