Another couple of weeks, another major release!
This time with more interesting features I hope.
First of all, now Touché is a KDE only application, no more dual releases with Qt-only version. Of course, this may disappoint someone, but this allowed me to add some nice new features, like Global Shortcuts for profile switching.
And profiles themself were improved too, both fixing small problems, and adding a "Default" profile, when no other profile is available.
Default profile will also provide a "fallback action" if some key is not configured on the selected profile, this way you can enable an action on multiple profiles without having to copy it.
Main menu was refactored a bit, It's nicer and much more well organized.
Code was refactored as well, which lead us to an API for adding new "device modules", not just HID devices.
And we have a living example too: Wiimote support. You can easly setup a Wiimote as remote control by just enabling it in the "Configure Touché" menu entry, "Modules section", then by just connecting it. Currently it supports every physical button on the wiimote; I plan to add some other nice stuff too as gestures.
As usual, source tarball is available on github download page.
More downloads and information in Touché Homepage
Showing posts with label Programmazione. Show all posts
Showing posts with label Programmazione. Show all posts
Wednesday, September 5, 2012
Thursday, August 23, 2012
Touché 0.2
Hello folks? Back from vacation?
I am, and so is Touché, with a second major release!
The most important change in this version is the addition of profiles, meaning you can have multiple bindings configured for each device, and you can switch them by context menu.
This release also features many code reworks, GUI and backend fixes and refactorings opening the way to future new features.
Here's the full changelog:
I am, and so is Touché, with a second major release!
The most important change in this version is the addition of profiles, meaning you can have multiple bindings configured for each device, and you can switch them by context menu.
This release also features many code reworks, GUI and backend fixes and refactorings opening the way to future new features.
Here's the full changelog:
- Ported build system to CMake
- Added profiles, to have different bindings for the same keys
- Major code cleanup, allowing to add more engines (next one probably will be Wiimote)
- Cleaning up desktop file and appName (fixing some issues with encoding)
- Binding keypress and keyrelease events on "Translate to key" dialog
- Added "aliases" in keyboard database, so now different device version can be mapped to the same key (example: apple remote)
Now the bad news: both the keyboard database file and the configuration file have changed.
The first one won't bother you, unless you've defined a custom keyboard mapping file. In this case, please contact me, I can help you fixing it, and it can also be added to the default keyboard database file.
For the configuration, it's much easy to migrate it:
- cd $HOME/.config/GuLinux
- mv Touché.conf Touche.conf # I renamed it, to avoid encoding issues
- now open Touche.conf with your favourite text editor, and replace "[bindings]" with "[bindings_Default]" (or bindings_YourDesiredProfileName).
Just enjoy the new release, and don't be shy! Feedback is welcome!
Download page: https://github.com/rockman81/Touche/downloads
More downloads and information in Touché Homepage
21:27 (GMT+2) Update: there was a small issue with the configuration GUI. I modified source code to fix the problem. Please download again and rebuild the package if you have any inconvenience.
21:27 (GMT+2) Update: there was a small issue with the configuration GUI. I modified source code to fix the problem. Please download again and rebuild the package if you have any inconvenience.
Wednesday, January 18, 2012
Test Driven Development, C++ e Qt: Mock Objects
Dopo aver ripulito a dovere il codice siamo pronti per aggiungere nuove funzionalità alla nostra applicazione. Chiaramente seguendo le regole del tdd, ossia aggiungendo un nuovo test prima di scrivere nuovo codice.
Prima di iniziare, ho corretto un paio di minor issue sullo script bash che lancia i test, e sulla classe c++ che li raggruppa. In questo modo i test rispondono meglio alla linea di comando, ed è possibile isolare i singoli test case. Come al solito, trovate tutto sul repository git, l'url è in fondo alla pagina.
Una prima banale funzionalità che manca alla nostra applicazione è distinguere tra l'invocazione senza parametri, che come output stampa il messaggio di help, e l'invocazione con uno o più parametri, che invece fa altro.
Testare l'invocazione senza parametri è stato piuttosto facile, ma come testiamo l'invocazione con parametri, se non sappiamo ancora esattamente cosa farà?
Ci viene in aiuto la composizione fra oggetti: assumiamo infatti che la nostra classe DownloadManagerCore avrà uno (o più) collaboratori addetti al download, in tal caso la nostra classe dovrà semplicemente dare in pasto a questi collaboratori la lista degli url da scaricare.
Dobbiamo quindi identificare questi collaboratori, dare loro un nome (e quindi una responsabilità) e scrivere la loro interfaccia. Non ci interessa implementarli davvero, lo faremo in un secondo momento nei rispettivi test unitari, ci interessa però definire la loro interfaccia in modo da poterne creare istanze "finte" da usare nel test di DownloadManagerCore.
In una prima approssimazione possiamo pensare di creare un oggetto Queue, con un metodo add(QStringList) che viene invocato se l'oggetto DownloadManagerCore riceve una lista di argomenti non vuota.
Cominciamo quindi a creare la sua interfaccia: dato che si tratterà di un'interfaccia di dominio, creiamo il suo header nella sottodirectory domain del progetto downloadmanagercore.
Ora possiamo scrivere una sua implementazione Fake (nel progetto UnitTests) su cui faremo delle asserzioni.
Gli oggetti Fake, detti anche Mock Objects, sono degli utili strumenti per verificare come gli oggetti interagiscono fra loro.
In realtà alcuni autori suggeriscono varie differenze tra mock objects, fake e stub, sopratutto legate alla loro programmabilità. Per semplicità al momento assumeremo equivalenti mock e fake, la rete è comunque piena di approfondimenti.
Vale la pena di specificare che in C++ è indispensabile che un oggetto abbia dei metodi virtual per poter essere correttamente mockato.
Un buon design fa comunicare fra loro le classi sopratutto tramite interfacce piuttosto che tramite oggetti concreti, questo facilita molto la creazione di mock object adeguati.
Tornando alla classe FakeQueue, per poter fare delle asserzioni un field addWasCalled di tipo bool, e un field di tipo QStringList chiamato receivedUrls. Nel nuovo test verificheremo quindi che addWasCalled venga settato a true, e che receivedUrls sia uguale alla lista degli url ricevuti da DownloadManagerCore.
Questo tipo di oggetto permette di testare l'interazione tra la classe sotto test (DownloadManagerCore) ed i suoi collaboratori.
Aggiungiamo il nuovo test case, che verifica che la chiamata add viene invocata istanziando DownloadManagerCore con almeno un argomento.
In questo momento il test non compila neanche, questo perchè DownloadManagerCore non accetta ancora un puntatore a Queue in costruzione; come prima cosa quindi modifichiamo la firma del costruttore di DownloadManagerCore.
Naturalmente dovremo sistemare anche gli altri test, che adesso avranno bisogno di Queue istanziando DownloadManagerCore.
Adesso vediamo finalmente il test fallire come ci aspettiamo: ci dice che si aspetta una QStringList di due stringe, e ne trova una vuota. Dobbiamo quindi far si che DownloadManagerCore accodi i parametri. Ma se invochiamo il metodo add su queue, il test fallisce perchè si aspetta che l'output (il messaggio di help) non venga scritto. Dobbiamo quindi aggiungere della logica di controllo dei parametri.
Adesso abbiamo bisogno di un'implementazione vera di Queue, in modo da poter anche compilare l'eseguibile principale. Questa classe deve:
Dovremo inoltre farla collaborare con un'altra entità, un FileWriter, che a fronte della ricezione di dati scrive il tutto su filesystem. Normalmente per testare questa classe è meglio far ricorso agli IntegrationTests, che però affronteremo più avanti. Se invece volessimo testare unitariamente una classe che collabora con alcune classi, come QNetworkAccessManager, di cui non possiamo creare implementazioni mock, come potremmo fare?
Purtroppo non c'è una risposta semplice, ed è proprio per questo che in questi casi si preferiscono di gran lunga i test di integrazione; tuttavia come esercizio proviamo ad elaborare una strategia.
Quella forse più efficace è di incapsulare le classi Qt da mockare in oggetti decorator, che implementeranno un'interfaccia che sarà la base del nostro mock object. In questo modo possiamo facilmente mockare i decorator, che saranno sufficientemente "stupidi" da non richiedere ulteriori test. Il decorator avrà anche il vantaggio di esporre solo i metodi che ci serviranno davvero, ripulendo l'interfaccia pubblica della classe decorata.
Per semplicità al momento ci occuperemo solo di testare la funzionalità di avvio e accodamento downlod.
Le classi che dovremo decorare saranno principalmente due:
Chiaramente i test sono fatti apposta per evolversi man mano che le funzionalità vengono aggiunte, intanto però possiamo fare una lista dei metodi dell'interfaccia pubblica che avrà la classe NetworkAccess:
Prima di iniziare, ho corretto un paio di minor issue sullo script bash che lancia i test, e sulla classe c++ che li raggruppa. In questo modo i test rispondono meglio alla linea di comando, ed è possibile isolare i singoli test case. Come al solito, trovate tutto sul repository git, l'url è in fondo alla pagina.
Una prima banale funzionalità che manca alla nostra applicazione è distinguere tra l'invocazione senza parametri, che come output stampa il messaggio di help, e l'invocazione con uno o più parametri, che invece fa altro.
Testare l'invocazione senza parametri è stato piuttosto facile, ma come testiamo l'invocazione con parametri, se non sappiamo ancora esattamente cosa farà?
Ci viene in aiuto la composizione fra oggetti: assumiamo infatti che la nostra classe DownloadManagerCore avrà uno (o più) collaboratori addetti al download, in tal caso la nostra classe dovrà semplicemente dare in pasto a questi collaboratori la lista degli url da scaricare.
Dobbiamo quindi identificare questi collaboratori, dare loro un nome (e quindi una responsabilità) e scrivere la loro interfaccia. Non ci interessa implementarli davvero, lo faremo in un secondo momento nei rispettivi test unitari, ci interessa però definire la loro interfaccia in modo da poterne creare istanze "finte" da usare nel test di DownloadManagerCore.
In una prima approssimazione possiamo pensare di creare un oggetto Queue, con un metodo add(QStringList) che viene invocato se l'oggetto DownloadManagerCore riceve una lista di argomenti non vuota.
Cominciamo quindi a creare la sua interfaccia: dato che si tratterà di un'interfaccia di dominio, creiamo il suo header nella sottodirectory domain del progetto downloadmanagercore.
class Queue {
public:
virtual void add(const QStringList &urls)=0;
};
Ora possiamo scrivere una sua implementazione Fake (nel progetto UnitTests) su cui faremo delle asserzioni.
Gli oggetti Fake, detti anche Mock Objects, sono degli utili strumenti per verificare come gli oggetti interagiscono fra loro.
In realtà alcuni autori suggeriscono varie differenze tra mock objects, fake e stub, sopratutto legate alla loro programmabilità. Per semplicità al momento assumeremo equivalenti mock e fake, la rete è comunque piena di approfondimenti.
Vale la pena di specificare che in C++ è indispensabile che un oggetto abbia dei metodi virtual per poter essere correttamente mockato.
Un buon design fa comunicare fra loro le classi sopratutto tramite interfacce piuttosto che tramite oggetti concreti, questo facilita molto la creazione di mock object adeguati.
Tornando alla classe FakeQueue, per poter fare delle asserzioni un field addWasCalled di tipo bool, e un field di tipo QStringList chiamato receivedUrls. Nel nuovo test verificheremo quindi che addWasCalled venga settato a true, e che receivedUrls sia uguale alla lista degli url ricevuti da DownloadManagerCore.
Questo tipo di oggetto permette di testare l'interazione tra la classe sotto test (DownloadManagerCore) ed i suoi collaboratori.
- header *
class FakeQueue : public Queue
{
Q_OBJECT
public:
explicit FakeQueue(QObject *parent = 0);
bool addWasCalled;
QStringList receivedUrls;
void add(const QStringList &urls);
};
- implementazione
FakeQueue::FakeQueue(QObject *parent) :
Queue(parent), addWasCalled(false), receivedUrls(QStringList())
{
}
void FakeQueue::add(const QStringList &urls)
{
addWasCalled=true;
receivedUrls=urls;
}
Aggiungiamo il nuovo test case, che verifica che la chiamata add viene invocata istanziando DownloadManagerCore con almeno un argomento.
void DownloadManagerCoreTest::itShouldQueueUrlsWhenCalledWithArguments()
{
QStringList urlList = QStringList() << "first url" << "second url";
QString output;
QTextStream outputStream(&output);
FakeQueue queue;
DownloadManagerCore *lib = new DownloadManagerCore(&outputStream, urlList, &queue, this);
lib->start();
QCOMPARE(queue.receivedUrls, urlList);
QCOMPARE(output, QString() );
QVERIFY2(queue.addWasCalled, "Add method should be called on queue");
}
In questo momento il test non compila neanche, questo perchè DownloadManagerCore non accetta ancora un puntatore a Queue in costruzione; come prima cosa quindi modifichiamo la firma del costruttore di DownloadManagerCore.
DownloadManagerCore(QTextStream *output, const QStringList &arguments, Queue *queue, QObject *parent = 0);
Naturalmente dovremo sistemare anche gli altri test, che adesso avranno bisogno di Queue istanziando DownloadManagerCore.
Adesso vediamo finalmente il test fallire come ci aspettiamo: ci dice che si aspetta una QStringList di due stringe, e ne trova una vuota. Dobbiamo quindi far si che DownloadManagerCore accodi i parametri. Ma se invochiamo il metodo add su queue, il test fallisce perchè si aspetta che l'output (il messaggio di help) non venga scritto. Dobbiamo quindi aggiungere della logica di controllo dei parametri.
class DownloadManagerCorePrivate {
public:
DownloadManagerCorePrivate(QTextStream *output, const QStringList &arguments, Queue *queue);
QTextStream *output;
QStringList arguments;
Queue *queue;
void printHelpMessage();
};
void DownloadManagerCore::start()
{
Q_D(DownloadManagerCore);
if(d->arguments.isEmpty()) {
d->printHelpMessage();
emit finished();
return;
}
d->queue->add(d->arguments);
}
Adesso abbiamo bisogno di un'implementazione vera di Queue, in modo da poter anche compilare l'eseguibile principale. Questa classe deve:
- ricevere la lista dei parametri
- memorizzarla in una lista di url
- effettuare validazioni
- avviare il download
Dovremo inoltre farla collaborare con un'altra entità, un FileWriter, che a fronte della ricezione di dati scrive il tutto su filesystem. Normalmente per testare questa classe è meglio far ricorso agli IntegrationTests, che però affronteremo più avanti. Se invece volessimo testare unitariamente una classe che collabora con alcune classi, come QNetworkAccessManager, di cui non possiamo creare implementazioni mock, come potremmo fare?
Purtroppo non c'è una risposta semplice, ed è proprio per questo che in questi casi si preferiscono di gran lunga i test di integrazione; tuttavia come esercizio proviamo ad elaborare una strategia.
Quella forse più efficace è di incapsulare le classi Qt da mockare in oggetti decorator, che implementeranno un'interfaccia che sarà la base del nostro mock object. In questo modo possiamo facilmente mockare i decorator, che saranno sufficientemente "stupidi" da non richiedere ulteriori test. Il decorator avrà anche il vantaggio di esporre solo i metodi che ci serviranno davvero, ripulendo l'interfaccia pubblica della classe decorata.
Per semplicità al momento ci occuperemo solo di testare la funzionalità di avvio e accodamento downlod.
Le classi che dovremo decorare saranno principalmente due:
- QNetworkAccessManager, che diventerà NetworkAccess
- QNetworkReply, che diventerà NetworkReply.
Chiaramente i test sono fatti apposta per evolversi man mano che le funzionalità vengono aggiunte, intanto però possiamo fare una lista dei metodi dell'interfaccia pubblica che avrà la classe NetworkAccess:
- NetworkReply * get ( const QNetworkRequest & request)
- [signal] void finished ( NetworkReply * reply )
Ecco invece l'interfaccia di NetworkReply:
- [signal] void QIODevice::readyRead ()
- QByteArray QIODevice::readAll ()
Il codice risultante è un po' lunghetto da pubblicare qui, rimando quindi direttamente al branch github per questa puntata. In ogni caso, per quanto un po' tedioso, il test ha comunque fatto il suo dovere, permettendoci di sviluppare una funzionalità molto legata alla rete senza di fatto avviare alcuna connessione.
* normalmente è uno smell avere in una classe dei field pubblici. È però vero che questa è una classe non applicativa, che esiste solo nei test e che non ha vere responsabilità se non memorizzare il valore di quei field e fornirlo a chi li testa.
Tuesday, January 17, 2012
Test Driven Development, C++ e Qt: refactoring, ottimizzazioni, d-pointer
Nel precedente post abbiamo dato una messa a punto ai file del progetto. Potrebbe essere il caso di fare qualcosina anche per il codice sorgente...
In questa puntata vedremo alcuni passi di refactoring che ci aiuteranno ad implementare funzionalità future, e renderanno il codice più robusto. Vedremo ad esempio come applicare il meccanismo di Signal e Slot di Qt, particolarmente indicato per la programmazione "ad eventi", e in cosa consiste il pattern d-pointer che in alcuni ambienti (ad esempio nella programmazione per KDE) è divenuto uno standard praticamente obbligatorio
Sunday, January 8, 2012
Test Driven Development, C++ e Qt: ottimizzazioni layout progetti
Nella puntata precedente abbiamo creato un primo "vero" test di dominio perfettamente funzionante; il ciclo del TDD prevede a questo punto di effettuare un po' di refactoring, se necessario, prima di iniziare la nuova funzionalità.
In questo capitolo ci occuperemo non tanto di refactoring relativi al design (qualcosa si può fare, ma avendo scritto pochissimo codice è davvero poca roba), quanto piuttosto di trasformare quello che adesso è codice "sperimentale" in qualcosa di più professionale, usando layout di progetto, ottimizzazioni e convenzioni che sono da favorire in progetti pronti per la produzione.
Ma che problemi ha il nostro progettino?
In realtà un bel po', cominciamo dalle cose più evidenti: la prima è che i progetti sono collegati tra loro piuttosto malamente.
Sappiamo che il progetto DownloadManager deve usare DownloadManagerCore, ma al momento non c'è nessun riferimento ad esso. Sappiamo anche che il progetto UnitTests deve fare, e in realtà fa già, la stessa cosa. Ma per farlo abbiamo dovuto aggiungere la riga:
e di certo non vorremo tenerla così: quando infatti dovremo compilare in modalità release sarebbe necessario modificare manualmente il file di progetto (tanto nel progetto UnitTests quanto in DownloadManager), e non è sicuramente il massimo!
Ma anche nel codice sorgente c'è qualcosina che non va: in downloadmanagercoretest.cpp vediamo la riga
#include "../DownloadManagerCore/downloadmanagercore.h"
che dovremmo ripetere per ogni futuro test case. Non va bene!
Per fortuna qmake ci viene in aiuto con alcune funzioni interessanti.
Aggiungendo al file di progetto la riga
rendiamo disponibli tutti gli header di DownloadManagerCore al progetto UnitTests, trasformando quindi la riga precedente in
Applichiamo questa modifica anche al progetto DownloadManager.
Per distinguere invece tra debug e release, usando anche un path migliore per la direttiva LIBS dobbiamo invece usare una funzionalità un po' più avanzata di qmake.
L'idea alla base è di compilare DownloadManagerCore, installarlo in una directory comune (con suffisso -debug o -release a seconda di come l'abbiamo compilata) e far puntare gli altri progetti a quella directory.
Per far questo dobbiamo aggiungere questa sezione a tutti i file di progetto (quindi DownloadManagerCore, DownloadManager e UnitTests):
Brevemente, viene assegnata una variabile COMPILE_MODE con un valore dipendente da come si compila il progetto corrente.
win32 viene gestito a parte perchè ho notato che COMPILE_MODE rimane vuota con la sintassi specificata nel ramo else.
Probabilmente dipende dal tipo di compilatore in uso, la sezione nel ramo win32 è stata testata su visual c++ 2010, probabilmente usando invece mingw non è ncessaria.
In ogni caso, provate a vedere se COMPILE_MODE viene popolata o meno usando l'istruzione
Adesso che abbiamo definito la variabile, proviamo ad usarla.
Nel progetto DownloadManagerCore aggiungiamo il path per l'installazione.
Questo farà in modo che eseguendo make install la libreria verrà copiata nella directory static-libraries.bin/debug (o release).
È consigliabile anche modificare i profili del progetto in qt creator: in "projects->build settings", tra i build steps, aggiungiamo al passo make l'argomento install. Questo per tutti i profili di DownloadManagerCore.
In questo modo, ad ogni build, la libreria statica verrà installata automaticamente.
Ora nei progetti DownloadManager e UnitTests andiamo ad usare questa nuova funzionalità. Molto semplicemente, la riga "LIBS+= ....." vista in precedenza, diventerà:
aggiungendola a DownloadManager dove non esisteva ancora.
Il modo migliore per verificare il risultato di quello che abbiamo fatto è cancellare tutte le directory di build, ricompilare da capo il progetto UnitTests (e naturalmente la sua dipendenza, DownloadManagerCore) e verificare che i test passino ancora.
Un' ultima ottimizzazione: risulta spesso utile in molti progetti avere una directory dove inserire script per automatizzare i task più frequenti, che spesso richiederebbero una serie di operazioni manuali non proprio comode. Creiamo quindi una directory "scripts". Che script possiamo scrivere per inaugurarla?
Beh, un primo task noioso e facilmente automatizzabile è la compilazione ed esecuzione automatica dei test: vogliamo uno script che sia in grado di compilare anzitutto il progetto DownloadManagerCore, installarlo nella directory static-libraries.bin, quindi compilare ed eseguire automaticamente i tests.
Come al solito, i sorgenti per questa puntata sono disponibili sul mio account github.
Nella prossima puntata ci occuperemo invece di refactoring e ottimizzazioni del codice vero e proprio.
In questo capitolo ci occuperemo non tanto di refactoring relativi al design (qualcosa si può fare, ma avendo scritto pochissimo codice è davvero poca roba), quanto piuttosto di trasformare quello che adesso è codice "sperimentale" in qualcosa di più professionale, usando layout di progetto, ottimizzazioni e convenzioni che sono da favorire in progetti pronti per la produzione.
Ma che problemi ha il nostro progettino?
In realtà un bel po', cominciamo dalle cose più evidenti: la prima è che i progetti sono collegati tra loro piuttosto malamente.
Sappiamo che il progetto DownloadManager deve usare DownloadManagerCore, ma al momento non c'è nessun riferimento ad esso. Sappiamo anche che il progetto UnitTests deve fare, e in realtà fa già, la stessa cosa. Ma per farlo abbiamo dovuto aggiungere la riga:
LIBS += -L../DownloadManagerCore-build-desktop/ -lDownloadManagerCore
e di certo non vorremo tenerla così: quando infatti dovremo compilare in modalità release sarebbe necessario modificare manualmente il file di progetto (tanto nel progetto UnitTests quanto in DownloadManager), e non è sicuramente il massimo!
Ma anche nel codice sorgente c'è qualcosina che non va: in downloadmanagercoretest.cpp vediamo la riga
#include "../DownloadManagerCore/downloadmanagercore.h"
che dovremmo ripetere per ogni futuro test case. Non va bene!
Per fortuna qmake ci viene in aiuto con alcune funzioni interessanti.
Aggiungendo al file di progetto la riga
INCLUDEPATH += ../DownloadManagerCore
rendiamo disponibli tutti gli header di DownloadManagerCore al progetto UnitTests, trasformando quindi la riga precedente in
#include "downloadmanagercore.h"
Applichiamo questa modifica anche al progetto DownloadManager.
Per distinguere invece tra debug e release, usando anche un path migliore per la direttiva LIBS dobbiamo invece usare una funzionalità un po' più avanzata di qmake.
L'idea alla base è di compilare DownloadManagerCore, installarlo in una directory comune (con suffisso -debug o -release a seconda di come l'abbiamo compilata) e far puntare gli altri progetti a quella directory.
Per far questo dobbiamo aggiungere questa sezione a tutti i file di progetto (quindi DownloadManagerCore, DownloadManager e UnitTests):
win32 {
build_pass:CONFIG(debug, debug|release) { COMPILE_MODE='debug' }
else:build_pass { COMPILE_MODE='release' }
} else {
CONFIG(debug, debug|release) { COMPILE_MODE='debug' }
else { COMPILE_MODE='release' }
}
Brevemente, viene assegnata una variabile COMPILE_MODE con un valore dipendente da come si compila il progetto corrente.
win32 viene gestito a parte perchè ho notato che COMPILE_MODE rimane vuota con la sintassi specificata nel ramo else.
Probabilmente dipende dal tipo di compilatore in uso, la sezione nel ramo win32 è stata testata su visual c++ 2010, probabilmente usando invece mingw non è ncessaria.
In ogni caso, provate a vedere se COMPILE_MODE viene popolata o meno usando l'istruzione
message("Using compile mode: $$COMPILE_MODE")
Adesso che abbiamo definito la variabile, proviamo ad usarla.
Nel progetto DownloadManagerCore aggiungiamo il path per l'installazione.
target.path='../static-libraries.bin/$$COMPILE_MODE'
INSTALLS += target
Questo farà in modo che eseguendo make install la libreria verrà copiata nella directory static-libraries.bin/debug (o release).
È consigliabile anche modificare i profili del progetto in qt creator: in "projects->build settings", tra i build steps, aggiungiamo al passo make l'argomento install. Questo per tutti i profili di DownloadManagerCore.
In questo modo, ad ogni build, la libreria statica verrà installata automaticamente.
Ora nei progetti DownloadManager e UnitTests andiamo ad usare questa nuova funzionalità. Molto semplicemente, la riga "LIBS+= ....." vista in precedenza, diventerà:
LIBS += -L../static-libraries.bin/$$COMPILE_MODE -lDownloadManagerCore
aggiungendola a DownloadManager dove non esisteva ancora.
Il modo migliore per verificare il risultato di quello che abbiamo fatto è cancellare tutte le directory di build, ricompilare da capo il progetto UnitTests (e naturalmente la sua dipendenza, DownloadManagerCore) e verificare che i test passino ancora.
Un' ultima ottimizzazione: risulta spesso utile in molti progetti avere una directory dove inserire script per automatizzare i task più frequenti, che spesso richiederebbero una serie di operazioni manuali non proprio comode. Creiamo quindi una directory "scripts". Che script possiamo scrivere per inaugurarla?
Beh, un primo task noioso e facilmente automatizzabile è la compilazione ed esecuzione automatica dei test: vogliamo uno script che sia in grado di compilare anzitutto il progetto DownloadManagerCore, installarlo nella directory static-libraries.bin, quindi compilare ed eseguire automaticamente i tests.
#!/bin/bash set -e PROJECTS="UnitTests" COLORIZE_CMD="ccze -A" MAIN_PROJECT="DownloadManagerCore" BASE_DIRECTORY="$PWD/$( dirname $0)/.." COMPILE_BUILD_PREFIX="$BASE_DIRECTORY/.build-" if test "x$TERM" != "xxterm" || test "x$( which $( echo $COLORIZE_CMD | cut -d " " -f 1 ) )" == "x"; then #TODO add other terminals supported by ccze here COLORIZE_CMD="cat" fi if test "x$1" == "x--help"; then echo "Usage: $0 [Test-Project-Name [test-method-name]]" echo "It runs every project configured in the PROJECTS variable, or can select from parameters" echo "The variable MODE can be used to choose between debug and release compilation" echo "Currently supported test projects: $PROJECTS" exit 0 fi if test "x$1" != "x"; then PROJECTS="$1" [ -f $PROJECTS ] && PROJECTS="$( basename "$( dirname "$PROJECTS")" )" echo "**** Project: $PROJECTS" shift fi BUILD_MODE="${MODE-debug}" function buildProject() { project="$1" shift make_args="$1" shift #rm -rf "$project.build" mkdir -p "${COMPILE_BUILD_PREFIX}$project" cd "${COMPILE_BUILD_PREFIX}$project" qmake "CONFIG+=$BUILD_MODE silent" "DEFINES += QT_NO_DEBUG_OUTPUT" "$@" "../$project" make $make_args > /dev/null cd .. } function runTests() { suitename="$1" shift echo -e "Running suite $suitename\n" cd "${COMPILE_BUILD_PREFIX}$suitename" binary="./$suitename" $binary $@ | while read line; do echo "$line" | $COLORIZE_CMD ; done ERRORS=${PIPESTATUS[0]} cd .. if test "x$ERRORS" == "x0"; then echo -e "\nSuite $suitename completed without errors\n" else echo -e "Suite $suitename finished with errors!\n" exit $ERRORS fi } buildProject $MAIN_PROJECT "all install" for project in $PROJECTS; do buildProject $project "clean all" "LIBS+=-L../${COMPILE_BUILD_PREFIX}$MAIN_PROJECT" runTests $project $@ done
Come al solito, i sorgenti per questa puntata sono disponibili sul mio account github.
Nella prossima puntata ci occuperemo invece di refactoring e ottimizzazioni del codice vero e proprio.
Friday, January 6, 2012
Test Driven Development, C++ e Qt: primi passi (e test)
Dopo aver creato la struttura del progetto nella prima parte, cominciamo adesso ad implementare l'applicazione.
Guardiamo l'esempio di Qt da cui stiamo prendendo spunto: il main prende in ingresso la lista di downloads (usando QCoreApplication, che li converte automaticamente in una comoda QStringList), e, se la lista è vuota stampa le informazioni di utilizzo, uscendo.
Potremmo andare avanti ad analizzare le successive funzionalità, certo l'applicazione non si limita a far questo, ma il tdd ci impone di creare un primo test (che inizialmente fallisce), ed implementare la funzionalità facendo passare il test prima di procedere troppo oltre. In una applicazione così semplice, è meglio semplificare anche l'approccio evitando di pensar troppo al futuro.
L'esempio mette il messaggio di errore nel main. Così facendo però non potremmo testarlo. D'altro canto è proprio il main che costruisce la lista di argomenti. Un buon compromesso quindi è di creare un metodo download(const QStringList &arguments)* nella classe DownloadManagerCore che come risultato stampi le informazioni di utilizzo, usando gli argomenti creati da QCoreApplication.
Può inoltre aiutare molto far estendere QObject alla classe DownloadManagerCore; in questo modo si ottimizza la memoria (c++ non ha garbage collector, però ogni classe che estende QObject verrà automaticamente distrutta quando viene distrutta la classe parent) e si migliora l'iterazione con le classi che verranno usate, le quali saranno infatti a loro volta derivate da QObject.
Nel main.cpp invocheremo QCoreApplication per estrarre gli argomenti, e invocheremo DownloadManagerCore::download(arguments).
Proviamo quindi a scrivere il primo test. In particolare asseriremo che il metodo download stamperà su standard output (o meglio, su un output stream testabile), se la lista degli argomenti è vuota, la seguente stringa:
Creiamo quindi il test case itShouldPrintHelpMessageWithNoArguments.
Al di là della funzionalità, di per se banale, notiamo un po' di cose importanti su come il test stia definendo in maniera molto precisa il design della classe:
Ho anche fatto estendere QObject a QDownloadManagerCore, in questo modo si potrà usare il meccanismo di auto-delete di Qt: se le classi del nostro progetto estendono QObject, e si assegna loro una classe "parent", le classi saranno automaticamente cancellate quando sarà cancellata la loro classe parent, senza nessun bisogno di cancellarle manualmente. In questo modo è possibile ridurre al minimo i rischi di memory leak, potendo assegnare alle classi uno scope ben preciso. Maggiori dettagli qui.
Naturalmente il nostro test non può ancora passare: abbiamo definito l'interfaccia della nostra classe, ma non l'implementazione.
Per avere un primo test verde basta usare lo stream per stampare in output l'help message, senza preoccuparci degli argomenti, dato che per il momento nessun test ne verifica l'utilizzo.
E finalmente, barra verde!
La prossima volta vedremo di ottimizzare il nostro codice prima di aggiungere nuove funzionalità: refactoring, ma anche migliorie del layout del progetto e ottimizzazioni varie.
Qui il codice sorgente per questa puntata.
Guardiamo l'esempio di Qt da cui stiamo prendendo spunto: il main prende in ingresso la lista di downloads (usando QCoreApplication, che li converte automaticamente in una comoda QStringList), e, se la lista è vuota stampa le informazioni di utilizzo, uscendo.
Potremmo andare avanti ad analizzare le successive funzionalità, certo l'applicazione non si limita a far questo, ma il tdd ci impone di creare un primo test (che inizialmente fallisce), ed implementare la funzionalità facendo passare il test prima di procedere troppo oltre. In una applicazione così semplice, è meglio semplificare anche l'approccio evitando di pensar troppo al futuro.
L'esempio mette il messaggio di errore nel main. Così facendo però non potremmo testarlo. D'altro canto è proprio il main che costruisce la lista di argomenti. Un buon compromesso quindi è di creare un metodo download(const QStringList &arguments)* nella classe DownloadManagerCore che come risultato stampi le informazioni di utilizzo, usando gli argomenti creati da QCoreApplication.
Può inoltre aiutare molto far estendere QObject alla classe DownloadManagerCore; in questo modo si ottimizza la memoria (c++ non ha garbage collector, però ogni classe che estende QObject verrà automaticamente distrutta quando viene distrutta la classe parent) e si migliora l'iterazione con le classi che verranno usate, le quali saranno infatti a loro volta derivate da QObject.
Nel main.cpp invocheremo QCoreApplication per estrarre gli argomenti, e invocheremo DownloadManagerCore::download(arguments).
Proviamo quindi a scrivere il primo test. In particolare asseriremo che il metodo download stamperà su standard output (o meglio, su un output stream testabile), se la lista degli argomenti è vuota, la seguente stringa:
Usage: downloadmanager url1 [url2... urlN]
Downloads the URLs passed in the command-line to the local directory.If the target file already exists, a .0, .1, .2, etc. is appended to differentiate.
Creiamo quindi il test case itShouldPrintHelpMessageWithNoArguments.
void DownloadManagerCoreTest::itShouldPrintHelpMessageWithNoArguments()
{
QString output;
QTextStream outputStream(&output);
DownloadManagerCore *lib = new DownloadManagerCore(&outputStream, this);
lib->start(QStringList());
QString expected("Usage: downloadmanager url1 [url2... urlN]\n");
expected.append("Downloads the URLs passed in the command-line to the local directory.");
expected.append(" If the target file already exists, a .0, .1, .2, etc. is appended to differentiate.\n");
QCOMPARE(output, expected);
}
Al di là della funzionalità, di per se banale, notiamo un po' di cose importanti su come il test stia definendo in maniera molto precisa il design della classe:
- Abbiamo bisogno di testare l'output. È quindi da evitare che la classe stampi direttamente l'output su schermo. Definiamo invece un costruttore che prende in ingresso un puntatore a QTextStream e facciamo delle asserzioni su quello. Lo stream di test, in particolare, sarà uno stream che scrive su una stringa anzichè su stdout.
- Abbiamo anche bisogno di una entry point che riceva in ingresso gli argomenti. Definiamo quindi il metodo "start" nella classe DownloadManagerCore, il cui header diventerà quindi questo:
#ifndef DOWNLOADMANAGERCORE_H #define DOWNLOADMANAGERCORE_H #include <QObject> class QTextStream; class DownloadManagerCore : public QObject { Q_OBJECT public: DownloadManagerCore(QTextStream *output); void start(const QStringList &arguments); private: QTextStream *output; }; #endif // DOWNLOADMANAGERCORE_H
Ho anche fatto estendere QObject a QDownloadManagerCore, in questo modo si potrà usare il meccanismo di auto-delete di Qt: se le classi del nostro progetto estendono QObject, e si assegna loro una classe "parent", le classi saranno automaticamente cancellate quando sarà cancellata la loro classe parent, senza nessun bisogno di cancellarle manualmente. In questo modo è possibile ridurre al minimo i rischi di memory leak, potendo assegnare alle classi uno scope ben preciso. Maggiori dettagli qui.
Naturalmente il nostro test non può ancora passare: abbiamo definito l'interfaccia della nostra classe, ma non l'implementazione.
Per avere un primo test verde basta usare lo stream per stampare in output l'help message, senza preoccuparci degli argomenti, dato che per il momento nessun test ne verifica l'utilizzo.
#include "downloadmanagercore.h" #include <QTextStream> DownloadManagerCore::DownloadManagerCore(QTextStream *output, QObject *parent) : QObject(parent), output(output) { } void DownloadManagerCore::start(const QStringList &arguments) { *output << "Usage: downloadmanager url1 [url2... urlN]" << endl; *output << "Downloads the URLs passed in the command-line to the local directory."; *output << " If the target file already exists, a .0, .1, .2, etc. is appended to differentiate." << endl; }
E finalmente, barra verde!
********* Start testing of DownloadManagerCoreTest *********
Config: Using QTest library 4.7.4, Qt 4.7.4
PASS : DownloadManagerCoreTest::initTestCase()
PASS : DownloadManagerCoreTest::itShouldLinkToStaticLibrary()
PASS : DownloadManagerCoreTest::itShouldPrintHelpMessageWithNoArguments()
PASS : DownloadManagerCoreTest::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped
********* Finished testing of DownloadManagerCoreTest *********
Config: Using QTest library 4.7.4, Qt 4.7.4
PASS : DownloadManagerCoreTest::initTestCase()
PASS : DownloadManagerCoreTest::itShouldLinkToStaticLibrary()
PASS : DownloadManagerCoreTest::itShouldPrintHelpMessageWithNoArguments()
PASS : DownloadManagerCoreTest::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped
********* Finished testing of DownloadManagerCoreTest *********
La prossima volta vedremo di ottimizzare il nostro codice prima di aggiungere nuove funzionalità: refactoring, ma anche migliorie del layout del progetto e ottimizzazioni varie.
Qui il codice sorgente per questa puntata.
* la sintassi "const QString &arg" o "const QStringList &arg" è del tutto equivalente a specificare "QString arg". In genere però è molto meglio specificare il "const" e la "&", per ottimizzare le chiamate. In questo modo infatti i parametri saranno immutabili e passati esplicitamente per riferimento, anzichè ricopiate nel metodo, (occupando quindi più memoria e tempo cpu). Sono piccoli accorgimenti da memorizzare ed usare con costanza, che possono però migliorare di molto le prestazioni del proprio codice.
Sunday, December 11, 2011
Test Driven Development, C++ e Qt: setup e layout progetto
I metodi agili cominciano a prendere piede anche in italia, e con essi quello che è probabilmente il loro più conosciuto cavallo di battaglia: il TDD, ossia Test Driven Development. Uno dei linguaggi in cui il TDD viene più spesso è applicato è java: non solo perchè è uno dei più diffusi, ma probabilmente perchè offre allo sviluppatore degli strumenti veramente potenti: da JUnit, scritto proprio da Kent Beck, ad Eclipse, che oltre ad integrare un perfetto supporto a JUnit offre anche dei potentissimi strumenti per il refactoring, che è parte integrante di questa tecnica.
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
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:
e anche la sua implementazione:
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:
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
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:
Alla prossima puntata!
p.s.: i sorgenti per questa versione del progetto potete trovarmi sul mio account github.
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.
Saturday, November 26, 2011
Leggibilità: meglio fuori che dentro
Un argomento di cui recentemente si discute non poco nel nostro team è l'argomento leggibilità.
Premesso che la leggibilità è sempre un soggetto altamente personale, ciò che per me può essere perfettamente leggibile può non esserlo per altri, la mia opinione è che, dovendo scegliere, è sempre meglio preferire la leggibilità esterna di una classe, piuttosto che quella interna.
La leggibilità interna ha quindi un valore abbastanza relativo, basta cioè che sia sufficientemente leggibile da rispettare principi come avere metodi corti e ben definiti, pochi (meglio niente) cicli for e if statement innestati in cascata (ma piuttosto estratti in metodi), nessuna ambiguità su ingressi e uscite, meglio ancora se con un forte controllo sui tipi (per quanto in java i generics siano molto verbosi, preferisco usarli sempre e comunque quando servono, perchè offrono forti garanzie sulla correttezza dei tipi in uso).
Un esempio concreto: la classe sottostante effettivamente ha una leggibilità interna sufficiente, ma non esaltante.
Le classi inner e l'uso pesante di reflection (purtroppo inevitabile, dato lo scopo di questa classe) sicuramente confondono molto, e anche la gestione delle eccezioni ci mette il suo zampino.
E' anche vero però che la classe principale ha un costruttore privato, e l'unico modo di istanziarla è usando l'unico metodo pubblico disponibile: il metodo "search(enumClass)". Da lì, navigando il codice seguendo lo stack delle chiamate, è possibile seguire il flusso senza perdersi troppo, dato che le biforcazioni non sono molte.
Inoltre i metodi (così come le inner class) sono abbastanza compatti e focalizzati.
Si riesce a capire cosa fa questa classe? (in basso la risposta...)
Certo, come ho già scritto prima la leggibilità interna non è esaltante...
Ma vediamo come viene utilizzata esternamente questa classe, nella fattispecie il suo unit test::
Le assert sono chiare: traducendo letteralmente dall'inglese, abbiamo, mettendo tra parentesi le parole sottointese:
Premesso che la leggibilità è sempre un soggetto altamente personale, ciò che per me può essere perfettamente leggibile può non esserlo per altri, la mia opinione è che, dovendo scegliere, è sempre meglio preferire la leggibilità esterna di una classe, piuttosto che quella interna.
Difatti, in teoria (specie se si vuole rispettare OCP), una classe dovrebbe essere modificata il meno possibile, e se scriviamo del buon design ben orientato agli oggetti è probabile che la classe stessa non sarà modificata mai, ma verranno solo aggiunte nuove implementazioni di interfacce a fronte di nuove funzionalità.![]()
"Meglio fuori che dentro, dico sempre io"
La leggibilità interna ha quindi un valore abbastanza relativo, basta cioè che sia sufficientemente leggibile da rispettare principi come avere metodi corti e ben definiti, pochi (meglio niente) cicli for e if statement innestati in cascata (ma piuttosto estratti in metodi), nessuna ambiguità su ingressi e uscite, meglio ancora se con un forte controllo sui tipi (per quanto in java i generics siano molto verbosi, preferisco usarli sempre e comunque quando servono, perchè offrono forti garanzie sulla correttezza dei tipi in uso).
Un esempio concreto: la classe sottostante effettivamente ha una leggibilità interna sufficiente, ma non esaltante.
Le classi inner e l'uso pesante di reflection (purtroppo inevitabile, dato lo scopo di questa classe) sicuramente confondono molto, e anche la gestione delle eccezioni ci mette il suo zampino.
E' anche vero però che la classe principale ha un costruttore privato, e l'unico modo di istanziarla è usando l'unico metodo pubblico disponibile: il metodo "search(enumClass)". Da lì, navigando il codice seguendo lo stack delle chiamate, è possibile seguire il flusso senza perdersi troppo, dato che le biforcazioni non sono molte.
Inoltre i metodi (così come le inner class) sono abbastanza compatti e focalizzati.
Si riesce a capire cosa fa questa classe? (in basso la risposta...)
package com.gmail.gulino.marco.utils; import static com.google.common.collect.Iterables.find; import static com.google.common.collect.Lists.newArrayList; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import com.google.common.base.Predicate; @SuppressWarnings("unchecked") public class SmartEnums<EnumType extends Enum<EnumType>> { public static <EnumType extends Enum<EnumType>> SmartEnums<EnumType> search(Class<EnumType> enumClass) { return new SmartEnums<EnumType>(enumClass); } private List<EnumType> allValues; private SmartEnums(Class<EnumType> enumClass) { try { Method valuesMethod = enumClass.getMethod("values"); this.allValues = newArrayList((EnumType[]) valuesMethod.invoke(enumClass)); } catch (Exception e) { throw new RuntimeException("Are you trying to apply SmartEnums to a non enum class?"); } } public MemberSearch byField(final String fieldName) { return new MemberSearch() { @Override protected Object findValueOn(EnumType enumType) throws Exception { Field field = enumType.getClass().getDeclaredField(fieldName); field.setAccessible(true); return field.get(enumType); } }; } public MemberSearch byMethod(final String methodName) { return new MemberSearch() { protected Object findValueOn(EnumType enumType) throws Exception { Method method = enumType.getClass().getDeclaredMethod(methodName); method.setAccessible(true); return method.invoke(enumType); } }; } public abstract class MemberSearch { public EnumType havingValue(Object value) { try { return find(allValues, valueFinderPredicate(value)); } catch (Exception e) { throw new RuntimeException(e); } } protected abstract Object findValueOn(EnumType enumType) throws Exception; protected Predicate<EnumType> valueFinderPredicate(final Object value) { return new Predicate<EnumType>() { @Override public boolean apply(EnumType enumType) { try { return value.equals(findValueOn(enumType)); } catch (Exception e) { throw new RuntimeException(e); } } }; } } }
Certo, come ho già scritto prima la leggibilità interna non è esaltante...
Ma vediamo come viene utilizzata esternamente questa classe, nella fattispecie il suo unit test::
package com.gmail.gulino.marco.utils; import static com.gmail.gulino.marco.utils.SmartEnums.search; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import org.junit.Test; public class SmartEnumsTest { @Test public void itShouldRetrieveEnumByField() throws Exception { assertThat(search(AnExampleEnum.class).byField("enumFieldToSearchFor").havingValue("first"), equalTo(AnExampleEnum.firstElement)); assertThat(search(AnExampleEnum.class).byField("enumFieldToSearchFor").havingValue("second"), equalTo(AnExampleEnum.secondElement)); } @Test public void itShouldRetrieveEnumByMethods() throws Exception { assertThat(search(AnExampleEnum.class).byMethod("enumMethodToSearchFor").havingValue("first"), equalTo(AnExampleEnum.firstElement)); assertThat(search(AnExampleEnum.class).byMethod("enumMethodToSearchFor").havingValue("second"), equalTo(AnExampleEnum.secondElement)); } public enum AnExampleEnum { firstElement("first"), secondElement("second"); private final String enumFieldToSearchFor; private AnExampleEnum(String value) { this.enumFieldToSearchFor = value; } public String enumMethodToSearchFor() { return enumFieldToSearchFor; } } }
Le assert sono chiare: traducendo letteralmente dall'inglese, abbiamo, mettendo tra parentesi le parole sottointese:
Asserisci (che la) ricerca (di) AnExampleEnum per field "enumFieldToSearchFor" avente valore "first" (sia) uguale a AnExampleEnum.firstElementLe parole sottointese sono ben poche, è quindi evidente che fa la classe:
Ricerca una enum per field "nome del field" avente valore "valore da ricercare".o, nella seconda delle due interfacce disponibili:
Ricerca una enum per metodo "nome del metodo" avente valore "valore da ricercare".
Ora, il vantaggio evidente è che gli utilizzatori di questa classe non dovranno scervellarsi troppo nel capirla, in effetti non avranno proprio alcun bisogno di aprire l'implementazione e leggerla. Basta solo leggere il suo test (o un eventuale altro utilizzo) per capire cosa fa e come va invocata.
Friday, July 8, 2011
FizzBuzz Game
E dopo anni di silenzio, qualcosa da scrivere ogni tanto torna in mente...
Questa settimana abbiamo iniziato un nuovo progetto con il nostro team in XPeppers, l'azienda con cui collaboro da diversi anni.
La prima settimana l'abbiamo dedicata allo studio, approfondendo vari design pattern e tecniche per la programmazione orientata agli oggetti.
Un esercizio (ormai quasi un classico :P ) proposto dal nostro Coach, Matteo Vaccari, è di implementare il gioco del FizzBuzz. Brevemente, un'applicazione che dato un numero restituisce lo stesso numero; per multipli di certi numeri però sostituisce la stampa del numero con quella di determinate parole preimpostate.
Un esempio è che per multipli di due restituisce "Fizz", per multipli di tre "Buzz". Il numero "6" quindi restituirà la concatenazione, ovvero "FizzBuzz".
Chiaramente un'implementazione molto semplice potrebbe essere quella di creare una serie di blocchi IF per gestire i casi particolari.
Ovviamente non è scalabile, perchè all'aggiunta di nuove regole la serie di blocchi condizionali verrebbe a crescere in maniera sproporzionatamente complessa.
La sfida era quindi di scrivere con un design il più orientato ad oggetti possibile un algoritmo tutto sommato non semplicissimo (ad esempio, l'output è condizionato dalle regole precedenti).
Ecco la mia soluzione.
Il punto d'ingresso è game.java, che prende in costruzione tre oggetti: RulesBuilder, una factory che crea le regole con le giuste dipendenze, OutputBuilder, che si occupa di comporre i risultati delle regole (che sono a loro volta oggetti di tipo OutputAdder e non semplici stringhe) dando loro uno StringBuilder su cui scrivere del testo, e OutputPrinter, un'interfaccia di rendering su cui OutputBuilder stamperà la stringa risultante.
Le regole sono verificate dalla classe DividerRule; in questo caso si tratta di una classe concreta e ben specializzata; aggiungendo nuovi tipi di regole si potrebbe estrarre un'interfaccia comune.
Da notare che DividerRule ha la sola responsabilità di verificare l'applicarsi della condizione, ma non di scrivere o creare l'output; questa responsabilità è delegata alle singole implementazioni di OutputAdder.
Ad esempio, la DividerRule che deve controllare la divisibilità per 3 collaborerà con un AddToWordOutput istanziato per scrivere "Buzz". Lo stampare il numero senza sostituzioni è delegato a un caso particolare di OutputAdder: la DividerRule corrispondente viene sempre verificata, dato che viene istanziata per dividere per 1, mentre l'OutputAdder AddNumberIfOutputIsEmpty controlla che lo StringBuilder sia vuoto (ossia che le regole precedenti non hanno avuto successo) e in tal caso aggiunge il numero.
Altro caso particolare la divisibilità per 7: in questo caso PatatracAdder cancella tutto l'output delle regole precedenti e sostituisce il tutto con "Patatrac".
Il progetto così realizzato cerca di rispettare tutte le principali regole di buona programmazione: anzitutto SRP, dato che ogni classe è altamente specializzata ed ha una sola responsabilità; OCP, infatti per aggiungere nuove regole non serve modificare il codice esistente (a meno di stravolgimenti logici, ovviamente), ma è sufficiente implementare nuovi OutputAdder o eventualmente nuove Rules; Tell, don't ask, nessuna classe ha statement "return", quindi nessun oggetto chiede ad altri oggetti qualcosa, ma piuttosto comanda ai suoi collaboratori di eseguire qualcosa, ignorando i dettagli implementativi; infine l'architettura esagonale, ossia fare in modo che il dominio non abbia alcuna conoscenza del mondo esterno: difatti le classi di dominio non operano nessuna system.out, nè ritornano valori, piuttosto viene usata una generica interfaccia OutputPrinter, oltretutto non implementata se non nei test, e che potrebbe essere sostituita con una classe qualsiasi in grado di stampare su console, su web, su file.
Ovviamente sono anche rispettati i classici pattern come rimozione duplicazione e semplicità logica (pochi IF o cicli).
Sono graditi commenti :)
Questa settimana abbiamo iniziato un nuovo progetto con il nostro team in XPeppers, l'azienda con cui collaboro da diversi anni.
La prima settimana l'abbiamo dedicata allo studio, approfondendo vari design pattern e tecniche per la programmazione orientata agli oggetti.
Un esercizio (ormai quasi un classico :P ) proposto dal nostro Coach, Matteo Vaccari, è di implementare il gioco del FizzBuzz. Brevemente, un'applicazione che dato un numero restituisce lo stesso numero; per multipli di certi numeri però sostituisce la stampa del numero con quella di determinate parole preimpostate.
Un esempio è che per multipli di due restituisce "Fizz", per multipli di tre "Buzz". Il numero "6" quindi restituirà la concatenazione, ovvero "FizzBuzz".
Chiaramente un'implementazione molto semplice potrebbe essere quella di creare una serie di blocchi IF per gestire i casi particolari.
Ovviamente non è scalabile, perchè all'aggiunta di nuove regole la serie di blocchi condizionali verrebbe a crescere in maniera sproporzionatamente complessa.
La sfida era quindi di scrivere con un design il più orientato ad oggetti possibile un algoritmo tutto sommato non semplicissimo (ad esempio, l'output è condizionato dalle regole precedenti).
Ecco la mia soluzione.
Il punto d'ingresso è game.java, che prende in costruzione tre oggetti: RulesBuilder, una factory che crea le regole con le giuste dipendenze, OutputBuilder, che si occupa di comporre i risultati delle regole (che sono a loro volta oggetti di tipo OutputAdder e non semplici stringhe) dando loro uno StringBuilder su cui scrivere del testo, e OutputPrinter, un'interfaccia di rendering su cui OutputBuilder stamperà la stringa risultante.
Le regole sono verificate dalla classe DividerRule; in questo caso si tratta di una classe concreta e ben specializzata; aggiungendo nuovi tipi di regole si potrebbe estrarre un'interfaccia comune.
Da notare che DividerRule ha la sola responsabilità di verificare l'applicarsi della condizione, ma non di scrivere o creare l'output; questa responsabilità è delegata alle singole implementazioni di OutputAdder.
Ad esempio, la DividerRule che deve controllare la divisibilità per 3 collaborerà con un AddToWordOutput istanziato per scrivere "Buzz". Lo stampare il numero senza sostituzioni è delegato a un caso particolare di OutputAdder: la DividerRule corrispondente viene sempre verificata, dato che viene istanziata per dividere per 1, mentre l'OutputAdder AddNumberIfOutputIsEmpty controlla che lo StringBuilder sia vuoto (ossia che le regole precedenti non hanno avuto successo) e in tal caso aggiunge il numero.
Altro caso particolare la divisibilità per 7: in questo caso PatatracAdder cancella tutto l'output delle regole precedenti e sostituisce il tutto con "Patatrac".
Il progetto così realizzato cerca di rispettare tutte le principali regole di buona programmazione: anzitutto SRP, dato che ogni classe è altamente specializzata ed ha una sola responsabilità; OCP, infatti per aggiungere nuove regole non serve modificare il codice esistente (a meno di stravolgimenti logici, ovviamente), ma è sufficiente implementare nuovi OutputAdder o eventualmente nuove Rules; Tell, don't ask, nessuna classe ha statement "return", quindi nessun oggetto chiede ad altri oggetti qualcosa, ma piuttosto comanda ai suoi collaboratori di eseguire qualcosa, ignorando i dettagli implementativi; infine l'architettura esagonale, ossia fare in modo che il dominio non abbia alcuna conoscenza del mondo esterno: difatti le classi di dominio non operano nessuna system.out, nè ritornano valori, piuttosto viene usata una generica interfaccia OutputPrinter, oltretutto non implementata se non nei test, e che potrebbe essere sostituita con una classe qualsiasi in grado di stampare su console, su web, su file.
Ovviamente sono anche rispettati i classici pattern come rimozione duplicazione e semplicità logica (pochi IF o cicli).
Sono graditi commenti :)
Subscribe to:
Posts (Atom)