Cohesion and Controller Ontology

On May 31, 2011, in Uncategorized, by derekgreer

In my article: Single Action Controllers with ASP.Net MVC, I presented a simple way of designing controllers within the ASP.Net MVC framework that represent discrete actions as opposed to classes which contain actions.  This prompted a few questions about the motivations behind this strategy which this article will discuss in more detail. 

To explain, we’ll review the topics of Cohesion, Role Stereotypes and the Single Responsibility Principle.

Cohesion

Cohesion is defined as the functional relatedness of the elements of a module.  If all the methods on a given object pertain to a related set of operations, the object can be said to have high-cohesion.  If an object has a bunch of miscellaneous methods which deal with unrelated concerns, it could be said to have low-cohesion.  Cohesion can be further broken down into categories based upon how the responsibilities are related.  The responsibilities of a given class may be similar in the sequence of operations they perform, in the dependencies they use, in the business domain concerns they facilitate, etc.  What makes an object cohesive depends upon which aspect of relatedness you are measuring. 

Role Stereotypes

Role Stereotypes concern the ontology of an object within object-oriented design.  Role stereotypes can be thought of as role patterns which help us to rationalize the assignment of roles and responsibilities to objects within an architecture.  One set of stereotypes set forth by Rebecca Wirfs-Brock is as follows:

  • Information holder – an object designed to know certain information and provide that information to other objects.
  • Structurer – an object that maintains relationships between objects and information about those relationships.
  • Service provider – an object that performs specific work and offers services to others on demand.
  • Controller – an object designed to make decisions and control a complex task.
  • Coordinator – an object that doesn’t make many decisions but, in a rote or mechanical way, delegates work to other objects.
  • Interfacer – an object that transforms information or requests between distinct parts of a system.

Another set of role stereotypes set forth by Steve Freeman and Nat Pryce as a heuristic for thinking about object roles within the context of collaboration is as follows:

  • Dependencies – Services that the object requires from its peers so it can perform its responsibilities.
  • Notifications – Peers that need to be kept up to date with the object’s activity.
  • Adjustments – Peers that adjust the object’s behavior to the wider needs of the system.

Object stereotypes serve as an aid when considering what roles and responsibilities a particular object should have within a given context, but they shouldn’t be looked to as a set of rules governing how to design.  Depending upon what aspect of a design is being considered, objects can serve in different contexts or may fit multiple stereotypes simultaneously.  Additionally, stereotypes should not be confused with the responsibilities and characteristics prescribed by formal patterns and pattern participants which may happen to share similar naming.  For example, Wirfs-Brock’s Controller  stereotype shouldn’t be confused with Controllers described by the Model-View-Controller, Application Controller or Supervising Controller patterns.

Single Responsibility Principle

The Single Responsibility Principle states:

A class should have only one reason to change.

The Single Responsibility Principle seeks to achieve cohesion by grouping concerns based upon the forces which cause them to change together.  For example, consider an object whose purpose is to provide caching services to an application.  It may have different behaviors for adding, retrieving, and removing items from a cache, but apart from each of these methods being functionally related (i.e. dealing with cache), they would also tend to change together based upon modifications to the underlying caching store implementation (e.g. changing the service to use a caching provider Strategy vs. a local dictionary field).  If the caching service were extended to also encrypt the cached items, this could be reasoned to violate the Single Responsibility Principle.  While the operations may be cohesive from the perspective of the responsibilities which are uniformly applied to the caching of items, persistence and encryption represent two different axes of change.

While the concept of Cohesion and the Single Responsibility Principle are related, something can be considered cohesive by one measure of relatedness, but have multiple reasons to change.  The Single Responsibility Principle measures one type of cohesion: how related the responsibilities of a module are with respect to their axes of change.

Confluence of Principles

So, how do all these abstract concepts relate to how one might decide to implement the Model-View-Controller pattern?  Going back to Trygve Reenskaug’s original design of the Model-View-Controller pattern, the Model’s role was to represent real world processes and entities; the View’s role was to provide the visualization of the model; the Controller’s role was to serve as an interface between the end user and the model.  The Controller’s responsibilities were achieved by intercepting hardware signals in the form of keyboard and mouse events and translating them to operations upon the Model.  As operations upon the Model changed its state, the View would be updated through the use of the Observer Pattern.  The Controller’s role in interacting with the View was primarily presentation-specific concerns (navigation, the expanding and collapsing of menus, highlighting, etc.).  As the MVC pattern was adapted for use with Web applications, the hardware signals were replaced by HTTP requests and the Controller took a more active role in communicating updates to the View due to the stateless nature of the Web.  Though the Controller had to step up its responsibility a bit in this new manifestation of the MVC pattern, its purpose was still that of Model interface.

While the Controller’s primary responsibility is to interpret signals and adapt those signals to meaningful operations upon the Model, the cohesiveness of the operations upon the Controller should be guided by the Single Responsibly Principle within the context of its role stereotype.  That is to say, the role of the Controller within the Model-View-Controller pattern is not to mirror the Model’s responsibility in terms of HTTP requests and responses, but to serve as an adaptor whose responsibilities are organized based upon the relatedness of the technical needs to serve in this adapting role.  For example, a single Domain object or service may have a number of operations which are grouped based upon their relatedness in representing a domain concept, but invoking the behavior of each may require that completely disparate dependencies be supplied to the Model.  One may require an auditing service, another a discount strategy, and still another a caching service.  These represent three separate axes of change which could affect the Controller for different reasons.  The types of requests and responses represent yet another axis of change, as some requests utilize different protocols (e.g. delegation to a View template engine, JSON, binary stream, etc.)

For simple applications, a single controller may adhere to the Single Responsibility Principle due to the singularity of protocol and dependencies required for the underlying Model it is tasked with interfacing (e.g. basic CRUD applications).  As applications grow in complexity, more forces of change begin to be imposed upon the design.  Therefore, applying the same sets of principles will lead you to different manifestations of Controllers depending on the style of application you are designing.

Conclusion

In summary, Cohesion is a measure of the relatedness of the elements of a module, but it is only until we provide context to an element’s purpose that we can begin to define cohesion in a meaningful way.  By identifying the role stereotype of a component, we can then apply the Single Responsibility Principle to the behaviors of the role based upon the cohesiveness of each element’s reason for change.

Tagged with:  

Effective Tests: Auto-mocking Containers

On May 31, 2011, in Uncategorized, by derekgreer

In the last installment, I set forth some recommendations for using Test Doubles effectively. In this article, I’ll discuss a class of tools which can aid in reducing some of the coupling and obscurity that comes with the use of Test Doubles: Auto-mocking Containers.

Auto-mocking Containers

Executable specifications can provide valuable documentation of a system’s behavior. When written well, they can not only clearly describe what the system does, but also serve as an example for how the system is intended to be used. Unfortunately, it is this aspect of our specifications which can often end up working against our goal of writing maintainable software.

Ideally, an executable specification would describe the expected behavior of a system in such a way as to also clearly demonstrate it’s intended use without obscuring its purpose with extraneous implementation details. One class of tools which aid in achieving this goal are Auto-mocking Containers.

An Auto-mocking Container is a specialized inversion of control container for constructing a System Under Test with Test Doubles automatically supplied for any dependencies. By using an auto-mocking container, details such as the declaration of test double fields and test double instantiation can be removed from the specification, rendering a cleaner implementation void of such extraneous details.

Consider the following class which displays part details to a user and is responsible for retrieving the details requested form a cached copy if present:

 public class DisplayPartDetailsAction
    {
        readonly ICachingService _cachingService;
        readonly IPartDisplayAdaptor _partDisplayAdaptor;
        readonly IPartRepository _partRepository;

        public DisplayPartDetailsAction(
            ICachingService cachingService,
            IPartRepository partRepository,
            IPartDisplayAdaptor partDisplayAdaptor)
        {
            _cachingService = cachingService;
            _partRepository = partRepository;
            _partDisplayAdaptor = partDisplayAdaptor;
        }

        public void Display(string partId)
        {
            PartDetail details = _cachingService.RetrievePartDetails(partId) ??
                                 _partRepository.GetPartDetailByPartId(partId);

            _partDisplayAdaptor.Display(details);
        }
    }

The specification for this behavior would need to verify that the System Under Test attempts to retrieve the PartDetail from the ICachingService, but would also need to supply implementations for the IPartRepository and IPartDisplayAdaptor as shown in the following listing:

    public class when_displaying_part_details
    {
        const string PartId = "12345";
        static Mock<ICachingService> _cachingServiceMock;
        static DisplayPartDetailsAction _subject;

        Establish context = () =>
            {
                _cachingServiceMock = new Mock<ICachingService>();
                var partRepositoryDummy = new Mock<IPartRepository>();
                var partDisplayAdaptorDummy = new Mock<IPartDisplayAdaptor>();
                _subject = new DisplayPartDetailsAction(_cachingServiceMock.Object, partRepositoryDummy.Object,
                                                        partDisplayAdaptorDummy.Object);
            };

        Because of = () => _subject.Display(PartId);

        It should_retrieve_the_part_information_from_the_cache =
            () => _cachingServiceMock.Verify(x => x.RetrievePartDetails(PartId), Times.Exactly(1));
    }

By using an auto-mocking container, the specification can be written without the need of an explicit Mock field, or instantiating Dummy instances for the IPartRepository and IPartDisplayAdaptor dependencies. The following demonstrates such an example using AutoMock, an auto-mocking container which leverages the Moq framework:

    public class when_displaying_part_details
    {
        const string PartId = "12345";
        static AutoMockContainer _container;
        static DisplayPartDetailsAction _subject;

        Establish context = () =>
            {
                _container = new AutoMockContainer(new MockFactory(MockBehavior.Loose));
                _subject = _container.Create<DisplayPartDetailsAction>();
            };

        Because of = () => _subject.Display(PartId);

        It should_retrieve_the_part_information_from_the_cache =
            () => _container.GetMock<ICachingService>().Verify(x => x.RetrievePartDetails(PartId), Times.Exactly(1));
    }

While this implementation eliminates references to the extraneous dependencies, it does impose a bit of extraneous implementation details of its own. To further relieve this specification of implementation details associated with the auto-mocking container, a reusable base context can be extracted:

    public abstract class WithSubject<T> where T : class
    {
        protected static AutoMockContainer Container;
        protected static T Subject;

         Establish context = () =>
            {
                Container = new AutoMockContainer(new MockFactory(MockBehavior.Loose));
                Subject = Container.Create<T>();
            };

        protected static Mock<TDouble> For<TDouble>() where TDouble : class
        {
            return Container.GetMock<TDouble>();
        }
    }

By extending the auto-mocking base context, the specification can be written more concisely:

    public class when_displaying_part_details : WithSubject<DisplayPartDetailsAction>
    {
        const string PartId = "12345";

        Because of = () => Subject.Display(PartId);

        It should_retrieve_the_part_information_from_the_cache =
            () => For<ICachingService>().Verify(x => x.RetrievePartDetails(PartId), Times.Exactly(1));
    }

Another advantage gained by the use of auto-mocking containers is decoupling. By inverting the concern of how the System Under Test is constructed, dependencies can be added, modified, or deleted without affecting specifications for which the dependency has no bearing.

Trade-offs

While auto-mocking containers can make specifications cleaner, easier to write, and more adaptable to change, their use can come at a slight cost. By using mocking frameworks and hand-rolled doubles directly, there is always at least one point of reference where the requirements of instantiating the System Under Test provides feedback about its design as a whole.

Use of auto-mocking containers allows us to produce contextual slices of how the system works, limiting the information about the system’s dependencies to that knowledge required by the context in question. From a documentation perspective, this can aid in understanding how the system is used to facilitate a particular feature. From a design perspective, however, their use can eliminate one source of feedback about the evolving design of the system. Without such inversion of control, hints of violating the Single Responsibility Principle can be seen within the specifications, evidenced by overly complex constructor initialization. By removing the explicit declaration of the system’s dependencies from the specifications, we also remove this point of feedback.

That said, the benefits of leveraging auto-mocking containers tend to outweigh the cost of removing this point of feedback. Cases of mutually-exclusive dependencies are usually in the minority and each addition and/or modification to a constructor provides an equal level of feedback about a class’s potential complexity.

Conclusion

In this article, we looked at the use of auto-mocking containers as a tool for reducing obscurity and coupling within our specifications. Next time, we’ll look at a technique for reducing the obscurity that comes from overly complex assertions.

Effective Tests: Double Strategies

On May 26, 2011, in Uncategorized, by derekgreer

In our last installment, the topic of Test Doubles was introduced as a mechanism for verifying and/or controlling the interactions of a component with its collaborators. In this article, we’ll consider a few recommendations for using Test Doubles effectively.

 

Recommendation 1: Clarify Intent

Apart from guiding the software implementation process and guarding the application’s current behavior against regression, executable specifications (i.e. Automated Tests) serve as the system’s documentation. While well-named specifications can serve to describe what the system should do, we should take equal care in clarifying the intent of how the system’s behavior is verified.

When using test doubles, one simple practice that helps to clarify the verification strategies employed by the specification is to use intention-revealing names for test double instances. Consider the following example which uses the Rhino Mocks framework for creating a Test Stub:

	public class when_a_user_views_the_product_detail
	{
		public const string ProductId = "1";
		static ProductDetail _results;
		static DisplayOrderDetailCommand _subject;

		Establish context = () =>
			{
				var productDetailRepositoryStub = MockRepository.GenerateStub<IProductDetailRepository>();
				productDetailRepositoryStub.Stub(x => x.GetProduct(Arg<string>.Is.Anything))
					.Return(new ProductDetail {NumberInStock = 42});

				_subject = new DisplayOrderDetailCommand(productDetailRepositoryStub);
			};

		Because of = () => _results = _subject.QueryProductDetails(ProductId);

		It should_display_the_number_of_items_currently_in_stock = () => _results.NumberInStock.ShouldEqual(42);
	}

 

In this example, a Test Stub is created for an IProductDetailRepository type which serves as a dependency for the System Under Test (i.e. the DisplayOrderDetailCommand type). By choosing to explicitly name the Test Double instance with a suffix of “Stub”, this specification communicates that the double serves only to provide indirect input to the System Under Test.

 

Note to Rhino Mock and Machine.Specification Users

For Rhino Mock users, there are some additional hints in this example which help to indicate that the test double used by this specification is intended to serve as a Test Stub. This includes use of Rhino Mock’s GenerateStub() method, the lack of “Record/Replay” artifacts from either the old or new mocking APIs and the absence of assertions on the generated test double. Additionally, those familiar with the Machines.Specifications framework (a.k.a. MSpec) would have an expectation of explicit and discrete observations if this were being used as a Mock or Test Spy. Nevertheless, we should strive to make the chosen verification strategy as discoverable as possible and not rely upon framework familiarity alone.

 

While this test also indicates that the test double is being used as a Stub by its use of the Rhino Mock framework’s GenerateStub() method, Rhino Mocks doesn’t provide intention-revealing method names for each type of test double and some mocking frameworks don’t distinguish between the creation of mocks and stubs at all. Using intention-revealing names is a consistent practice that can be adopted regardless of the framework being used.

 

Recommendation 2: Only Substitute Your Types

Applications often make use of third-party libraries and frameworks. When designing an application which leverages such libraries, there’s often a temptation to substitute types within the framework. Rather than providing a test double for these dependencies, create an abstraction representing the required behavior and provide a test double for the abstraction instead.

There are several issues with providing test doubles for third party components:

First, it precludes any ability to adapt to feedback received from the specification. Since we don’t control the components contained within a third-party libraries, coupling our design to these components limits our ability to guide our designs based upon our interaction with the system through our specifications.

Second, we don’t control when or how the API of third-party libraries may change in future releases. We can exercise some control over when we choose to upgrade to a newer release of a library, but aside from the benefits of keeping external dependencies up to date, there are often external motivating factors outside of our control. By remaining loosely-coupled from such dependencies, we minimize the amount of work it takes to migrate to new versions.

Third, we don’t always have a full understanding of the behavior of third-party libraries. Using test doubles for dependencies presumes that the doubles are going to mimic the behavior of the type they are substituting correctly (at least within the context of the specification). Substituting behavior which you don’t fully understand or control may lead to unreliable specifications. Once a specification passes, there should be no reason for it to ever fail unless you change the behavior of your own code. This can’t be guaranteed when substituting third-party libraries.

While we shouldn’t substitute types from third-party libraries, we should verify that our systems work properly when using third-party libraries. This is achieved through integration and/or acceptance tests. With integration tests, we verify that our systems display the expected behavior when integrated with external systems. If our systems have been properly decoupled from the use of third-party libraries, only the Adaptors need to be tested. A system which has taken measures to remain decoupled from third-party libraries should have far fewer integration tests than those that test the native behavior of the application. With acceptance tests, we verify the behavior of the entire application from end to end which would exercise the system along with its external dependencies.

 

Recommendation 3: Don’t Substitute Concrete Types

The following principle is set forth in the book Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, et al:

Program to an interface, not an implementation.

All objects possess a public interface and in this sense all object-oriented systems are collaborations of objects interacting through interfaces. What is meant by this principle, however, is that objects should only depend upon the interface of another object, not the implementation of that object. By taking dependencies upon concrete types, objects are implicitly bound by the implementation details of that object. Subtypes can be substituted, but subtypes are inextricably coupled to their base types.

Set forth in the book Agile Software Development, Principles, Patterns, and Practices by Robert C. Martin, a related principle referred to as the Interface Segregation Principe states:

Clients should not be forced to depend on methods that they do not use.

The Interface Segregation Principle is set forth to address several issues that arise from non-cohesive or “fat” interfaces, but the issue most pertinent to our discussion is the problem of associative coupling. When a component takes a dependency upon a concrete type, it forms an associative coupling with all other clients of that dependency. As new requirements drive changes to the internals of the dependency for one client, all other clients coupled directly to the same dependency may be affected regardless of whether they depend upon the same sets of behavior or not. This problem can be mitigated by defining dependencies upon Role-based Interfaces. In this way, objects declare their dependencies in terms of behavior, not specific implementations of behavior.

So, one might ask, “What does this have to do with Test Doubles?” There is nothing particularly problematic about replacing concrete types from an implementation perspective. There’s certainly the issue in some languages of needing to take measures to ensure virtual dispatching can take place thereby allowing the behavior of a concrete type to be overridden, but where this actually becomes relevant to our discussion is in what our specifications are trying to tell us about our design. When you find yourself creating test doubles for concrete types, it’s as if your specifications are crying out: “Hey dummy, you have some coupling here!” By listening to the feedback provided by our specification, we can begin to spot code smells which may point to problems in our implementation.

 

Recommendation 4: Focus on Behavior

When writing specifications, it can be easy to fall into the trap of over-specifying the components of the system. This occurs when we write specifications that not only verify the expected behavior of a system, but which also verify that the behavior is achieved using a specific implementation.

Writing component-level specifications will always require some level of coupling to the component’s implementation details. Nevertheless, we should strive to minimize the coupling to those interactions which are required to verify the system requirements. If the System Under Test takes 10 steps to achieve the desired outcome, but the outcome of step 10 by itself is sufficient to verify that the desired behavior occurred, our specifications shouldn’t care about steps 1 through 9. If someone figures out a way to achieve the same outcome with only 3 steps, the specifications of the system shouldn’t need to change.

This leads us to a recommendation from the book XUnit Test Patterns: Refactoring Test Code by Gerard Meszaros:

Use the Front Door First

By “front door”, the author means that we should strive to verify behavior using the public interface of our components when possible, and use interaction-based verification when necessary. For example, when the behavior of a component can be verified by checking return values from operations performed by the object or by checking the interactions which occurred with its dependencies, we should prefer checking the return values over checking its interactions. At times, verifying the behavior of an object requires that we examine how the object interacted with its collaborators. When this is necessary, we should strive to remain as loosely coupled as possible by only specifying the minimal interactions required to verify the expected behavior.

 

Conclusion

In this article, we discussed a few strategies for using Test Doubles effectively. Next time we’ll take a look at a technique for creating Test Doubles which aids in both reducing coupling and obscurity … but at a cost.

Tagged with:  

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: