Archives par mot-clé : Qt

Créer et exécuter des tests cases avec QTestLib

Publié dans C / C++ | Un commentaire

Après avoir soliloqué sur l’architecture de tests Qt / C++ que j’aimerai obtenir, commençons par le commencement : créer un test avec QTestLib et l’exécuter. A vrai dire, cet article traine dans mes brouillons depuis au moins 2014, mais je me suis décidé à le publier. Les liens datent un peu, mais tout reste très fonctionnel.

Un mini rappel sur les tests unitaires :

  • Un test case par classe : ce test case testera les différentes fonctions de cette classe
    • dans des cas nominaux,
    • des cas d’erreurs,
    • voire des cas limites.
  • Tester le plus petit bloc possible : si la classe à tester utilise d’autres composants, on dit qu’elle a des « dépendances », il faut alors « mocker » ces dépendances (si possible) ou en tout cas les maîtriser au maximum. Ce sont les integration tests qui testeront plusieurs blocs ensemble.

Créer un test case

Avec QTestLib, un test case est un QObject classique. La classe de test contient plusieurs slots privés (i.e. « private slots », un slot public ne sera pas exécuté lors du passage des tests) qui seront exécutés les uns à la suite des autres. La documentation de Qt fournit aussi un exemple de test case, mais nous en verrons d’autres plus loin.

Dans chaque test, on va utiliser des assertions, permettant de déterminer si un test est en Success ou Failure. On place dans l’assertion ce que l’on considère comme le bon comportement pour un bout de code selon le contexte donné. En enchaînant plusieurs assertions, on est censé tester un morceau code dans tous les contextes possibles.

  • QVERIFY(bool assessmentExpectedTrue) : « assessmentExpectedTrue » doit être vrai pour que le test soit en Success
  • QVERIFY2(bool assessmentExpectedTrue, char *descriptionIfItFailed) : Idem mais on peut afficher un message de debug si l’assertion est faux
  • QCOMPARE(actual, expected) : Idem, sauf que cette fois-ci la valeur obtenue (actual) et attendue (expected) doivent être égales. L’avantage, c’est que ces valeurs apparaissent dans les logs si le test échoue (ce qui n’est pas le cas avec QVERIFY) et c’est bien pratique pour le debug ! A noter : il faut que l’opérateur « == » et l’opérateur QDebug operator<<(QDebug dbg, T val) soit définie pour les types complexes.

En plus des slots privés qui représentent chacun un test, on peut définir au besoin 4 méthodes particulières dans le test case :

  • initTestCase (static) : exécuté au tout début du test case.
  • cleanupTestCase (static) : exécuté à la toute fin du test case.
  • init : exécuté avant chaque méthode de test. On parle parfois de « set up » ou « préambule ».
  • cleanup : exécuté après chaque méthode de test. On parle parfois de « tear down » ou « postambule »

Au niveau des bonnes pratiques, pour ma part je reprends les notations d’autres moteurs de tests (PHPUnit et JUnit), et donc j’aime bien :

  • préfixer le nom des test cases par « Test ». Pour une classe MyClass → MyClassTest.
  • préfixer le nom des tests (les private slots) par « test ». Ex : « testAdd » et « testAddWithErrors » pour tester la méthode MyClass::add.

Voici un exemple de test case, le header et sa source :

[cpp title="MyClassTest.h"]
#ifndef MYCLASSTEST_H
#define MYCLASSTEST_H

#include <QObject>

class MyClassTest : public QObject
{
  Q_OBJECT
private slots:
  void initTestCase();
  void cleanupTestCase();
  void init();
  void cleanup();

  void testHelloThere();
};

#endif // MYCLASSTEST_H
[/cpp]
[cpp title="MyClassTest.cpp"]
#include "MyClassTest.h"

#include <QDebug>
#include <QTest>

#include "MyClass.h"

void MyClassTest::initTestCase()
{
  qDebug()<<"Test is starting";
}

void MyClassTest::cleanupTestCase()
{
  qDebug()<<"Test is finished";
}

void MyClassTest::init()
{
  qDebug()<<"init";
}

void MyClassTest::cleanup()
{
  qDebug()<<"cleanup";
}

void MyClassTest::testHelloThere()
{
  MyClass myClass;
  QVERIFY("Hello!" == myClass.helloThere());
  QVERIFY2("Hello Martin!" == myClass.helloThere("Martin"), "Martin should have been saluted...");
  QCOMPARE(myClass.helloThere("Riggs"), QStringLiteral("Hello Riggs!"));
}
[/cpp]

Voilà qui est bien beau, mais voyons voir comment exécuter ce bout de test.

Exécuter un test case

Lancer un test case se fait relativement simple via la méthode qExec. Un petit exemple qui se contente d’exécuter le test case « MyClassTest » et d’afficher le résultat :

[cpp]
#include <QDebug>
#include <QTest>

#include "MyClassTest.h"

int main(int argc, char **argv)
{
  MyClassTest testCase;
  int testCaseResult = QTest::qExec(&testCase, argc, argv);
  qDebug()<<"MyClassTest: "<<(0 == testCaseResult ? "Success" : "Failure");
  return testCaseResult;
}
[/cpp]

Voici qui devrait afficher :

********* Start testing of MyClassTest *********

Config: Using QtTest library 5.3.2, Qt 5.3.2

QDEBUG : MyClassTest::initTestCase() Test is starting

PASS : MyClassTest::initTestCase()

QDEBUG : MyClassTest::testHelloThere() init

QDEBUG : MyClassTest::testHelloThere() cleanup

PASS : MyClassTest::testHelloThere()

QDEBUG : MyClassTest::cleanupTestCase() Test is finished

PASS : MyClassTest::cleanupTestCase()

Totals: 3 passed, 0 failed, 0 skipped

********* Finished testing of MyClassTest *********

MyClassTest: Success

Lancer une série de test cases

Allons plus loin en se fabriquant un petit outil, un test suite runner, qui va lancer plusieurs test cases d’un coup. Pour se faire nous allons créer une classe TestRunner. Chaque test case aura un log séparé, et le TesterRunner doit décrire globalement le statut de chaque test case (passed / failure), ainsi que le statut du test suite (passed / failure) c’est-à-dire un « ET » logique sur le résultat de tous les test cases.

[code file="TestRunner.h" language="cpp"]
#ifndef TESTRUNNER_H
#define TESTRUNNER_H

#include <QHash>
#include <QTimer>
#include <QDateTime>
#include <QCoreApplication>
#include <QtTest>
#include "Logger.h"

class TestRunner: public QObject
{
    Q_OBJECT

public:
    Logger logTestsResult;
    TestRunner()  : overallResult(0)
    {
        logTestsResult = Logger("TestRunner", "testsresult_", Logger::DontAddToGeneralLog);
        logTestsResult.setFolder("result");
        logTestsResult.cleanAll();
    }

    void addTest(QString testCaseName, QObject * testCase) {
        testCase-&amp;gt;setParent(this);
        testCaseList.insert(testCaseName, testCase);
    }

    bool runTests() {
        int argc =0;
        char * argv[] = {0};
        QCoreApplication app(argc, argv);
        QTimer::singleShot(0, this, SLOT(run()));
        app.exec();
        return overallResult == 0;
    }

private slots:
    void run() {
        // Launch
        doRunTests();
        // Log overall result
        logTestsResult.i(0 == overallResult ? "Passed" : "Failure");
        // Quit
        QApplication::instance()-&amp;gt;quit();
    }

private:
    void doRunTests() {
        foreach (QString testCaseName, testCaseList.keys()) {
            QStringList outputLogfileCmd;
            outputLogfileCmd<<" "<<"-o"<<logTestsResult.getFolder()+testCaseName+".log";
            int testCaseResult = QTest::qExec(testCaseList.value(testCaseName), outputLogfileCmd);
            logTestsResult.i(testCaseName+": "+(0 == testCaseResult ? "Passed" : "Faillure"));
            overallResult |= testCaseResult;
        }
    }

    QHash<QString, QObject *> testCaseList;
    int overallResult;
};

#endif // TESTRUNNER_H

[/code]
[code file="TestRunner.cpp" language="cpp"]
#include "TestRunner.h"
#include "tst_ExampleTest.h"

#include <QString>

int main() {
 TestRunner testRunner;
 testRunner.addTest("ExampleTest", new ExampleTest());
 testRunner.runTests();
 return 0;
}
[/code]
[code file="tst_ExampleTest.h" language="cpp"]
#ifndef TST_EXAMPLETEST_H
#define TST_EXAMPLETEST_H

#include <QtCore/QString>
#include <QtTest/QtTest>

class ExampleTest: public QObject
{
    Q_OBJECT

public:
    ExampleTest(bool debugMode=false);

private Q_SLOTS:
    void initTestCase();
    void cleanupTestCase();

    void testEx1();

};

#endif // TST_EXAMPLETEST_H
[/code]
[code file="tst_ExampleTest.cpp" language="cpp"]
#include "tst_ExampleTest.h"

ExampleTest::ExampleTest()
{
}

void ExampleTest::initTestCase()
{
}

void ExampleTest::cleanupTestCase()
{
}

void ExampleTest::testEx1()
{
    QVERIFY2(true, "Should be true");
}
[/code]

L’environnement de tests de mes rêves

Voici ma liste personnelle du test suite runner de mes rêves en C++/Qt :

  • Pas de TestRunner à créer, juste des classes de tests (héritant d’un type spécifique, ou au pire s’inscrivant quelque part)
  • Lancement des tests unitaires au choix lors de la compilation (grâce à une config ou un define)
  • Résultat des tests (par test case et résultat global) dans des fichiers de logs, et/ou au choix dans stdout (coloré dans ce cas). En utilisant exclusivement qDebug bien sûr, ou stdout, pour ne pas inclure de bibliothèque supplémentaire.
  • Fonction de setup/teardown par test case, et avant chaque test
  • Supporter les fixtures (les quoi ? cf l’excellent article Increase your QTest productivity)
  • Tester les signaux / slots Qt avec QSignalSpy (outil indispensable pour les tests unitaires Qt !)
  • Ajouter une librairie pour gérer facilement les stub / mock

La bonne nouvelle c’est que j’ai partiellement atteint ce rêve (ohoh) avec un jeu de classes TestRunner et IUnitTest, empaqueté dans un package Qompoter appelé autotest pour l’utiliser facilement. C’est ce que l’on va voir au prochain épisode 🙂

Plus d’informations

Plus de folie avec les QThread

Publié dans C / C++ | Laisser un commentaire

La programmation avec des threads est complexe et ne doit jamais être prise à la légère. Tête froide, mutex et variables atomiques sont de rigueur si on veut s’éviter les affres de l’horreur.

Qt peut nous simplifier la vie. Il peut nous y aider en tout cas. Mais. Oui il y a un mais. En regardant la documentation disponible sur Internet, c’est le bazar car : il y a bien longtemps, entre Qt 4.4 et Qt 4.7, on utilisait les mêmes noms de méthodes (run) pour quelque chose de différent ! Du coup, même aujourd’hui on trouve de tout sur le Web.

Voici quelques conseils en vrac avant de pointer vers de la documentation intéressante.

Conseils en vrac

  • Si cela ne vous parle pas, allez regarder les définitions de thread-safe et re-entrant. En fait, renseignez-vous sur les threads avant d’utiliser les QThreads, vous gagnerez du temps.
  • Un QObject appartient au thread dans lequel il est créé, ou dans lequel il est déplacé (mais dans ce cas, il ne peut avoir de parents)
    • Corolaire : les enfants d’un QObject vivent dans le même thread que leur parent. Mais attention, les variables membres ne deviennent pas automatiquement enfant de leur QObject ! (bigre)
  • Si la notion de QEventLoop Qt ne vous parle pas, renseignez-vous !
  • De manière assez logique, on peut appeler tranquillement un QObject via plusieurs thread, donc la même fonction d’une même classe peut être appelée par différents threads. En général on évite. Pour faire de la communication inter-thread, le plus safe est d’utiliser les signaux / slots en mode QueueConnection (mode par défaut dans ce cas). Si le thread A émet un signal variableChanged(int), ce signal peut être catché par une classe dans le thread principal via un slot handleVariableChanged(int). Voir QueuedConnection
  • En fait, si on ne fait pas gaffe, notamment si on n’utilise pas les signaux / slots, ou si on utilise le mode DirectConnection, on peut sans se rendre compte avoir plusieurs threads qui modifient en même temps la même donnée. Le mécanisme de protection de base, c’est les mutex (QMutexLocker et QMutex sont vos amis). Mais on peut aussi jouer avec les variables atomiques. Plus difficile, pas toujours possible, mais indispensable si on veut éviter de manière fiable les interlocks.
  • Pour utiliser les signaux / slots entre thread, il faut au préalable s’assurer que les paramètres des signaux soient bien enregistrés avec Q_DECLARE_METATYPE(Type) et qRegisterMetaType<Type>(). Sans cela, en QueuedConnection le slot ne serait juste pas appelé (dommage, try again).
  • D’une manière générale, je préconise d’utiliser QConcurrent et QFutureWatcher qui encapsule le comportement des QThreads. C’est mieux, plus simple et presque toujours possible. Je ne suis pas le seul à le préconiser, c’est la doc Qt qui le dit.
  • Si on doit utiliser directement QThread, alors la lecture de la doc Qt et des articles de Maya et FabienPN est fortement encourager.

Continuer la lecture

Structure d’un projet Qt

Publié dans C / C++ | Un commentaire

Note: c’est un brouillon 🙂

Ma grande question du moment, comment architecture un projet Qt pour :

  • avoir une bonne structure de dossier,
  • pouvoir gérer plusieurs modules, et choisir ceux à utiliser lors de la compilation,
  • séparer le code source du code des tests unitaires,
  • choisir lors de la compilation de lancer les tests ou non.

Structure

src
   module1/
      src/
      test/
      module1.pro
   module2/
      src/
      test/
      module2.pro
   communication
   data
   service
   util
   ui/
      project.cpp
      project.h
      project.ui
      ui.pro
   main.cpp
   src.pro
test/
   testcase/
      MyGreatTest.cpp
      MyGreatTest.h
   test.pro
   IUnitTestCase.h
   TestRunner.cpp
   TestRunner.h
vendor/
   ...
   vendor.pri
.pro

En général, j’ai plusieurs projets liés entre eux qui partagent des fichiers. Si on passe donc un cran en dessous, j’ai quelque chose du style :

project1/
project2/
vendor/
   ...
   vendor.pri
release/

Quelques informations sur les les .pro et les .pri

Ah, et il est normal qu’un message s’affiche 3 fois dans les logs (lorsqu’on utilise les commandes « message » ou « system »), puisqu’il y a 3 Makefile à générer, donc le fichier .pro est consulté 3 fois.

Inspiration

Installer Qt pour développer en 32bits et 64bits

Publié dans C / C++ | Un commentaire

Comment installer plusieurs versions de Qt en même temps, et surtout, comment gérer une cross-compilation 32bits / 64bits ? Voilà un problème qui peut prendre des heures à régler… C’est possible, autant sous Windows que sous Linux.

Pour ma part, je compile sous Debian testing (jessie) pour Qt5.3 ou Qt5.4 en 32bits ou 64bits, et grâce à Wine, je compile aussi pour Windows pour Qt5.1, Qt5.3 ou  Qt5.4 en 64bits. Je garde toujours un Qt5.1 sous Windows car c’est la dernière version à fonctionner convenablement pour Windows XP.

Compiler est une chose, et je ne m’y attarderai pas trop dans ce billet, mais installer est la première étape. La méthode 1 ne fonctionne que sous Linux (et ne marche pas d’ailleurs). La méthode 2 conviendra à Windows et Linux.

Continuer la lecture

Etude – Lire un document Word, OpenDocument, PDF avec Qt (ou autre)

Publié dans C / C++ | Laisser un commentaire

Lire un document Office, OpenDocument ou PDF, ou autre format riche  programmatiquement en Qt / C++ / autre… comme il fallait s’y attendre, ce n’est pas de la tarte ! Apparemment, il est relativement faisable d’afficher un document PDF, Office ou LibreOffice directement via un objet Qt, ou une bibliothèque. Mais ici, je cherche à lire le contenu, afin de pouvoir en extraire des morceaux suivant un patron (vous l’aurez deviné, c’est un document de norme, dont j’aimerai extraire la liste des exigences ><).

Voici une liste des ressources que j’ai pu trouver sur le sujet. Je n’ai rien testé pour l’instant.

Continuer la lecture

Corriger une string JSON en Qt

Publié dans Snippet | Laisser un commentaire

La méthode ci-dessous permet d’autoriser une syntaxe moins rigoureuse en prenant soin de modifier les petites erreurs qui ont pu être commises.

/**
 * Clean a string
 * * Remove comments ("//" and "/*")
 * * Add first and last braces ("{" and "}") if missing
 * * Remove unauthorized commas
 *
 * @param data Almost JSON string that will be cleaned to become a valid JSON string
 * @return Valid JSON string (or at least, a more valid one)
 */
QString cleanJsonString(QString data)
{
    // Remove inline comms
    QRegExp pattern = QRegExp("(^|\\[|\\{|,|\\n|\\s)//.*($|\\n)");
    pattern.setMinimal(true); //ungreedy
    data.replace(pattern, "\\1\n");
    data.replace(pattern, "\\1\n"); //2 times, I am not sure why...
    // Remove bloc comms
    pattern = QRegExp("/\\*.*\\*/");
    pattern.setMinimal(true); //ungreedy
    data.replace(pattern, "");
    // Add first and last brace
    if (!data.startsWith("{")) {
        data = "{\n"+data;
    }
    if (!data.endsWith("}")) {
        data += "\n}";
    }
    // Remove commas before } or ]
    pattern = QRegExp(",(\\s*[}\\]])");
    pattern.setMinimal(true); //non-greedy
    data.replace(pattern, "\\1");
    return data;
}

J’ai commis une méthode similaire en PHP : Manipuler du JSON en PHP.

Attendre la fin d’un signal Qt de manière synchrone

Publié dans Snippet | Laisser un commentaire

Ou comment attendre la fin d’une méthode asynchrone.

Snippet

/**
 * Wait synchronously for a Qt signal (+ timeout management)
 * May be useful for test purpose
 * Not recommended for production usage
 */

// -- Configure the waiting
// Signal to wait
QEventLoop loop;
QObject::connect(&anObject, SIGNAL(signalToWait()), &loop, SLOT(quit()));
// Timeout to avoid infite waiting
QTimer timer;
timer.setInterval(10*1000); //10s
timer.setSingleShot(true);
QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));

// -- Do stuff that should trigger the signal
// ...

// -- Start to wait
// What is done after "loop.exec()" is not executed until "loop.quit()" is called
timer.start();
loop.exec();
timer.stop(); // Just in case there is no timeout

// -- Other stuff
// ...

Dessiner des graphiques avec Qt

Publié dans C / C++ | Laisser un commentaire

Dessiner des courbes, des graphiques, des histogrammes, c’est relativement faisable avec Qt. Il « suffit » d’utiliser la classe QGraphicsScene. C’est en réalité bien compliqué. Pour permettre la réutilisation (ce qui est souhaitable dans ce cas afin d’éviter les cheveux blancs précoces), il est préférable d’étendre cette classe, et d’y ajouter une méthode « addCurve » auquel il faut fournir tout ce qui est nécessaire pour dessiner une courbe (au sens mathématiques du terme ou non) : une liste de points, une formule… Une autre méthode « addAxes » pourrait permettre de dessiner les deux axes.

Bref, franchement, j’en suis à la quatrième itération d’une classe appelée CurveGraphicScene, et ce n’est pas la panacée. Oh que non ! Quand j’aurai quelque chose d’un peu plus compréhensible, je verrai ce que je peux en dire ici.
En attendant, je note deux bibliothèques Qt : Qwt et QCustomPlot. Elles permettent de faire ce genre de choses, avec normalement moins de peines. Je n’ai pu encore les tester, soit à cause d’une difficulté d’installation, un manque de documentation (et donc la crainte d’y perdre beaucoup de temps), ou à cause des conditions d’utilisation non compatibles avec mes projets du moment.

Continuer la lecture

Qt: No matching function for call to connect

Publié dans C / C++ | Un commentaire

Qt permet de relier facilement entre elle des classes héritant de QObject avec des liens signaux / slots. Voici un petit exemple de connexion.

class OtherQObject : public QObject
{
Q_OBJECT
public:
    OtherQObject();

signal:
    void aSuperSignal();
};
class MyQObject : public QObject
{
Q_OBJECT
public:
    MyQObject();

public slots:
    void myGreatMethod();
};
MyQObject *myClass;
OtherQObject *otherClass;
connect(otherClass, SIGNAL(aSuperSignal()), myClass, SLOT(myGreatMethod());

A de nombreuses reprises, il m’est arrivé d’être confronté à l’erreur suivante (ou similaire) : « No matching function for call to connect ». Plus précisément :

error: no matching function for call to ‘mainWindow::connect(MyQObject *&, const char [38], OtherQObject *&, const char [30])’
note: candidates are: static bool QObject::connect(const QObject*, const char*, const QObject*, const char*, Qt::ConnectionType)
note: bool QObject::connect(const QObject*, const char*, const char*, Qt::Connection

J’aimerai lister ici quelques causes possibles de ce type d’erreur. Un article sans fioriture, avec l’information brute de chez brute, mais qui sait, peut-être aurais-je un jour l’envi de peaufiner un petit peu ce bric à brac ?

Continuer la lecture

Les pointeurs intelligents C++ avec Qt

Publié dans C / C++ | Laisser un commentaire

J’entends parler de pointeurs intelligents (ou smart pointers) en C++ depuis pas mal de temps. N’étant pas encore tout à fait à l’aise avec cette notion, je me suis fait une petit séance d’état de l’art. Je compte utiliser des shared pointers tout bientôt, ce qui devrait me permettre d’acquérir d’avantage d’expérience sur le sujet.

Oh le joli pointeur intelligent !

Oh le joli pointeur intelligent !

L’idée qui se cache derrière, c’est de faciliter la gestion mémoire, et d’éviter les fuites mémoires par la même occasion. Je ne sais pas quand l’idée est apparue, mais pour bien comprendre l’état actuel de ce domaine, il faut savoir que les premières implémentations n’ont pas tout de suite été disponibles nativement dans le langage C++. La librairie Boost propose différentes sortes de pointeurs intelligents, tout comme le framework Qt. Il en existe d’autres bien assurément.
Je crois qu’aujourd’hui depuis C++11 (??? à vérifier), trois types de smart pointers sont disponibles nativement.
Continuer la lecture