Often when writing tests one value can be as good as any other value. Such as if you need to apply a number within a range from 1-18. 5 might be just as good as 10. In this blog post I will go through some techniques used to find appropriate values for tests - and getting the right coverage for these.
Equivalence classes
Let's go basic. If you have ever studied software testing you are bound to have heard of Equivalence classes or Equivalence partitioning. Equivalence classes are sets of items with elements that are equal in result. They can be ranges, sets or simply booleans (true/false). Meaning in the above mentioned example 5 and 10 would be in the same equivalence class. As they both give the same result - which is that they are acceptable inputs. Therefore they are in the same equivalence class of "valid inputs". In the invalid input equivalence class we have values that are < 1 and > 18. Meaning any values below 1 and above 18 are equally good - if you want an invalid result.
For the above scenario we would have 3 equivalence classes:
Valid
1) < 19 and > 1
Invalid
2) > 18
3) < 1
"Why is > 18 and < 1 split? you are probably wondering. The reason is that this would often be implemented as the following:
if (age <= 18 && age >= 1){
//OK
}
else {
//NOT OK
}
In the above snippet we see two boundaries. One at 1 and one at 18. The values on and within these boundaries are valid and the ones outside are invalid. In order to just test an OK scenario we only need one testcase. We can also test the NOT OK scenario with one test case. But we have only tested for values below 1 or above 18. In order to test both boundaries we need to test both. The programmer might as well have implemented it as:
if (age > 18 || age < 1){
//NOT OK
}
else {
//OK
}
Which requires both invalid test cases for it to test both sides of the if statement. Meaning if we provided the values 0, 5 and 200 we would be using a number in all 3 equivalence classes. Covering them all and thereby having adequate coverage.
However for ranges (such as our current example) we can apply boundaries values. Meaning we test the boundaries where each of our equivalence classes meet. Meaning that we would have to write tests using 0 (invalid), 1 (valid), 18 (valid) and 19 (invalid).
Item sets and booleans
Another type of equivalence classes is item sets. Item sets could be months (values: January, February etc..), weekdays (Monday, Tuesday etc..), Genders (Male/Female) and so on. The rule here is that every item in the set must be placed within different equivalence classes. Meaning they should each have their own testcase. This is due to them often being tested like the following:
If (gender == "Male" || gender == "Female") {
//OK
}
else {
//NOT OK
}
It could be done in a smarter way than the above. Such as a regular expression or some sort of "contains()" method on a list with the items in it. But I choose the above as it is is more language independent. So for the above example we would have the following Equivalence classes:
Valid
1) Male
2) Female
Invalid
3) Anything not in the set.
In theory there are no invalid equivalence classes in the above. But since the above example was using a string
and not an enum
there is a chance we might see something not in the set. The pitfall with item sets is that people often test just with one valid and one invalid input. But this does not catch errors where some items was wrongfully implemented as valid or invalid. It also does not test if an item is fully missing.
For booleans you only have a true or false input. Meaning you end up with 2 equivalence classes. This is something that people often do not have problems with deciding coverage on (or input values for that matter..)
In short
In short you may use this list as a checklist:
- For ranges use boundaries. Remember to test both sides of a boundary.
- For sets test all values.
- For bools test both true and false.
I hope you enjoyed this post! If you have any comments please write them below!