4

PySide と Qt を使用して、ある種の GUI テスト ライブラリを開発しています。これまでのところ、テスト ケースで 1 つの条件 (シグナルやタイムアウトなど) が発生するのを待つ必要がある場合は非常にうまく機能しますが、私の問題は、データ検証に進む前に複数の条件が発生するのを待たなければならないことです。

テスト ランナーは、メイン スレッドをあまり妨害しないように、独自のスレッドで動作します。シグナル/タイムアウトの待機はイベント ループで発生します。これはうまく機能する部分です (簡略化された例)。

# Create a  simple event loop and fail timer (to prevent infinite waiting)
loop = QtCore.QEventLoop()
failtimer = QtCore.QTimer()
failtimer.setInterval(MAX_DELAY)
failtimer.setSingleShot(True)
failtimer.timeout.connect(loop.quit)

# Connect waitable signal to event loop
condition.connect(loop.quit) # condition is for example QLineEdit.textChanged() signal

# Perform test action
testwidget.doStuff.emit() # Function not called directly, but via signals

# Wait for condition, or fail timeout, to happen
loop.exec_()

# Verify data
assert expectedvalue == testwidget.readValue()

待機は同期的でなければならないため、イベント ループが有効ですが、複数のシグナルに対しては機能しません。もちろん、複数の条件のいずれかを待つことは可能ですが、複数の条件/シグナルがすべて発生するのを待つことはできません。それで、これをどのように進めるかについて何かアドバイスはありますか?

受信したシグナルの数をカウントし、必要な数に達すると ready() シグナルを発行するヘルパー クラスについて考えていました。しかし、これは本当に最善の方法でしょうか?ヘルパーは、特定のシグナルの「インスタンス」が 1 つだけ考慮されるように、各送信者もチェックする必要があります。

4

3 に答える 3

3

最終的に、かなり単純なヘルパー クラスを実装しました。待機可能なシグナル用と受信シグナル用のセットがあります。待機可能な各シグナルは、1 つのスロットに接続されます。スロットはsender()レディセットに を追加し、セットのサイズが一致すると信号を発しreadyます。

誰かが興味を持っているなら、これが私がやったことです:

from PySide.QtCore import QObject, Signal, Slot

class QMultiWait(QObject):
    ready = Signal()

    def __init__(self, parent=None):
        super(QMultiWait, self).__init__(parent)
        self._waitable = set()
        self._waitready = set()

    def addWaitableSignal(self, signal):
        if signal not in self._waitable:
            self._waitable.add(signal)
            signal.connect(self._checkSignal)

    @Slot()
    def _checkSignal(self):
        sender = self.sender()
        self._waitready.add(sender)
        if len(self._waitready) == len(self._waitable):
            self.ready.emit()

    def clear(self):
        for signal in self._waitable:
            signal.disconnect(self._checkSignal)

このclear関数はほとんど必要ありませんが、クラス インスタンスを再利用できます。

于 2014-01-14T13:28:04.367 に答える
3

個人的には、必要なすべてのシグナルを、対応するシグナル ハンドラー (別名) に接続します。スロット。

それらはすべて、放出が「完了」したことをマークし、全体的な状態が「完了」したかどうかをチェックし、各シグナル ハンドラが独自の「完了」を設定した後、グローバルな「完了」チェックが行われる可能性があります。それで十分な場合は、「グローバル完了」シグナルを送信します。

次に、最初にその「グローバル完了」シグナルに接続することもできます。対応するシグナルハンドラーがトリガーされると、その間に条件が変更されない限り、それが完了したことがわかります。

理論的な設計の後、次のようなものになります(疑似コード)

connect_signal1_to_slot1();
connect_signal2_to_slot2();
...
connect_global_done_signal_to_global_done_slot();

slotX: mark_conditionX_done(); if global_done: emit global_done_signal();
global_done_slot: do_foo();

おそらく、シグナルとスロットを 2 つだけ持つことで単純化することもできます。つまり、1 つは渡された引数に基づいて完了したローカル シグナルを「マーク」するローカル完了操作用であり、次に「グローバル完了」シグナルとスロットがあります。

違いは、1 つのシグナルとスロットで引数を使用するか、引数なしで多数のシグナルとスロットを使用するかのセマンティクスですが、原理的には同じ理論です。

于 2014-01-14T08:08:18.417 に答える
1

これを C++ で行う非常に簡単な方法は次のとおりです。

  1. シグナルが送信されると予想される (オブジェクト、シグナル インデックス) ペアのセットを用意します。

  2. 待機が開始される前にセットをコピーします。

  3. スロットで、コピーしたリストから (sender(), senderSignalIndex()) 要素を削除します。リストが空の場合は、完了したことがわかります。

このソリューションの利点は移植性です。このアプローチは PySideC++ の両方で機能します。

C++ では、connect()通常、メソッド引数をSIGNALまたはSLOTマクロでラップして呼び出します。これらのマクロは、呼び出し可能なメソッド、シグナル、またはスロットのいずれであるかを示すために、'0'、'1'、または '2' のいずれかのメソッド コードを先頭に追加します。registerSignal生のメソッド名が必要なため、このメソッド コードは を呼び出すときにスキップされます。

indexOfMethod呼び出されるにregisterSignalは正規化された署名が必要であるため、メソッドconnectはそれを正規化します。

class SignalMerge : public QObject {
    Q_OBJECT
#if QT_VERSION>=QT_VERSION_CHECK(5,0,0)
    typedef QMetaObject::Connection Connection;
#else
    typedef bool Connection;
#endif
    typedef QPair<QObject*, int> ObjectMethod;
    QSet<ObjectMethod> m_signals, m_pendingSignals;

    void registerSignal(QObject * obj, const char * method) {
        int index = obj->metaObject()->indexOfMethod(method);
        if (index < 0) return;
        m_signals.insert(ObjectMethod(obj, index));
    }
    Q_SLOT void merge() {
        if (m_pendingSignals.isEmpty()) m_pendingSignals = m_signals;
        m_pendingSignals.remove(ObjectMethod(sender(), senderSignalIndex()));
        if (m_pendingSignals.isEmpty()) emit merged();
    }
public:

    void clear() {
        foreach (ObjectMethod om, m_signals) {
            QMetaObject::disconnect(om.first, om.second, this, staticMetaObject.indexOfSlot("merge()"));
        }
        m_signals.clear();
        m_pendingSignals.clear();
    }
    Q_SIGNAL void merged();
    Connection connect(QObject *sender, const char *signal, Qt::ConnectionType type = Qt::AutoConnection) {
        Connection conn = QObject::connect(sender, signal, this, SLOT(merge()), type);
        if (conn) registerSignal(sender, QMetaObject::normalizedSignature(signal+1));
        return conn;
    }
};
于 2014-01-15T16:29:06.377 に答える