3

One of the most important rule in TDD is to write the simplest test to implement the simplest code in order to get closer to the end of a project.

I'd like to implement the game of Briscola. It's a simple card game, with 40 cards, 2-5 players, and simple rules.

To start, I thought the best thing to implement was the core of the game, playing a hand.

To play a hand I need at least 2 players in a game, both will have 3 cards in hand, both have to be able to play one card each, and the game has to be able to decide the winner, and count points.

That looks like a lot of things to implement before passing that first test.

Should I still write that test down, and use it to implement everything needed ? Or should I write tests for everything before trying to make that test pass ?

One thing that makes me reluctant to write a test as simple as "an_ace_is_worth_11_points" is that I'm afraid the design I'd start implementing would absolutely not fit the design to make a more concrete and complete test pass.

13
  • 2
    Where to Start? Sep 12, 2017 at 21:51
  • 3
    "True" TDD suggests that you start by writing the smallest possible test for the smallest possible thing that you can get to pass. Neither of your proposed options fits that bill. I suggest instead that you come up with a sensible basic design for your game, and then begin writing tests and implementations for those tests. Sep 12, 2017 at 22:03
  • 2
    Notice that even the bowling game kata has a bunch of unspoken design decisions made before the first test is written.
    – jscs
    Sep 12, 2017 at 22:04
  • 1
    Think about the objects that you will be creating and the order you will need them in. For example maybe card class and tests(suits, numbers) deck class and tests ( number of cards, shuffle) player class and tests, hand class and tests, game class and tests(start game, deal, track points, determine winner)
    – Mike
    Sep 12, 2017 at 22:25
  • 4
    TDD may sometimes help in designing interfaces of modules (or classes), but it won't help you in designing the overall structure of an application. You need to make some decisions about your modules and their responsibilities and dependencies before you can write any meaningful unit tests.
    – COME FROM
    Sep 13, 2017 at 7:01

3 Answers 3

8

Because you're running too fast. When reading specifications you should stop as soon as you can write some code (after you understood the domain). I usually read the domain model specifications few times until I think I understood them and then I read them again using an highlighter to mark important bits to start coding.

To start, I thought the best thing to implement was the core of the game, playing a hand. To play a hand I need at least 2 players in a game, both will have 3 cards in hand, both have to be able to play one card each, and the game has to be able to decide the winner, and count points.

First of all write down the complete specifications for this game. Everything, even if you think you have it clear in your mind. It doesn't even need to be strictly formal.

Let's go through this word by word, the first keyword to highlight is game. You will, probably, then need a Game class you want to create. Let's write a test method (syntax is for C# but the important part is the concept):

[TestMethod]
void CanCreateNewGame()
{
    var game = new Game();
}

So far so good, we just want to be sure we can create a Game object and it doesn't throw any exception. If we will introduce a factory (for example to join an on-line pending game) then this is the test we need to rewrite. Let's write code to make it pass:

public sealed class Game { }

Now continue scanning your specifications, you have players. You will then need to create them, same as above. You see then ...in a game.... It means they must be stored in the Game class and they can be from 2 to 5, you need also a collection.

[TestMethod]
public void CanCreatePlayer()
{
    var game = new Game();
    game.Players.Add(new Player());
}

Let's declare the class and its collection PlayerCollection then add it to the game class:

public sealed class Player { }
public sealed class PlayerCollection : Collection<Player> { }

public sealed class Game
{
    public PlayerCollection Players { get; } = new PlayerCollection();
}

Our tests are green, we can now move to the next important part (the number of players). When we need at least two players and no more than 5? When we start a game:

[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void CannotStartGameWithLessThan2Players()
{
    var game = new Game();
    game.Start();
}

Make it compile, add Start() to Game:

public void Start()
{
    if (Players.Count < MinimumNumberOfPlayers || Players.Count > MaximumNumberOfPlayers)
        throw new InvalidOperationException("...");
}

Just continue like this until your application is completed. Now you can move that check into a separate function EnsureThereIsRightNumberOfPlayers() and you already have all the tests to do it safely. I'd suggest to also add assertions to help you with tests (countless times an assertion failed when running my tests and I found I had to add more tests...) but it's probably outside the scope of your question.

Writing tests before code will also help to have a better code coverage and to keep your code testable even when you just started using unit testing.


What I shown above is a literal TDD approach, I am often little bit more pragmatic and I write bigger code blocks and/or multiple tests in one shot. For example I will NEVER write int Sum(int a, int b) => 2; because I need to pass the test Assert.AreEqual(2, Sum(1, 1,)) (to fix it in the next test when I need to verify Sum(-1, -1)). I'd write, whenever possible, enough code to pass the test but using what may be a true implementation (which I may refine and refactor later because I already have tests in-place).

With some experience with TDD you will find what's the best compromise for you.


TDD as a tool to study the domain

Note: TDD won't replace design phase, it's not its role. It will, however, help you to refine your design from an abstract domain point of view to a practical implementation. Also note that the type of tests you write in this case is pretty different from the type of tests you write on a normal TDD approach.

Sometimes the domain isn't perfectly clear and you need to write some code to outline overall architecture. This case, IMO, is when you have greater benefits with TDD. Didn't ever happen to write a great model which is a pain to consume? If you start consuming your model first then this won't ever happen.

In this case I find convenient to write a slightly complex test method to understand who the actors are, for example a very naive first approach to the problem may be:

[TestMethod]
public void CanSetupGame()
{
    // I have a game
    var game = new Game();

    // With at least two named players
    game.Players.Add(new Player("Adam"));
    game.Players.Add(new Player("Jon"));

    // I can start the game
    game.Start();

    // And I keep playing until there are cards in the table...
    while (game.Cards.Count > 0)
    {
        // Print current cards to...console?
        var availableCards = game.CurrentPlayer.Cards;

        // Read user input from?
        var selectedCard = availableCards.First();

        game.CurrentPlayer.Play(selectedCard);
    }
}

As you can see you immediately see what your objects have to do, you need to keep track of the current player, its cards and stop playing when...cards are finished. You also immediately see that more users may share the same screen and so on...

Of course you will need to write a lot of code before this test can pass (you may temporarily [Ignore] it) and it MUST change/evolve over time but it will be your GPS to write all the other smaller tests you need. You may even find that some of these "high level abstraction" tests should be moved to integration tests, for example. Even better. Note that you should not throw away these tests, they're great self-documenting examples and the first thing that someone else should see when studying your code.

Writing this kind of rough code will greatly help you to better understand the Big Picture before writing one single line in your model (and some IDEs let you generate stubs from this code...). After you wrote above code you may then think "Shouldn't everything be inside Start()?". You will then drop that while and you will next think "How should I get inputs?" and add, for example, an Input property of type Stream to the Game class. You will do the same for Output and you will then ask yourself "What if I want to make it graphical?" and replace it with with a UserInteraction class. Keep changing, refining and increasing your understanding. You won't write a model (and its tests) you need to throw away because it's a pain to use (which may also cause the calling code to be a mess because you don't want to throw away such beautiful already written model...)

Note that this consumer to implementation approach will also help you to refine some of your architectural decisions, for example:

  • Is the language a good fit to express the model? Should I move to another language? To a DSL?
  • Do I need any framework? Are frameworks I supposed to use good for this job? Did they drive in the choice of the language?
  • Are performance good enough? Should I review my algorithms?
  • This package/module/class depend on the other one to perform its task. Is it a good enough decision or should I move responsibilities?
  • Did I practically put too many responsibilities on this class and I'd better move them out? Is there a chance to reuse this code somewhere else?

You probably have already noted that DDD, TDD and - when dealing with UI - lean UX design aren't separate steps or different approaches (!!!) but they go pretty well all together.

9
  • What do you think about other comments stating I should first create a design to then know in which direction the code will be going ? Sep 13, 2017 at 9:31
  • 1
    @SteveChamaillard: there are lots of higher level design decisions you typically make beforehand, like which programming language & tools & framework to pick, are you going to design for web or desktop or a mobile platform, will you have a separate model layer etc. TDD helps you for the lower level design decisions, especially testability.
    – Doc Brown
    Sep 13, 2017 at 9:41
  • @DocBrown agree, when you have some code to write then you already took some very important architectural decisions but, I think, TDD will then help you to refine those decisions (can I write a good representation of the domain using this language? should I use this or that framework? With this rough performance test - performed inside my unit testing - can I assert that response time of a typical web application is good enough?) Sep 13, 2017 at 9:49
  • @AdrianoRepetti: the OP was asking about Robert Harvey's suggestion to come up with a basic design first. Robert's comment should be seen in context with answers like this one (which I fully agree to).
    – Doc Brown
    Sep 13, 2017 at 11:48
  • @Doc understand! Yes, I agree about that. You must have an idea of what you're doing/where you're going before even starting to write the very first line of code. What I mean is that TDD will help you to refine your design Sep 13, 2017 at 11:56
4

Each of the things you mentioned is a testable unit to me. Doing a whole hand would be perhaps dozens of tests.

I would probably start up with the basic setup pieces. Test to make sure that a Deck object can be created (or fetched from a Factory or whatever). Test to make sure it can deal cards. How do you represent Players? are they objects or something else? Is there a "Table" object or "Game" object that encapsulates state in some fashion? These are all questions you'll have to design your way out of. But starting with whatever you know (e.g. that there will be a deck of cards, that deck will have 52 cards of 4 suits, and so on) is where you begin.

3

That looks like a lot of things to implement before passing that first test.

Stop, you have your activities in the wrong order. Before you go off and implement a bunch of things to calculate the correct answer, you first need a passing test.

That usually means a check, and a trivial implementation that returns a hardcoded answer that satisfies the check.

Roman numeral kata:

Assert.that(sut.toRoman(50)).equals("L");

String toRoman(int value) {
    return 50;
}

Bowling game kata:

Assert.that(sut.score()).equals(0);

int score() {
    return 0;
}

Mars Rover kata

String input = "5 5\n1 2 N\nLMLMLMLMM\n3 3 E\nMMRMMRMRRM\n";
String expectedOutput = "1 3 N\n5 1 E\n";

Assert.that(sut.run("5 5\n1 2 N\nLMLMLMLMM\n3 3 E\nMMRMMRMRRM\n")).equals("1 3 N\n5 1 E\n");

String run(String input) {
    return "1 3 N\n5 1 E\n";
}

Where to start?

So for a given unit, you should always be starting from a passing test (probably a trivially passing one, like above), before you begin fleshing out the "real" implementation.

Two different approaches - the outside in approach starts with something big, and encourages you to discover the small parts as you go. By it nature, it's consumer focused -- "how does the caller invoke this" comes before "how is this implemented". The inside out approach instead begins with a mental decomposition into components of a solution (players, cars, counting points), picks one, and takes an outside in approach to each component in turn.

I'm not a fan of inside out, myself; the inside out demonstrations I've seen tend to need to deal with a lot of baggage before finally resulting in something useful.

Outside in, I have found, tends to produce better module decomposition; you are constantly discovering boundaries to hide the decision to hard code an answer. The decomposition that we do with the inside out approach is effectively guessing up front where the significant boundaries lie.

Also: naming things sucks. I'd rather get stumped up front guessing a good name for a single entry point than get stumped trying to guess good names for units within the model that I haven't started using yet.

So my recommendation is: Start with a question

  • that you know the answer to
  • where you can make a reasonable guess at a simple api for the question
  • that provides value in isolation

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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