Testing is full of numbers:
- How long will the test pass take?
- What percentage of the features have you tested?
- What is the automation test pass rate?
- How confident are we that the failing tests are real product failures and not failures of the test system?
- What is my raise going to be?
Code Coverage is just a number. It tells us how much of the code has been exercised, and maybe verified, by our testing effort. This is also sometimes called White Box testing since we look at the code in order to develop our test cases. Management sometimes puts a high value on the code coverage number. Whether they should or not is a discussion best left to each company. There are multiple ways we can get code coverage numbers. Here are three examples.
Block testing
Definition: Execute a contiguous block of code at least once
Block testing is the simplest first order method to obtain a code coverage number. The strength is it’s quick. The weakness is it’s not necessarily accurate. Take a look at this code example:
bool IsInvalidTriangle(ushort a, ushort b, short c) { bool isInvalid; if ((a + b <= c) || (b + c <= a) || (a + c <= b)) { isInvalid = true; } return isInvalid; }
If we tested it with the values of a=1, b=2, and c=3; we would get a code coverage of about 80%. Great, management says, SHIP IT! Wait, you say, there is a weakness of block level testing. Can you spot it? The one test case only hits the first condition of the IF statement. Block level testing will report the line as 100% covered, even though we did not verify the second and third conditions. If one of the expressions was “<” instead of “<=” we would never catch the bug.
Condition testing
Definition: Make every sub-expression of a predicate statement evaluate to true and false at least once
This is one step better than block level testing since we validate each condition in a multiple condition statement. The trick is to break any statement with multiple conditions to one condition per line, and then put a letter in front of each condition. Here is an example:
void check_grammar_if_needed(const Buffer& buffer) { A: if (enabled && B: (buffer.cursor.line < 10) && C: !buffer.is_read_only) { grammarcheck(buffer); } }
Our tests would be:
Test | enabled | value of ‘line’ | is_read_only | Comment |
1 | False | N/A | N/A | |
2 | True | 11 | N/A | A is now covered |
3 | True | 9 | True | B is now covered |
4 | True | 9 | False | C is now covered |
Breaking the conditions into one per line doesn’t really help much here. This trick will help if you have nested loops. You can set up a table to help make sure each inner expression condition is tested with each outer expression condition.
Basis Path testing
Definition: Test C different entry-exit paths where C (Cyclomatic complexity) = number of conditional expressions + 1
Does the term “Cyclomatic complexity” bring back nightmares of college? Most methods have one entry and one or two exits. Basis Path testing is best applied when there are multiple exit points since you look at each exit path in order to determine your code coverage. The steps you follow to find the basis paths (shortest path method):
- Find shortest path from entry to exit
- Return to algorithm entry point
- Change next conditional expression or sub-expression to alternate outcome
- Follow shortest path to exit point
- Repeat until all basis paths defined
Here is an example:
A: static int GetMaxDay(int month, int year) { int maxDay = 0; B: if (IsValidDate(month, 1, year)) { C: if (IsThirtyOneDayMonth(month)) { maxDay = 31; } D: else if (IsThirtyDayMonth(month)) { maxDay = 30; } else { maxDay = 28; E: if (IsLeapYear(year)) { maxDay = 29; } } } return maxDay; F: }
Test cases:
Branch to flip | Shortest path out | Path | Input |
n/a | B==false | ABF | 0, 0 |
B | B==true, C==true | ABCF | 1,1980 |
C | B==true, C==false, D==true | ABCDF | 4,1980 |
D | B==true, C==false, D==false, E==false | ABCDEF | 2,1981 |
E | B==true, C==false, D==false, E==true | ABCDEF | 2,1980 |
These are just three of the many different ways to calculate code coverage. You can find these and more detailed in any decent book on testing computer software. There are also some good references online. Here is one from a fellow Expert Tester. As with any tool, you the tester have a responsibility to know the benefits and weaknesses of the tools you use.
Thankfully, most compilers will produce these numbers for us. Code Coverage goals at Microsoft used to be around 65% code coverage using automation. For V1 of OneNote, I was able to drive the team and get it up to 72%. Not bad for driving automation for a V1 project. With the move from boxed products to services, code coverage is getting less attention and we are now looking more into measuring feature and scenario coverage. We’ll talk about that in a future blog.
Now, what will we tell The Powers That Be?
Filed under: Coding Tips, Testing Tools | Tagged: basis path, block, code coverage, condition | 1 Comment »