Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I'm inching my way up the TDD ladder and I've got to a point where I'd like to get advice on the "next move". I realize there might not be a single answer here, so any logical suggestion would be great.

The thing I think I'm stuck on is knowing weather I should force duplication or proceed with primary functionality implemented by minimal (and temporary due to not being general) code.

I'm trying to go in a strict green/red/re-factor cycle. Also, this example is so simple it doesn't require any mocked objects, but I think it's still a valid example (please correct me if I'm wrong or I've gone into some kind of trivial case that's not worth working with).

UPDATE: I continued with this almost to the conclusion so I suppose I'll leave it up and see if anyone has any comments and if not I will delete..

Tests:

using NSpec;

namespace TicTacToe
{
    public class new_game : nspec
    {
        protected TicTacToeGame game;

        private void before_each()
        {
            game = new TicTacToeGame();
        }

        private void when_game_starts()
        {
            it["board should be clear"] = () => game.BoardState.should_be("---,---,---");
            it["it's x's turn"] = () => game.PlayerUp.should_be('x');
            it["no winner yet"] = () => game.Winner.should_be('-');
        }
    }

    public class specify_game : new_game
    {
        private void first_move()
        {
            context["is x in the center"] = () =>
                {
                    act = () => game.Move('x', 2, 2);

                    it["board should have x in the center"] = () => game.BoardState.should_be("---,-x-,---");
                    it["it's y's turn"] = () => game.PlayerUp.should_be('y');

                    context["then y in the center"] =
                        () =>
                            {
                                it["should throw OtherPlayerOccupiesSpaceException"] =
                                    expect<OtherPlayerOccupiesSpaceException>(() => game.Move('y', 2, 2));
                                it["no winner yet"] = () => game.Winner.should_be('-');
                            };

                    context["then y in the upper left"] = () =>
                        {
                            act = () => game.Move('y', 1, 1);
                            const string expectedBoardState = "y--,-x-,---";
                            it["correct board state is " + expectedBoardState] = () => game.BoardState.should_be(expectedBoardState);
                            it["it's x's turn"] = () => game.PlayerUp.should_be('x');
                            it["no winner yet"] = () => game.Winner.should_be('-');

                            context["then x in the middle top"] = () =>
                                {
                                    act = () => game.Move('x', 1, 2);
                                    const string expectedBoardState2 = "yx-,-x-,---";
                                    it["correct board state is " + expectedBoardState2] = () => game.BoardState.should_be(expectedBoardState2);
                                    it["no winner yet"] = () => game.Winner.should_be('-');

                                    context["then y in the right top"] = () =>
                                    {
                                        act = () => game.Move('y', 1, 3);
                                        const string expectedBoardState3 = "yxy,-x-,---";
                                        it["correct board state is " + expectedBoardState3] = () => game.BoardState.should_be(expectedBoardState3);
                                        it["no winner yet"] = () => game.Winner.should_be('-');

                                        context["then x in the middle bottom"] = () =>
                                            {
                                                act = () => game.Move('x', 3, 2);
                                                const string expectedBoardState4 = "yxy,-x-,-x-";
                                                it["correct board state is " + expectedBoardState4] = () => game.BoardState.should_be(expectedBoardState4);
                                                it["x wins"] = () => game.Winner.should_be('x');
                                            };
                                    };
                                };
                        };
                };
            context["is y int the center"] =
                () =>
                    {
                        it["should throw NotYourTurnException"] =
                            expect<NotYourTurnException>(() => game.Move('y', 2, 2));
                    };
        }
    }
}

Implementation:

using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace TicTacToe
{
    public class TicTacToeGame
    {
        private readonly char[,] grid = new char[3,3];

        private bool xsTurn = true;

        public TicTacToeGame()
        {
            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    grid[i, j] = '-';
                }
            }
        }

        public string BoardState
        {
            get
            {
                var sb = new StringBuilder();
                for (int i = 0; i < 3; i++)
                {
                    for (int j = 0; j < 3; j++)
                    {
                        sb.Append(grid[i, j]);
                    }
                    if (i < 2)
                        sb.Append(',');
                }
                return sb.ToString();
            }
        }

        public char PlayerUp
        {
            get { return xsTurn ? 'x' : 'y'; }
        }

        public void Move(char player, int down, int over)
        {
            over -= 1;
            down -= 1;
            if (player != PlayerUp)
                throw new NotYourTurnException();
            if (grid[down, over] != '-')
                throw new OtherPlayerOccupiesSpaceException();
            this.grid[down, over] = player;
            xsTurn = !xsTurn;
        }

        public char Winner
        {
            get
            {
                string boardState = BoardState;

                var xWins = new[]
                    {
                        "xxx,...,...",
                        "...,xxx,...",
                        "...,...,xxx",
                        "x..,x..,x..",
                        ".x.,.x.,.x.",
                        "x..,x..,x..",
                        "..x,..x,..x",
                        "x..,.x.,..x",
                        "..x,.x.,x..",
                    };

                    var yWins = new[]
                    {
                        "yyy,...,...",
                        "...,yyy,...",
                        "...,...,yyy",
                        "y..,y..,y..",
                        ".y.,.y.,.y.",
                        "y..,y..,y..",
                        "..y,..y,..y",
                        "y..,.y.,..y",
                        "..y,.y.,y..",
                    };

                if (xWins.Any(win => Regex.IsMatch(boardState, win)))
                    return 'x';

                if (yWins.Any(win => Regex.IsMatch(boardState, win)))
                    return 'y';

                return '-';
            }
        }
    }
}

Output:

PM> NSpecRunner.exe .\TicTacToe\bin\Debug\TicTacToe.dll

new game
  when game starts
    board should be clear
    it's x's turn
    no winner yet
  specify game
    first move
      is x in the center
        board should have x in the center
        it's y's turn
        then y in the center
          should throw OtherPlayerOccupiesSpaceException
          no winner yet
        then y in the upper left
          correct board state is y--,-x-,---
          it's x's turn
          no winner yet
          then x in the middle top
            correct board state is yx-,-x-,---
            no winner yet
            then y in the right top
              correct board state is yxy,-x-,---
              no winner yet
              then x in the middle bottom
                correct board state is yxy,-x-,-x-
                x wins
      is y int the center
        should throw NotYourTurnException

17 Examples, 0 Failed, 0 Pending
share|improve this question
5  
Are you making the assumption that you can write some tests, write some code to make the tests pass, and the underlying structure and logic of your tic-tac-toe game will naturally emerge from those tests? Because I think this is a widely-held misconception about TDD. TDD does not replace your design efforts; it augments them. –  Robert Harvey Mar 18 '13 at 22:11
    
Yes, I am proceeding on the assumption that the implementation and logic will "emerge" from the tests, probably in the process of eliminating duplication. You feel this is not a practical reality or do I misunderstand? –  Aaron Anodide Mar 18 '13 at 22:14
1  
Uncle Bob Martin (probably the foremost authority on TDD) says: "if what you're doing is ignoring architecture and throwing tests together and getting them to pass, you're destroying the thing that will allow the building to stay up because it's the concentration on the structure of the system and solid design decisions that helped the system maintain its structural integrity" –  Robert Harvey Mar 18 '13 at 22:20
1  
How, for example, could you expect an architectural design like MVC to emerge organically from writing unit tests, without knowing the architecture beforehand? –  Robert Harvey Mar 18 '13 at 22:54
2  
Counterpoint here: cumulative-hypotheses.org/2011/08/30/tdd-as-if-you-meant-it –  Robert Harvey Mar 18 '13 at 22:57
show 4 more comments

1 Answer

up vote 1 down vote accepted

To respond to the comments that you received, I think this was a useful experiment.

To answer your question, the approach that I take is to do the easiest thing to make the test pass. This can have one of two outcomes.

The first outcome is that another test is needed. This happens when the code you've written can't possibly be good enough. The example I always use to demonstrate this is a method that adds two numbers.

Test:

expect(plus(2, 2)).to eq(4)

Implementation:

def plus(x, y)
  return 4
end

That's the simplest way to make the test pass, but it's obviously not good enough. So we need to add another test so that in order for both tests to pass, the implementation must be correct.

Test:

expect(plus(2, 2)).to eq(4)
expect(plus(2, 3)).to eq(5)

Implementation:

def plus(x, y)
  return x + y
end

The second outcome is that I end up copying and pasting, because that's the easiest way to get a green bar. But that leaves me with duplication that I don't want. So I refactor in small chunks to remove any duplication and the keep the green bar as I go. I can't really think of a good small example to demonstrate that, which is something that I should work on.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.