For learning purposes, I dived into the field of unit testing. I’ve read through a few tutorials concerning this issue using QT, and came up with the following:
class QMyUnitTest : public QObject
{
Q_OBJECT
private:
bool isPrime(unsigned int ui);
private Q_SLOTS:
void myTest();
};
bool QMyUnitTest::isPrime(unsigned int n) {
typedef std::map<unsigned int, bool> Filter;
Filter filter;
for(unsigned int ui = 2; ui <= n; ui++) {
filter[ui] = true;
}
unsigned int ui = filter.begin()->first;
for(Filter::iterator it = filter.begin();
it != filter.end(); it++) {
if(it->second) {
for(unsigned int uj = ui * ui; uj <= n; uj += ui) {
filter[uj] = false;
}
}
ui++;
}
return filter[n];
}
void QMyUnitTest::myTest() {
}
QTEST_MAIN(QMyUnitTest)
#include "tst_myunittest.moc"
I know my prime finding algorithm is inefficient and especially flawed; it is meant this way. Now I want to test it thoroughly, but the following question arose:
To test properly, do I not have to have a very precise idea of what could go wrong?
Of course I can run through the first 1000 prime numbers and check if they come out true or 1000 not prime numbers and check if they come out false, but that might not catch the flaws in the algorithm (for example: return filter[n]; is obviously horrible as filter[n] might not exist if n<2).
What use is unit testing if I already have to know what potential problems of my function are?
Am I doing something wrong? Is there a better way to test?
The purpose of unit testing is to verify that the code you wrote actually does what it is supposed to do.
To write correct and complete tests, you need to know precisely what your code is supposed to do. You then write tests to verify:
Your example of testing the behavior of your
isPrimeroutine with a numbers it doesn’t handle is a good one. Without knowing your implementation, 0, 1, 2 and negative values would be good testcases for anisPrimeroutine – they are things you might not think about when implementing your algorithm, so they are valuable tests.Note that verifying normal conditions isn’t necessarily the easiest part. As in this case, making sure your algorithm is perfect requires a mathematical analysis, then a verification that your code implements that correctly – and this is sometimes hard. Checking a few hundred known values is not necessarily enough (it might start failing at the 101st value).
You’ve got it reversed. Don’t write your unit tests with your implementation code in mind. Write it with the specification in mind. Your code must fit the spec, and your tests must ensure that as much as they can. (Code coverage tools will help you find border conditions once the bulk of your testing is done.)