4

I am trying to employ TDD in writing a backgammon game in C++ using VS 2010.

I have set up CxxTest to write the test cases.

The first class to test is

class Position
{
public:
...
...
bool IsSingleMoveValid(.....)
...
...
}

I 'd like to write a test for the function IsSingleMoveValid(), and I guess the test should prove that the function works correctly. Unfortunately there are so many cases to test and even if I test several cases, some might escape.

What do you suggest ? How does TDD handle these problems ?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
Wartin
  • 1,965
  • 5
  • 25
  • 40

5 Answers5

8

A few guidelines:

  1. Test regular cases. In your problem: test legal moves that you KNOW are valid. You can either take the easy way and have only a handful of test cases, or you can write a loop generating all possible legal moves that can occur in your application and test them all.
  2. Test boundary cases. This is not really applicable to your problem, but for testing simple numerical functions of the form f(x) where you know that x has to lie in a range [x_min, x_max), you would typically also test f(x_min-1), f(x_min), f(x_max-1), f(x_max). (It could be relevant for board games if you have an internal board representation with an overflow edge around it)
  3. Test known bugs. If you ever come across a legal move that is not recognized by your IsSingleMoveValid(), you add this as a testcase and then fix your code. It's useful to keep such test cases to guard against future regressions (some future code additions/modifications could re-introduce this bug, and the test will catch it).

The test coverage (percentage of code lines covered by tests) is a target that can be calculated by tools such as gcov You should do your own cost-benefit analysis how thorough you want to test your code. But for something as essential as legal move detection in a game program, I'd suggest you be vigilant here.

Others have already commented on breaking up the tests in smaller subtests. The nomenclature for that is that such isolated functions are tested with unit testing, whereas the collabaration between such functions in higher-level code is tested with integration testing.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
2

Generally, by breaking complex classes into multiple simpler classes, each doing a well-defined task that is easy to test.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
2

If you are writing the tests then the easiest thing to do is to break your IsSingleMoveValid function down into smaller functions and test them individually.

Component 10
  • 10,247
  • 7
  • 47
  • 64
1

As you can see on Wikipedia, TDD - Test Driven Development means writing the test first.

In your case, it would mean to establish all valid moves and write a test function for them. Then, you write code for each of those breaking test, until all the test pass.

... Unfortunately there are so many cases to test and even if I test several cases, some might escape.

As other said, when a function is too complex it is time for Refactoring!

I strongly suggest you the book Refactoring - Improve the Design of Existing Code from Martin Fowler with contribution of Kent Beck and others. It is both a learning and reference book which makes it very valuable in my opinion.

This is probably the best book on refactoring and it will teach you how to split your function without breaking everything. Also, refactoring is a really important asset for TDD. :)

ForceMagic
  • 6,230
  • 12
  • 66
  • 88
0

There is no such thing as "too many cases to test". If the code to handling a set of cases can be written, they need to be thought. If they can be written and are thought, they code that test them can we written as well. In average, for each 10 lines of (testable) code that you write, you can add a constant factor of testing code associated to it.

Of course, the whole trick is knowing how to write code that matches the testable description.

Hence, you need to start by writing a test for all the cases.

if there is a big, let's say for the sake of discussion that you have a countable set of possible cases to test (i.e: that add(n,m) == n+m for all n and m integer), but your actual code is really simple; return n+m. This of course is trivially true but don't miss the point: you don't need to test all the possible moves in the board, TDD aims so that your tests cover all the code (i.e: the tests exercise all the if branches in your code), not necessarily all possible values or combinations of states (which are exponentially big)

a project with 80-90% of line coverage, means that your tests exercise 9 lines out of each 10 lines of your code. In general if there is a bug in your code, it will in the majority of circumstances be evidenced when walking a particular code path.

lurscher
  • 25,930
  • 29
  • 122
  • 185
  • "There is no such thing as "too many cases to test". -1. There is. Try implementing std::map yourself and then try writing a test that will test all possible scenarios in which containers can be misused. – SigTerm May 17 '12 at 15:58
  • 1
    The number of positions in backgammon is about 1.8 x 10^19. For each such position, there are up to 56,025 possible moves that must be checked for validity. – Wartin May 17 '12 at 16:14
  • I'm divided on this answer, since I don't fully agree on the first paragraph. In real life, except if you are working for the NASA, there is a "too many cases to test" principle IMO. The point is that you must focus on critical part of your system, because each test adds value to your system however, they also comes with a cost. For the second part, you are right, there are many methods and principle in testing that doesn't require to test "all possible moves". I think if you could find time to re-work the first part this would be a very good answer. :) – ForceMagic Oct 15 '12 at 01:48