Ma per quanto riguarda altri linguaggi, come il vetusto ma sempre attuale C++? Come si può conciliare la "dinamicità" di un processo come il TDD alla macchinosità di un linguaggio che prevede costante sincronizzazione header-implementazione, build system non sempre amichevoli e pochissimi tool di refactoring?
In questa guida cercherò di offrire uno dei possibili approcci. Dovendo scegliere un IDE e un toolkit ho optato per Qt di Nokia (originariamente di Trolltech) e il suo IDE Qt Creator.
Ovviamente ognuno avrà le sue preferenze, il mondo del C++ è fin troppo ricco. Per quanto mi riguarda, da utente e sviluppatore KDE, conosco Qt da molti anni, lo trovo piuttosto semplice e intuitivo, e altrettanto può dirsi di Qt Creator, sicuramente non un IDE potente e pieno di strumenti come Eclipse, ma che ha dalla sua una notevole leggerezza, nonchè l'essere studiato proprio per Qt.
Qt offre un ambiente molto omogeneo, includendo anche un modulo per i test unitari. Sicuramente ci sono framework di testi anche migliori, ma per continuare sulla via della semplicità ho preferito usare QTest. Ha anche il non indifferente vantaggio di essere orientato al cross platform, il che vuol dire che un'applicazione o libreria creata con Qt girerà sui principali sistemi operativi (Windows, Mac, Linux) senza modificare (o quasi) il codice, ma solo ricompilando per i sistemi desiderati.
Disclaimer: non oso definirmi nè un esperto di C++ nè tantomeno di Qt. Ho imparato entrambi amatorialmente, e sono un po' arrugginito dai molti anni lavorativi in cui ho utilizzato quasi esclusivamente Java.
Come progetto di esempio prenderò spunto dal Download Manager presente nella documentazione di Qt. Si tratta di un eseguibile da riga di comando, senza GUI, anche molto semplice da realizzare.
Andiamo a creare il layout del nostro progetto.
Da QtCreator sceglieremo l'opzione "New Project", scegliendo tra i vari template disponibili "Qt C++ Project << Qt Console Application". Lo chiameremo "DownloadManager", e lo creeremo nella directory "$HOME/qt".
Il progetto verrà a trovarsi quindi in "DownloadManager", avrà un main pressochè vuoto.
Adesso cominciamo a creare le prime classi? Sbagliato. In realtà il progetto "DownloadManager" è praticamente già finito. Tutto il nostro codice andrà invece in una libreria statica: questo perchè dovrà essere accessibile anche dai test. Una libreria statica è molto diversa da una dinamica: essa non verrà infatti "esportata" come una dipendenza, ma viene inglobata dall'eseguibile, avendo però il vantaggio di poter condividere le proprie classi anche con i test che creeremo.
Quindi, nuovo progetto, con tipo "Other Project << C++ Library". Scegliamo nel wizard "Statically Linked Library", assegnamo come nome "DownloadManagerCore" e lo creiamo sempre nella posizione "$HOME/qt". Nella pagina successiva scegliamo di aggiungere i moduli "QtCore" e "QtNetwork". Il wizard creerà anche una classe principale (che potremo usare come un main) DownloadManagerCore.
Adesso è ora di creare il progetto (o come vedremo, i progetti) per i test, cominciando dagli unit: sempre dal menù nuovo progetto, scegliamo nuovamente "Other Project << Qt Console Application", con nome "UnitTests" e solita posizione, "$HOME/qt". Nel file UnitTests.pro creato dal wizard cambiamo la riga
QT += corein
QT += core testlib
Col tasto destro sul progetto selezioniamo "Add New...", quindi scegliamo "C++ Class".
Diamo nome alla classe: "DownloadManagerCoreTest", base class "QObject".
Modifichiamo lo header creando un primo test come private slot:
#ifndef DOWNLOADMANAGERCORETEST_H #define DOWNLOADMANAGERCORETEST_H #include <QObject>class DownloadManagerCoreTest : public QObject { Q_OBJECT public: explicit DownloadManagerCoreTest(QObject *parent = 0); private slots: void itShouldLinkToStaticLibrary(); }; #endif // DOWNLOADMANAGERCORETEST_H
e anche la sua implementazione:
#include <QTest>#include "downloadmanagercoretest.h" #include "../DownloadManagerCore/downloadmanagercore.h" DownloadManagerCoreTest::DownloadManagerCoreTest(QObject *parent) : QObject(parent) { } void DownloadManagerCoreTest::itShouldLinkToStaticLibrary() { DownloadManagerCore *lib = new DownloadManagerCore(); QVERIFY2(lib != NULL, "Library should not be null"); }
QVERIFY è una semplice asserzione che verifica il valore booleano passato come parametro.
QVERIFY2 aggiunge la possibilità di specificare un messaggio.
Questi sono un po' i "mattoncini base" per le asserzioni; ne esistono altre, ed è anche possibile implementarne di personalizzate.
Modificare anche il main.cpp come segue:
#include <QtCore/QString>#include <QtTest/QTest> #include <QList> #include "downloadmanagercoretest.h" class UnitTests : public QObject { Q_OBJECT private: int argc; char **argv; public: UnitTests(int argc, char *argv[]); int runAll(); private: QList<QObject*> tests; }; UnitTests::UnitTests(int argc, char *argv[]) : argc(argc), argv(argv) { tests << new DownloadManagerCoreTest(this); } int UnitTests::runAll() { int testResults=0; foreach(QObject *currentTest, tests) { testResults += QTest::qExec(currentTest, argc, argv); } return testResults; } int main(int argc, char *argv[]) { UnitTests testSuite(argc, argv); return testSuite.runAll(); } #include "main.moc"
Aggiungere nuovi test alla suite è semplice, basta creare dei metodi (void) come "private slots" nelle classi di test.
Se vorremo aggiungere nuove classi di test alla test suite, basterà invece creare nuove classi del tutto simili a DownloadManagerCoreTest: molto semplicemente devono estendere QObject e tutti i metodi dichiarati "private slot" verranno automaticamente usati come test case.
Si deve poi includere lo header corretto nel main.cpp ed aggiungere una riga uguale all'esistente nel costruttore di UnitTests per aggiungere la classe di test alla suite.
Infine, le altre suite di test, come gli integration test e gli acceptance test, possono essere creati esattamente nello stesso modo del progetto UnitTests.
Ma che succede se proviamo ad eseguire il nostro primo test unitario?
Non compila nulla.. o per meglio dire, il link fallisce: questo perchè il nostro progetto di test non "vede" ancora il progetto contenente la classe "DownloadManagerCore". Come Fare?
Beh, basta aprire nuovamente il file "UnitTests.pro" ed aggiungere alla fine del file la riga
LIBS += -L../DownloadManagerCore-build-desktop/ -lDownloadManagerCore
Visto che anche l'applicazione "DownloadManager" richiederà la libreria può essere utile aggiungere questa riga anche nel suo file .pro.
Può anche essere utile andare nel tab "Projects" di Qt Creator, e nei progetti "DownloadManager" e "UnitTests" selezionare come dipendenza (tab Dependencies) il progetto "DownloadManagerCore".
Adesso basta compilare il tutto per avere la suite di test funzionante:
********* Start testing of DownloadManagerCoreTest *********Config: Using QTest library 4.7.4, Qt 4.7.4PASS : DownloadManagerCoreTest::initTestCase()
PASS : DownloadManagerCoreTest::itShouldLinkToStaticLibrary()
PASS : DownloadManagerCoreTest::cleanupTestCase()Totals: 3 passed, 0 failed, 0 skipped********* Finished testing of DownloadManagerCoreTest *********
p.s.: i sorgenti per questa versione del progetto potete trovarmi sul mio account github.
Bell'articolo, complimenti!
ReplyDelete