Qtでのプログラムの機能テスト

まえがき



バージョン管理システムに最後の修正を行った後でも、重要なアプリケーションシナリオが引き続き正常に機能することを確認する唯一の方法は、テストシステムでこれらのスクリプトを取得して実行することです。 手作業で行うのは長く、退屈で、エラーがたくさんあります。







上記のすべてと、TORの顧客が同じTKで指定された機能要件の自動テストの必要性を次のプロジェクトの開始時に指定した「重要でない」事実を考慮すると、GUIテストを自動化するツールを選択する問題が関連するようになりました。 プロジェクトはQt上にあり、クロスプラットフォーム(Windows、Linux)が必要でした。







最終的にオープンソースツールが登場したのは猫です。







画像







GUIテストツール



当時(数年前)、どのような既製のGUIテストソリューションが利用できましたか?







要約すると、可能なユーティリティには2つのクラスがありました。







  1. テスト用ではなく、ユーザーアクションを自動化するために最初に作成されたプログラム。

    誰もがお気に入りのOSの名前をカップルに付けることができると思います。 たとえば、Linux / X11の場合- ハブの投稿を参照してください







    これらのユーティリティはいずれも、記載されている要件の少なくとも1つを満たしていないため、私たちに適していません。







    1. クロスプラットフォーム。

      それらのほとんどはクロスプラットフォームではありません。 Windowsのみ、またはLinuxのみで動作します。







    2. 実装の詳細への過度のバインド。

      クロスプラットフォームの問題を解決したとしても(たとえば、Linuxマシンでプログラムを実行し、WindowsでXサーバーを実行するなど)、実装の詳細への過剰なバインドは、次のような問題を引き起こします。


    最も単純なユーティリティは、画面の座標系(SC)でのマウスクリック(つまり、異なる画面解像度-およびテストクラッシュ)、スマートに使用されたSCウィンドウ(異なるQtスタイル、異なるDPI-およびテストクラッシュ)を記録および再現しました。 最高は「ネイティブ」OSウィジェットを認識できましたが、残念ながら、Qtは特定のOSの「ネイティブ」要素をほとんど使用しませんでした(Qtは特定のOS、この場合LinuxまたはWindowsのウィジェットの外観をマスクしますが、内部は同じQWidgetです) 。







    結論:このクラスの単一のユーティリティは私たちに適していません。 同じユーザーシナリオ-LinuxとWindowsの2つのテストケースを作成する必要があります。 さらに、それらを使用して作成されたテストをサポートすることは非常に困難です(インターフェイスの変更はそれらを破壊します)。







  2. テスト専用に作成されたプログラム。 経験(1)を考慮すると、このクラスのユーティリティから、Qtをサポートするユーティリティのみを検討しました。 これは正確に1つでした-スキッシュ( ハブに関する説明 )と価格設定への適切なアプローチ-「お問い合わせください。評価し、価格を設定します。」 それは数年前でしたが、おそらく何かが変わったのかもしれません。 私は彼らにリクエストを送りましたが、答えを待ちませんでした。


結果は論理的です-そのようなツールを自分で作ることが決定されました。







Qt Monkey



アルファ版


最初は、タスクは非常に単純に見えました。 特性名がQTestの Qtサブシステムがあります(自分のウィジェットの単体テストを作成するプロジェクトで既に使用しています)。 キーストロークとマウスクリックのシーケンスを記録するのは非常に簡単です( QTest::mouseClick



QTest::keyCick



)。 テストコードは、 QEvent



> QTest::something



でテスト対象のアプリケーションのすべてのイベントをレポートするようにQtに要求した後、 QEvent



> QTest::something



変換を使用してQTest::something



できます。 その結果、予備バージョンはすぐに準備できました。







確かに、何らかの理由でプラグインメカニズムを使用してテストをロードし、テスト自体をC ++で記述しても、QAエンジニアの間で理解を深めることはできませんでした。 幸いなことに、QtにはJavaScriptをアプリケーションに簡単に埋め込む方法があります-QtScriptです。 このサブシステムを使用すると、わずか数行のコードで非常に簡単にQObject



とJavaScriptの相続人の相互作用を確保し、双方向で呼び出しをブロードキャストできます。







 QScriptEngine engine; QScriptValue global = engine.globalObject(); QScriptValue val = engine.newQObject(qobject); global.setProperty(QLatin1String("myobject"), val);
      
      





およびJavaScriptで:







 myobject.slot1(); var v = myobject.property1;
      
      





プロパティ( Q_PROPERTY



)が今まで作成されたプログラムのすべてのグラフィック要素へのポインターであるオブジェクト( QObject



)を作成することは非常に難しいため、ウィジェットを識別する方法を理解することだけが残っています。







熟考した後、このウィジェットの命名スキームに決めました。







「親識別子(ある場合)」ポイント「特定のオブジェクトの識別子」

「親ID(存在する場合)」が再びペアリングされる場所

「親の親識別子(存在する場合)」ドット「特定の親識別子」 。 そのため、 QObject::parent



nullptr



はありませんを返します。







特定のオブジェクトの識別子は、オブジェクトの名前(最も単純な場合(Qt Designerが使用されている場合、オブジェクトの名前が存在する))のいずれかです。オブジェクトに名前がない場合、クラス名とシリアル番号を使用して識別します。







例:







MainWindow.centralwidget.tabWidget.qt_tabwidget_stackedwidget.tab.pushButton_ModalDialog


ベータ版


すべてが素晴らしく、テストは簡単に作成でき、変更することなく両方のプラットフォームで動作します(要素のピクセルごとの配置に関連付けられていないため)。 しかし、すべてがそれほど単純ではないことが判明しました。 モーダルダイアログが有効になりました(ファイルオープンダイアログQMessageBox



、Yes / NoのあるQMessageBox



も問題の原因になります)。







エラーは次のとおりです。









したがって、 QTest::-_



からのコントロールは、ダイアログが閉じるまで戻りません。 しかし、ダイアログが閉じるまで制御が戻らない場合、スクリプトはどのように閉じることができますか?







この状況は、 QTest::-_



ブロッキング動作が、もちろん、ダイアログを呼び出すために必要QTest::-_



であるという事実によって悪化しました。 あらゆる種類のチェックをテストに入れることは、行の後に正確に何があるかを知っていれば、はるかに簡単です

Test.activateItem('MainWindow.centralwidget.tabWidget.qt_tabwidget_tabbar', 'Tab 5');



タブ'Tab 5'



タブがアクティブになります。たとえば、5行後ではありません。







もちろん、回避できないブロッキング呼び出しがある場合、明らかな解決策は別のスレッドを作成することですが、残念ながら、GUI関連のイベントはメインスレッドでのみ処理する必要があります。 したがって、最初の解決策は次のとおりです(擬似コード)。







 //addition thread qApp->postEvent(objectInGuiThread, customEventObject); semaphore->tryAcquire(timeout); //gui thread void ClassInGuiThread::customEvent() { QTest::somthing(); semaphore->release(); }
      
      





つまり QEvent



を使用して、メイン(GUI)ストリームにあるオブジェクトに何かする必要があることを通知します。 objectInGuiThread



はGUIスレッドにあるため、その::customEvent



メソッドはGUIスレッドのコンテキストで呼び出され、スレッドはセマフォを使用して同期されます。







実際、シグナルが1つのストリームから送信され、スロットを所有するオブジェクトが別のストリームにある場合、 Qt::QueuedConnection



QObject::connect



してQObject::connect



を呼び出すとき、シグナル/スロットメカニズムは同様に機能します。








明らかな欠点は、 semaphore->tryAcquire(timeout);



呼び出しで適切なタイムアウトを選択する必要があることですsemaphore->tryAcquire(timeout);



。 たとえば、ウィジェットをクリックすると長い操作が発生し、タイムアウトが発生した場合、その後、たとえば長い操作の完了後にのみ表示されるウィジェットをクリックして作業を続けることで、テストの作成者にとって予期しない結果になる可能性があります。







悲しみは、 QCoreApplication::loopLevel()



からQt 4に切り替えるときにQCoreApplication::loopLevel()



廃止QCoreApplication::loopLevel()



れ、qt3supportオプションでQt 4.xをビルドするときにのみ利用可能であり、そのアナログQThread::loopLevel()



のみが返されたという事実によっても発生しますQt 5.5で。 つまり 「押す->長い操作」の場合と「押す->モーダルダイアログ」の場合を区別することは困難です。







このコードの明らかな欠点は、2つの連続したイベントを処理する方法です。1つ目はモーダルウィンドウを閉じ、2つ目はキーストロークをシミュレートします。 この場合、 QDialog::exec



内で作成されたQEventLoop



を閉じることQDialog::exec



(少なくともglibを使用したQAbstractEventDispatcher



の実装では)瞬時でQDialog::exec



ないため、 QEventLoop



クラスQEventLoop



分類され、呼び出しません、メインウィンドウで対応するQAction



をトリガーします。







したがって、モーダルダイアログを処理するための最後のオプションは非常に複雑であることが判明しました。







  1. 現在のモーダルウィジェットqApp->activeModalWidget()



    取得します。このメソッドはスレッドセーフとしてマークされていないため、コードは別のスレッドで呼び出されます。







  2. GUIスレッドで目的のクリック/クリックを実行するように求めるメッセージを送信します。







  3. 2)からのメッセージがqApp->sendPostedEvents



    ことをqApp->sendPostedEvents



    で確認します

    処理が開始されました。







  4. 1)を繰り返し、結果を比較します。







  5. 新しいモデルウィジェットが表示されない場合、完了を待っています2)タイムアウトなし、そうでない場合はユーザー指定のタイムアウト(デフォルトでは5秒)のみ。


このアルゴリズムにも問題があります(新しいダイアログが5秒以上「表示」または「表示」された場合)が、この場合、JavaScriptを使用して、より長いタイムアウトを設定したり、何らかの方法で同期したりできます。 また、モーダルダイアログを作成せずにユーザーがQEventLoop



を使用しても処理されません。 ダイアログを無効にした後、qt monkeyは非常に安定して機能し、プロジェクトは完了しました。







Githubバージョン


なぜなら この種のタスクのためのオープンソースのソリューションはまだ見ていませんが、強制休暇中に、私は自由に水泳でプロジェクトをリリースすることにしました。 前の従業員はqt monkeyの公開に反対していませんでしたが、念のためゼロからコピーして、githubに投稿しました。







現在のバージョンは3つのコンポーネントで構成されています。







  1. プロジェクトにリンクするライブラリ。次に、メインスレッドのどこかにqt_monkey_agent::Agent



    クラスを作成します。







  2. qtmonkey_appは、1)と通信するためのコンソールプログラムです。これを使用して、たとえば、継続的インテグレーションシステムでテストを実行できます。







  3. qtmonkey_gui-qtmonkey_appの基本GUI。


2と3はstdout



/ stdin



を使用して通信し、データストリームはJSONを使用して構造化されます 。 qtmonkey_guiは、お気に入りのIDEのプラグインに簡単に置き換えることができると想定されています。







このプロジェクトに興味を持つ人がいる場合、githubの「qt monkey」という言葉で簡単に見つけることができます。 プルリクエストは大歓迎です。








All Articles