2014-01-14 91 views
4

我正在開發一個使用PySide和Qt的GUI測試庫。到目前爲止,當測試用例需要等待只發生一種情況時(例如信號或超時),它的工作情況非常好,但是我的問題是在繼續進行數據驗證之前需要等待多個條件發生。Qt:如何等待多個信號?

測試運行器在其自己的線程中工作,以免干擾主線程太多。等待信號/超時與事件循環發生,這是一個很好地工作(簡化的例子)的部分:

# 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()信號。但這真的是最好的方式嗎?助手還必須檢查每個發件人,以便只記錄特定信號的一個「實例」。

+0

「那麼,關於如何進行此操作的任何建議?」我相信你想[模擬](http://en.wikipedia.org/wiki/Mock_object)而不是Qt庫。更容易,你有更多的控制權。此外,您目前的測試以某種方式測試* Qt庫*比您的代碼更多。 – Bakuriu

+0

@Bakuriu:赦免? – lpapp

+0

@LaszloPapp請參閱['unittest.mock'](http://docs.python.org/dev/library/unittest.mock)。 – Bakuriu

回答

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(); 

你也許也只有兩個信號和槽,即簡化:一個用於本地完成的操作,「標誌「基於通過的參數完成的本地信號,然後將會有」全局完成「信號和時隙。

不同之處在於語義,無論是使用帶有一個信號和槽的參數還是多個沒有參數的信號和槽,但原則上都是相同的理論。

+0

這聽起來像是一種方式,但這種方法的問題在於,我不一定事先知道我可能需要多少個特定插槽。測試用例在一個單獨的.xml文件中,不是由我寫的,所以可能有一到六個。 –

+0

@TeemuKarimerto:我沒有看到那個問題?你關心什麼?當然,您可以通過參數將所有內容放入一個信號槽關係中,然後設置和全局檢查將在一個插槽中完成。 – lpapp

+0

現在我想到了,多種不同的插槽方法確實可以工作。我只需跟蹤已分配的插槽,並在添加新條件時選擇下一個插槽。幸運的是,這對於Python來說都是相對簡單的。另一種方法是考慮接收信號並檢查dict的大小是否等於等待條件計數,然後發出'global'ready()信號。然而,這可能會有無法預料的問題? –

3

我最終實現了一個相當直接的幫助類。它有一組等待信號和另一組接收信號。每個等待信號連接到一個插槽。該插槽將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功能幾乎沒有必要,但允許被重用的類的實例。

+0

+1這聽起來像是等待多種情況時通用的方式。 – Trilarion

1

一個非常簡單的方法來做到這一點,在C++中,將是:

  1. 有一套,你期望得到信號(對象,信號指標)對。

  2. 在等待開始之前複製設置。

  3. 在插槽中,從複製列表中刪除(sender(),senderSignalIndex())元素。如果列表爲空,則表明您已完成。

這個方案的好處是便攜性:這種方法在兩個PySide C++工程。

在C++中,通常調用connect()時將方法參數封裝在一個SIGNALSLOT宏中。這些宏會預先設置一個方法代碼:'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; 
    } 
}; 
+0

這也是一個非常有趣的方法,雖然與我已經實現的類似。但是,爲了將來的參考,特別是對於C++來說,這確實是一件好事。我以前不知道'senderSignalIndex()'。你能解釋爲什麼registerignal是用'(sender,signal + 1)'調用的,就像爲什麼'+ 1'跳過一個字符? –