Unit-testing – TDD and complete test coverage where exponential test cases are needed

algorithmstddunit testing

I am working on a list comparator to assist sorting an unordered list of search results per very specific requirements from our client. The requirements call for a ranked relevance algorithm with the following rules in order of importance:

  1. Exact match on name
  2. All words of search query in name or a synonym of the result
  3. Some words of search query in name or synonym of the result (% descending)
  4. All words of the search query in the description
  5. Some words of the search query in the description (% descending)
  6. Last modified date descending

The natural design choice for this comparator seemed to be a scored ranking based on powers of 2. The sum of lesser important rules can never be more than a positive match on a higher importance rule. This is achieved by the following score:

  1. 32
  2. 16
  3. 8 (Secondary tie-breaker score based on % descending)
  4. 4
  5. 2 (Secondary tie-breaker score based on % descending)
  6. 1

In the TDD spirit I decided to start with my unit tests first. To have a test case for each unique scenario would be at a minimum 63 unique test cases not considering additional test cases for secondary tie breaker logic on rules 3 and 5. This seems overbearing.

The actual tests will actually be less though. Based on the actual rules themselves certain rules ensure that lower rules will always be true (Eg. When 'All Search Query words appear in description' then rule 'Some Search Query words appear in description' will always be true). Still is the level of effort in writing out each of these test cases worth it? Is this the level of testing that is typically called for when talking about 100% test coverage in TDD? If not then what would be an acceptable alternative testing strategy?

Best Answer

Your question implies that TDD has something to do with "writing all test cases first". IMHO that's not "in the spirit of TDD", actually it's against it. Remember that TDD stands for "test driven development", so you need only those test cases which really "drive" your implementation, not more. And as long as your implementation is not designed in a way where the number of code blocks grows exponentially with each new requirement, you won't need an exponential number of test cases either. In your example, the TDD cycle probably will look like this:

  • start with the first requirement from your list : words with "Exact match on name" must get a higher score than everything else
  • now you write a first test case for this (for example: a word matching a given query) and implement the minimal amount of working code which makes that test pass
  • add a second test case for the first requirement (for example: a word not matching the query), and before adding a new test case, change your existing code until the 2nd test passes
  • depending on details of your implementation, feel free to add more test cases, for example, an empty query, an empty word etc (remember: TDD is a white box approach, you can make use of the fact that you know your implementation when you design your test cases).

Then, start with the 2nd requirement:

  • "All words of search query in name or a synonym of the result" must get a lower score than "Exact match on name", but a higher score than everything else.
  • now build test cases for this new requirement, just like above, one after another, and implement the next part of your code after each new test. Don't forget to refactor in between, your code as well as your test cases.

Here comes the catch: when you add test cases for requirement/category number "n", you will only have to add tests for making sure that the score of the category "n-1" is higher than the score for category "n". You will not have to add any test cases for every other combination of the categories 1,...,n-1, since the tests you have written before will make sure that the scores of that categories will still be in the correct order.

So this will give you a number of test cases which grows approximately linear with the number of requirements, not exponentially.

Related Topic