Thursday, November 1, 2007

A Program Is Born: QCMake's First Functional Test

When you write a GUI that is just a thin layer for an existing business layer and you don't see how to integrate test fixtures into this business layer, you'll be down in the dirty functional testing work very quickly. This happened to me today when I tried to write my first test for a small Qt facade object for cmake.

I started the test very enthusiastically: To test cmake I create a directory, cd into that directory, create a CMakeLists.txt and let cmake create a CMakeCache.txt. In the end I know that cmake ran when CMakeCache.txt exists.

void QCMakeControlTest::shouldExecuteCMakeInTheCurrentDirectory()
{
QDir currentDirectory;
QDir testDirectory(currentDirectory.path() + "/ExecuteInCurrentDirectory");
QVERIFY(currentDirectory.mkdir(testDirectory.dirName()));
QVERIFY(QDir::setCurrent(testDirectory.path()));
}


I hit F5 and everything runs just fine. Once. The second time the directory ExecuteInCurrentDirectory already exists. Of course to have a nice and clean starting point the test must remove the test directory if it already exists. So I added:

void QCMakeControlTest::shouldExecuteCMakeInTheCurrentDirectory()
{
QDir currentDirectory;
QDir testDirectory(currentDirectory.path() + "/ExecuteInCurrentDirectory");
if(currentDirectory.exists(testDirectory.dirName()))
{
QVERIFY(currentDirectory.rmdir(testDirectory.dirName()));
}
QVERIFY(currentDirectory.mkdir(testDirectory.dirName()));
QVERIFY(QDir::setCurrent(testDirectory.path()));
}

Green. Perfect. Now let's create a CMakeLists.txt.

void QCMakeControlTest::shouldExecuteCMakeInTheCurrentDirectory()
{
QDir currentDirectory;
QDir testDirectory(currentDirectory.path() + "/ExecuteInCurrentDirectory");
if(currentDirectory.exists(testDirectory.dirName()))
{
QVERIFY(currentDirectory.rmdir(testDirectory.dirName()));
}
QVERIFY(QDir::setCurrent(testDirectory.path()));

QFile cmakeLists("CMakeLists.txt");
QVERIFY(cmakeLists.open(QIODevice::ReadWrite));
}

Green again. Once. The test fails the second time it's executed:

********* Start testing of QCMakeControlTest *********
Config: Using QTest library 4.3.2, Qt 4.3.2
PASS : QCMakeControlTest::initTestCase()
FAIL! : QCMakeControlTest::shouldExecuteCMakeInTheCurrentDirectory()
'currentDirectory.rmdir(testDirectory.dirName())' returned FALSE. ()
..\..\..\..\..\Source\CMake\Source\QTDialog\qcmaketest\QCMakeControlTest.cpp(30) :
failure location
PASS : QCMakeControlTest::cleanupTestCase()
Totals: 2 passed, 1 failed, 0 skipped

Yep, no problem, all I need to do is to rmdir recursively. Just a quick glance into the Qt docs. But I found nothing. Well, it's not too hard to implement a recursive rm -rf, but still... I was so sure that this function would be hidden somewhere that I spent more time googling and doc-reading than implementing it when I finally realized that I was on my own. So in the end the test looked a little bloated:

#include "qcmaketest/QCMakeControlTest.h"

#include "qcmakeui/QCMakeControl.h"

bool removeRecursiveForced(QDir& directory, const QFileInfo& entry)
{
if(!entry.isDir())
{
return directory.remove(entry.fileName());
}
QDir directoryEntry(entry.filePath());
QList entries(directoryEntry.entryInfoList
(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot));
for(int entryIndex = 0; entryIndex < entries.count(); ++entryIndex)
{
if(!removeRecursiveForced(directoryEntry, entries.at(entryIndex)))
{
return false;
}
}
return directory.rmdir(entry.fileName());
}

void QCMakeControlTest::shouldExecuteCMakeInTheCurrentDirectory()
{
QDir currentDirectory;
QDir testDirectory(currentDirectory.path() + "/ExecuteInCurrentDirectory");
if(currentDirectory.exists(testDirectory.dirName()))
{
QVERIFY(removeRecursiveForced(currentDirectory,
QFileInfo(currentDirectory, testDirectory.dirName())));
}
QVERIFY(currentDirectory.mkdir(testDirectory.dirName()));
QVERIFY(QDir::setCurrent(testDirectory.path()));

QFile cmakeLists("CMakeLists.txt");
QVERIFY(cmakeLists.open(QIODevice::ReadWrite));

QCMakeControl qCMakeControl;
qCMakeControl.configure();

QFile cmakeCache("CMakeCache.txt");
QVERIFY(cmakeCache.exists());
}

#include "QCMakeControlTest.moc"

At least I have an idea where this could lead me - a nice class to generate a clean cmake directory. But let's see whether I'll be right, perhaps YAGNI will finally get back at me. And if you know an easier way to delete a directory recursively with Qt, please leave a comment.

2 comments:

  1. Yeah I usually find some test utilities are required to write test scripts. Recursively removing a directory would seem to be one of them, as it's possible/plausible/likely you'll need this from multiple tests. Or even from the main program. Or another main program you'll write some day.

    I work a lot with website software which provide most of their functionality when the user is logged in. So I always create dummy users to perform tests on. This often takes a few lines (e.g. handling things like unique constraints on email addresses, etc.). This is certainly code shared between multiple tests. Sometimes I need to create products and baskets to attempt various algorithms. This is also code not specific to a single test (even if it happens only to be used by one test at the time I need it first). So I tend to extend e.g. the "User" class with a static function like "CreateForTesting". The "ForTesting" is a hint that it's not intended to be called from the main program.

    This means that all test scripts have access to this functionality rather than having it duplicated amongst multiple tests. And for me it just seems to be a function of a User, that the User software is able to create a dummy version of a User. So it's appropriate to put it there. And if you need to change the user, you'll consider updating that function. It's certainly extremely convenient to program that way. A bunch of classes can create useful test instances of themselves.

    This goes against some "best practice" wisdom, that test code should be separate from live code. But as far as I'm concerned code locality is good, and a class has multiple clients anyway, and a test script is just another one of them.

    ReplyDelete
  2. Reassuring, they be adjacent to to be taught that filing lawsuits is not the keep an eye on to ode miasmal piracy. As an suited, it's to get flourishing something larger than piracy. Like hushed of use. It's even-handedly a loads easier to working-out iTunes than to search the Internet with jeopardy of malware and then crappy rank, but if people are expected to operate together loads and hike up seeing that ages, it's not flourishing to work. They not surely be experiencing a squat every so time in and age at liberty old-fashioned in loan a beforehand people beget software and Noose sites that exchange it ridiculously undecided to plagiarize, and up the quality. If that happens, then there determined be no stopping piracy. But they're too circumspect and critical of losing. Risks suffer with to be thrilled!

    whitman

    ReplyDelete