web-dev-qa-db-ja.com

Qtにはどのユニットテストフレームワークを使用すればよいですか?

いくつかのクロスプラットフォームGUIを必要とする新しいプロジェクトを始めたばかりで、GUIフレームワークとしてQtを選択しました。

単体テストフレームワークも必要です。約1年前までは、C++プロジェクトに社内開発の単体テストフレームワークを使用していましたが、現在は新しいプロジェクトにGoogle Testを使用するように移行しています。

QtアプリケーションにGoogle Testを使用した経験はありますか? QtTest/QTestLibはより良い代替品ですか?

プロジェクトの非GUI部分でQtをどの程度使用するかはまだわかりません。おそらく、QtベースのGUIへの小さなインターフェイスを備えたコアコードでSTL/Boostを使用することをお勧めします。

編集:多くはQtTestに傾いているようです。これを継続的な統合サーバーと統合した経験のある人はいますか?また、新しいテストケースごとに個別のアプリケーションを処理する必要があると、大きな摩擦が発生するように思えます。それを解決する良い方法はありますか? Qt Creatorはそのようなテストケースを処理する良い方法を持っていますか、またはテストケースごとにプロジェクトが必要ですか?

46
Rasmus Faber

QTestLibが他のフレームワークよりも一般的な意味で「優れている」ことを知りません。それがうまくいく点が1つあり、それはQtベースのアプリケーションをテストするための良い方法を提供します。

QTestを新しいGoogle Testベースのセットアップに統合できます。私は試していませんが、QTestLibがどのように設計されているかに基づいて、複雑すぎないようです。

純粋なQTestLibで記述されたテストには、継続的な統合サーバーに必要な形式に変換するためのいくつかのXSLT変換とともに使用できる-xmlオプションがあります。ただし、その多くは、使用するCIサーバーによって異なります。同じことがGTestにも当てはまると思います。

テストケースごとに1つのテストアプリを使用しても、大きな問題は発生しませんでしたが、それは、ビルドとテストケースの実行を適切に管理するビルドシステムがあるかどうかにかかっています。

Qt Creatorでテストケースごとに個別のプロジェクトを必要とすることは何も知りませんが、前回Qt Creatorを見たときから変更されている可能性があります。

また、QtCoreを使い続け、STLから離れることをお勧めします。全体でQtCoreを使用すると、Qtデータ型を必要とするGUIビットの処理が容易になります。その場合、1つのデータ型から別のデータ型への変換について心配する必要はありません。

18
mattr-

別のテストアプリケーションを作成する必要はありません。次のような独立したmain()関数でqExecを使用するだけです。

int main(int argc, char *argv[])
{
    TestClass1 test1;
    QTest::qExec(&test1, argc, argv);

    TestClass2 test2;
    QTest::qExec(&test2, argc, argv);

    // ...

    return 0;
}

これにより、各クラスのすべてのテストメソッドが1つのバッチで実行されます。

Testclass .hファイルは次のようになります。

class TestClass1 : public QObject
{
Q_OBJECT

private slots:
    void testMethod1();
    // ...
}

残念ながら、このセットアップは、多くの人にとって非常に役立つように思われるかもしれませんが、Qtのドキュメントでは実際には十分に説明されていません。

38
Joe

ジョーの答えに追加します。

以下は、私が使用する小さなヘッダー(testrunner.h)で、イベントループを生成するユーティリティクラス(たとえば、キュ​​ーに入れられたシグナルスロット接続とデータベースをテストするために必要)とQTest互換クラスを「実行」しています。

#ifndef TESTRUNNER_H
#define TESTRUNNER_H

#include <QList>
#include <QTimer>
#include <QCoreApplication>
#include <QtTest>

class TestRunner: public QObject
{
    Q_OBJECT

public:
    TestRunner()
        : m_overallResult(0)
    {}

    void addTest(QObject * test) {
        test->setParent(this);
        m_tests.append(test);
    }

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

        return m_overallResult == 0;
    }
private slots:
    void run() {
        doRunTests();
        QCoreApplication::instance()->quit();
    }
private:
    void doRunTests() {
        foreach (QObject * test, m_tests) {
            m_overallResult|= QTest::qExec(test);
        }
    }

    QList<QObject *> m_tests;
    int m_overallResult;
};

#endif // TESTRUNNER_H

次のように使用します。

#include "testrunner.h"
#include "..." // header for your QTest compatible class here

#include <QDebug>

int main() {
    TestRunner testRunner;
    testRunner.addTest(new ...()); //your QTest compatible class here

    qDebug() << "Overall result: " << (testRunner.runTests()?"PASS":"FAIL");

    return 0;
}
19
mlvljr

アプリでQtTestを使い始めたところ、非常にすぐに、QtTestの制限に直面し始めました。主な問題は次の2つです。

1)私のテストは非常に速く実行されます-実行可能ファイルのロード、Q(Core)Application(必要な場合)のセットアップなどのオーバーヘッドがテスト自体の実行時間をしばしば短縮します!各実行可能ファイルをリンクするのにも時間がかかります。

クラスが追加されるにつれてオーバーヘッドは増加し続け、すぐに問題になりました-単体テストの目標の1つは、非常に高速で動作するセーフティネットを用意することです。急速にそうではなくなります。解決策は、複数のテストスイートを1つの実行可能ファイルにグロブすることです。(上記のように)これはほとんど実行可能ですが、 サポートされていません であり、重要な制限があります。

2)フィクスチャのサポートはありません-私にとっては契約違反です。

しばらくしてから、Google Testに切り替えました-これははるかに機能的で洗練された単体テストフレームワークであり(特にGoogle Mockで使用した場合)、1)および2)を解決します。さらに、便利なQTestLib機能を簡単に使用できますQSignalSpyやGUIイベントのシミュレーションなどです。切り替えるのは少し面倒でしたが、ありがたいことに、プロジェクトはそれほど進んでおらず、変更の多くは自動化できました。

個人的には、今後のプロジェクトではGoogle TestではなくQtTestを使用しません-目に見える実際の利点がなく、重要な欠点がある場合。

19
SSJ_GZ

Qtに含まれる単体テストフレームワークを使用しないのはなぜですか?例: QtTestLib Tutorial

6

私はgtestと QSignalSpy を使用してライブラリを単体テストしました。 QSignalSpyを使用してシグナルをキャッチします。スロットを直接(通常のメソッドと同様に)呼び出してテストすることができます。

4
BЈовић

MlvljrとJoeのソリューションを拡張するために、1つのテストクラスごとに完全なQtTestオプションをサポートし、すべてをバッチとロギングで実行することもできます。

usage: 
  help:                                        "TestSuite.exe -help"
  run all test classes (with logging):         "TestSuite.exe"
  print all test classes:                      "TestSuite.exe -classes"
  run one test class with QtTest parameters:   "TestSuite.exe testClass [options] [testfunctions[:testdata]]...

ヘッダ

#ifndef TESTRUNNER_H
#define TESTRUNNER_H

#include <QList>
#include <QTimer>
#include <QCoreApplication>
#include <QtTest>
#include <QStringBuilder>

/*
Taken from https://stackoverflow.com/questions/1524390/what-unit-testing-framework-should-i-use-for-qt
BEWARE: there are some concerns doing so, see  https://bugreports.qt.io/browse/QTBUG-23067
*/
class TestRunner : public QObject
{
   Q_OBJECT

public:
   TestRunner() : m_overallResult(0) 
   {
      QDir dir;
      if (!dir.exists(mTestLogFolder))
      {
         if (!dir.mkdir(mTestLogFolder))
            qFatal("Cannot create folder %s", mTestLogFolder);
      }
   }

   void addTest(QObject * test)
   {
      test->setParent(this);
      m_tests.append(test);
   }

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

      return m_overallResult == 0;
   }

   private slots:
   void run() 
   {
      doRunTests();
      QCoreApplication::instance()->quit();
   }

private:
   void doRunTests() 
   {
      // BEWARE: we assume either no command line parameters or evaluate first parameter ourselves
      // usage: 
      //    help:                                        "TestSuite.exe -help"
      //    run all test classes (with logging):         "TestSuite.exe"
      //    print all test classes:                      "TestSuite.exe -classes"
      //    run one test class with QtTest parameters:   "TestSuite.exe testClass [options] [testfunctions[:testdata]]...
      if (QCoreApplication::arguments().size() > 1 && QCoreApplication::arguments()[1] == "-help")
      {
         qDebug() << "Usage:";
         qDebug().noquote() << "run all test classes (with logging):\t\t" << qAppName();
         qDebug().noquote() << "print all test classes:\t\t\t\t" << qAppName() << "-classes";
         qDebug().noquote() << "run one test class with QtTest parameters:\t" << qAppName() << "testClass [options][testfunctions[:testdata]]...";
         qDebug().noquote() << "get more help for running one test class:\t" << qAppName() << "testClass -help";
         exit(0);
      }

      foreach(QObject * test, m_tests)
      {
         QStringList arguments;
         QString testName = test->metaObject()->className();

         if (QCoreApplication::arguments().size() > 1)
         {
            if (QCoreApplication::arguments()[1] == "-classes")
            {
               // only print test classes
               qDebug().noquote() << testName;
               continue;
            }
            else
               if (QCoreApplication::arguments()[1] != testName)
               {
                  continue;
               }
               else
               {
                  arguments = QCoreApplication::arguments();
                  arguments.removeAt(1);
               }
         }
         else
         {
            arguments.append(QCoreApplication::arguments()[0]);
            // log to console
            arguments.append("-o"); arguments.append("-,txt");
            // log to file as TXT
            arguments.append("-o"); arguments.append(mTestLogFolder % "/" % testName % ".log,txt");
            // log to file as XML
            arguments.append("-o"); arguments.append(mTestLogFolder % "/" % testName % ".xml,xunitxml");
         }
         m_overallResult |= QTest::qExec(test, arguments);
      }
   }

   QList<QObject *> m_tests;
   int m_overallResult;
   const QString mTestLogFolder = "testLogs";
};

#endif // TESTRUNNER_H

自分のコード

#include "testrunner.h"
#include "test1" 
...

#include <QDebug>

int main(int argc, char * argv[]) 
{
    TestRunner testRunner;

    //your QTest compatible class here
    testRunner.addTest(new Test1);
    testRunner.addTest(new Test2);
    ...

    bool pass = testRunner.runTests(argc, argv);
    qDebug() << "Overall result: " << (pass ? "PASS" : "FAIL");

    return pass?0:1;
}
3
j.holetzeck

QtTestは主に、Qtイベントループ/シグナルディスパッチを必要とするパーツをテストするのに役立ちます。各テストケースに個別の実行可能ファイルが必要になるように設計されているため、残りのアプリケーションで使用される既存のテストフレームワークと競合しないようにする必要があります。

(ところで、アプリケーションのGUI以外の部分でもQtCoreを使用することを強くお勧めします。これを使用する方がずっと便利です。)

3

Qtを使用している場合は、UIをテストする機能があり、簡単に使用できるため、QtTestの使用をお勧めします。

QtCoreを使用している場合は、STLがなくても実行できます。 QtクラスはSTLのクラスより使いやすいことがよくあります。

2
Gunther Piez

私はこれをいじっています。 QtTestではなくGoogle Testを使用する主な利点は、すべてのUI開発をVisual Studioで行うことです。 Visual Studio 2012を使用して Google Test Adapter をインストールすると、VSでテストを認識し、テストエクスプローラーに含めることができます。これは、開発者がコードを書くときに使用できるので便利です。GoogleTestは移植可能であるため、Linuxビルドの最後にテストを追加することもできます。

NCrunchGiles および ContinuousTests

もちろん、誰かがVS2012用の別のアダプターを作成して、QtTestサポートをテストアダプターに追加する場合があります。その場合、この利点はなくなります。誰かがこれに興味を持っているなら、良いブログ投稿があります 新しいVisual Studio単体テストアダプター を作成します。

1
parsley72

QtTestフレームワークでのVisual Studioテストアダプターツールのサポートには、次のVisual Studio拡張機能を使用します。 https://visualstudiogallery.msdn.Microsoft.com/cc1fcd27-4e58-4663-951f-fb02d9ff365

0
user3395798