What’s the next logical TDD move in this learning example?

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 .TicTacToebinDebugTicTacToe.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

9

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.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật