Unit-testing – How would one test a function with a large number of scenarios

integration-teststddtestingunit testing

I've heard that it's ideal to have one assert (or expect or should, etc) per aspect of a component's behavior you're trying to test. Is this how it works? An addOne function would be tested like this, I suppose (example is written in javascript syntax):

it("returns one plus its input", function() {
  assert(addOne(5) == 6);
});

And I suppose that would be the complete extent of the tests for the addOne function, as each individual assert should be orthogonal.

How would one go about testing something more complex, but which still really has only one defining behavior?

Say for example we have an OO class for storing a chessboard, and each tile of the board is assigned an id number between 0 and 63. Then let's say there's a method getNeighbors which takes an integer from 0 to 63 and returns a sorted integer list/array/set containing the ids of all neighboring tiles. For example, if the numbers were laid out starting at 0 in the upper-left and incrementing across rows, then getNeighbors(0) would return [1, 8, 9] I suppose.

How many test cases would be written for this? I see two main options:

Test the entire behavior once

it("throws an error when called with a number below 0", ...);
it("throws an error when called with a number above 63", ...);
it("returns a list of the IDs of the neighboring tiles", function() {
  assert(getNeighbors(0) == [1, 8, 9]);
});

This seems like too weak of a test.

Test each case of the behavior once

it("throws an error when called with a number below 0", ...);
it("throws an error when called with a number above 63", ...);
it("returns [1, 8, 9] when called on 0", function() {
  assert(getNeighbors(0) == [1, 8, 9]);
});
it("returns [0, 2, 8, 9, 10] when called on 1", function() {
  assert(getNeighbors(1) == [0, 2, 8, 9, 10]);
});
... // and so on, for 62 more test cases

This seems over-the-top.

Best Answer

You can do two things:

First, use parameterized tests to minimize the duplication of the test code:

   cases([
     [0, [1, 8, 9]],
     [1, [0, 2, 8, 9, 10]],
     // more testcases here
   ])
   .it('sample', function(n, expected) {
     expect(getNeighbors(n)).toEqual(expected);
   });

Second, partition your testcases into equivalence classes where you expect the code to behave exactly the same, and have only one test for each class. In your cases, the classes would probably be:

  • top left corner square
  • top right corner square
  • bottom left corner square
  • bottom right corner square
  • non-corner top border squares
  • non-corner left border squares
  • non-corner right border squares
  • non-corner bottom border squares
  • all inner squares
Related Topic