[Из песочницы] Строго-типизированный SignalSpy для тестирования Qt приложений
При написании юнит-тестов за правило хорошего тона считается проверка инвариантов класса посредством открытого интерфейса класса. В случае с Qt всё немного сложнее, так как функции-члены могут потенциально посылать сигналы, которые выходят «наружу» объектов и являются тем самым частью открытого интерфейса. Для этих целей в модуле QtTestLib имеется полезный класс QSignalSpy, который следит за определённым сигналом издаваемым тестируемым обьектом и скурпулёзно ведёт протокол, сколько раз и с какими значениями этот сигнал был вызван.Вот как это работает:. // Предполагается, что в классе MyClass определён сигнал «void someSignal (int, bool)». MyClass someObject; QSignalSpy spy (&someObject, SIGNAL (someSignal (int, bool))); // шпионим за сигналом «someSignal».
emit someObject.someSignal (58, true);
emit someObject.someSignal (42, false);
QList
assert (2 == firstCallArgs.size ()); assert (58 == firstCallArgs.at (0).toInt ()); // второй синтактический звоночек assert (true == firstCallArgs.at (0).toBool ());
assert (2 == secondCallArgs.size ()); assert (42 == secondCallArgs.at (1).toInt ()); assert (false == secondCallArgs.at (2).toBool ()); Как Вы видите, у данного подхода есть ряд недостатков: если кто-то переименует сигнал someSignal, код по прежнему будет компилироваться, так как запись SIGNAL (someSignal (int, bool)) всего-лишь создаёт из сигнатуры сигнала строковую константу (третий звоночек). если в ходе теста, Вам понадобиться проверить, что сигнал ни разу не был вызван, т.еassert (0 == spy.size ()); то в случае переименовывания сигнала, тест будет не только компилироваться, но ещё и успешно проходить выполнение. все распаковки из QVariant в …toInt (), …toBool () и так далее компилируются в независимости от того, что изначально было в этот QVariant запаковано. В крайнем случае получите 0. А если Вы как раз хотите проверить значение на равенство нулю, то Ваш тест будет работать даже после того как кто-то поменяет аргумент сигнала с int на QString. ну, и последнее: необходимость всё время распаковывать содержимое QVariant’а немного утомляет. Если подобные недостатки вызывают у Вас недовольство и если Вы относитесь к тем программистам, которые пишут машинный код медленнее и хуже компилятора, то давайте попросим компилятор заодно и помочь в решении проблемы со шпионом сигналов.Итак, что же нужно сделать? Для начала, набросаем шапку класса:
template <... тут потом заполним...> class SignalSpy; Определять обьекты нашего класса хотелось используя не строку с именем сигнала, а сам сигнал. Т.е как-то так: SignalSpy<...тут чё-то...> spy (&someObject, &MyClass: someSignal); Так мы сможем узнать на этапе компиляции, что такого сигнала, к примеру, нет или, что список его аргументов не подходит под тип шпиона.Далее, зачем хранить аргументы вызова в списке QVariant’ов, если их количество и качество известно заранее? Намного лучше было бы использовать что-то вроде такого чудища:
std: tuple
assert (58, get<0>(spy.at (0))); assert (true, get<1>(spy.at (0))); assert (42, get<0>(spy.at (1))); assert (false, get<1>(spy.at (1))); Другое дело.