To write the test(s) for a function, we need to consider the different inputs the function can take and the expected outputs for those inputs. We generally do not need to (and could not) test every possible input to a function. Instead, we can test a few specific cases that cover the different scenarios we expect the function to handle. For example: if we are writing a function bool isEven(int n), we do not need to test every possible integer. Instead, we would start with a representative sample. If the function reports that 2, 4, and 52 are even, it is likely going to work for other even numbers.
A test can not generally prove that a function is correct for all possible inputs. The goal is to give us confidence that the function works correctly in all the scenarios we care about.
If we are writing a function that involves conditional logic, you should consider testing each possible path through the code. Knowing that a function isEven returns true when it is given 2 is good. But it doesnβt show that it returns false when given an odd number. So we should also test it with odd numbers like 1 and 3 to ensure it behaves as expected.
We should also think about possible tricky scenarios. These are inputs that are valid, but might not be typical. For isEven, we might consider 0 and negative numbers. Our tests should include these cases to ensure the function handles them correctly.
Finally we should think about possible edge cases. These are values right at the boundary between two possible scenarios for the code. If we were writing a function isAdult to tell us if someone is a legal adult, 18 would be an edge case. That is the value at which the status changes from minor to adult. A coding error (say age > 18 instead of age >= 18) could result in this value being misclassified.
We can either implement a single test that covers all these cases, or we can write separate tests for each case. The advantage of separate tests is that they can provide more detailed feedback about which specific case is failing if the function does not behave as expected.
Here is what our tests might look like using the Doctest framework. We have grouped the tests into two TEST_CASEs, one for basic behavior and the other for special cases:
Note that we have not yet implemented the actual function isEven. We just wrote enough code for it that the tests will compile, run, and fail. Once we have tests that compile and run, we are ready to start writing the code to pass the tests.
If anyone isnβt sure our implementation is correct, looking at the tests should convince them. If they do find a bug that is not covered by our tests, the first thing we should do is add a new one or more new tests that exposes the bug - tests that fail because of the bug. Then we can worry about trying to fix the bug using the new test(s) as our goalpost.
If we ever decide to refactor (improve without intentionally changing the behavior) our code, we can run the tests to ensure that we did not break anything.
We want to write a function string academicYear(int credits) that determines what academic year a student is in based on their number of credits. A student with 0-44 credits is a freshman, 45-89 is a sophomore, 90-134 is a junior, and 135 or more is a senior.
We want to write a function string academicYear(int credits) that determines what academic year a student is in based on their number of credits. A student with 0-44 credits is a freshman, 45-89 is a sophomore, 90-134 is a junior, and 135 or more is a senior.