我正在使用PySide和Qt开发各种GUI测试库。到目前为止,当测试用例只需要等待一个条件发生时(例如信号或超时),它可以很好地工作,但我的问题是在继续进行数据验证之前必须等待多个条件发生。
测试运行器在自己的线程中工作,以免过多地干扰主线程。等待信号/超时发生在事件循环中,这是可以很好地工作的部分(简化示例):
# 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 :(得分: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();
你也可以通过只有两个信号和插槽来简化,即:一个用于本地完成操作,根据传递的参数“标记”本地信号,然后将有“全局完成”信号和插槽
不同之处在于语义,无论是使用带有一个信号和槽的参数,还是带有参数的许多信号和槽,但原则上它是相同的理论。
答案 1 :(得分: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
函数,但允许重用类实例。
答案 2 :(得分:1)
在C ++中,一个非常简单的方法是:
有一组您希望发出信号的(对象,信号索引)对。
在等待开始前复制该集。
在插槽中,从复制的列表中删除(sender(),senderSignalIndex())元素。如果列表为空,则表示您已完成。
此解决方案的好处是可移植性:该方法适用于PySide 和 C ++。
在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;
}
};