среда, 7 октября 2009 г.

Qt4 эмуляция сокета с помощью QBuffer в юнит тестах

В одной своей разработке, я решил реализовать работу с сетью не напрямую через QTcpSocket, а через общий интерфейс для всех устройств ввода вывода имеющийся в Qt4, QIODevice. И в первую очередь решил сэкономить время при написании юнит тестов, используя вместо честных сокетов QBuffer, имплементацию интерфейса QIODevice, пишущую и читающую данные из буфера в оперативной памяти. Но не тут то было.

Первое, что я выяснил, что сигнал QIODevice::readyRead() выбрасывается из EventLoop'а даже в случае такого простого класса, как буфер. Так что первое что пришлось сделать это понять как во время выполнения теста, не надолго отлучиться из тестовой функции в EventLoop. Первая идея завести свой QEventLoop прямо в тесте запустить, его не забыв предварительно задать условия его остановки, например так:
QTimer::singleShot(500,myELoop,SLOT(quit()))

За пол секунды EventLoop заведомо сделает всё необходимое, и тест можно будет завершать. Но выглядит это решение откровенно говоря не очень. Покопавшись в документации, я нашёл более элегантное решение:
void QTest::qWait(int ms)

приостанавливает выполнение текущего теста на заданное количество миллисекунд отдавая управление EventLoop'у. Причём экспериментальным путём было выяснено, что все необходимые операции цикл обработки событий успевает проделать за одну миллисекунду. Подозреваю, что получив управление EventLoop не возвращается раньше, чем сгенерирует все сигналы, хотя это я не проверял.

Для эмуляции сокета я решил создать два объекта QBuffer с общим буфером, но фокус не прошёл, так как класс QBuffer не генерирует сигнал readyRead если содержимое буфера изменилось без помощи его методов. По всей видимости просто нету надёжного механизма сделать это, так как QByteArray используемый в качестве буфера данных не имеет никаких механизмов оповещения об изменении содержащихся в нём данных. После внимательного перечитывание документации по Qt4 и нескольких экспериментов я пришёл к следующему решению. Создаём два объекта QBuffer после чего пишем в один из них данные и выполняем "передачу данных по сети вручную":
 quint64 pos = dev2.pos();  
 dev2.write( dev1.buffer() );  
 dev2.seek(pos);  
 QTest::qWait(1);  

Тут стоит отметить, что тот факт, что сигнал readyRead() генерируется в цикле обработке событий, а не непосредственно в функции write() есть очень хорошо. Ведь если бы он генерировался сразу, то его обработчик был бы вызван раньше, чем я верну указатель на текущую позицию в устройстве в прежнее значение и обработчик счёл бы, что на самом деле новых данных не прибыло. Вышеприведённый код имеет смысл обернуть в некую функцию, чтобы не страдать копипастом понарпасну.

В общем на изобретение такого метода ушло около двух часов, но результат того стоил. Мне не нужно создавать QTcpServer и слушать какой-нибудь порт, который по абстрактным соображениям вроде никто другой слушать не должен. Я могу эмулировать сетевое соединение используя пару буферов в оперативной памяти.