Реализация и тестирование Qt C++ клиента сетевого сервиса с асинхронным интерфейсом на примере Yandex Dictionary Api

от автора

Есть у меня один проект долгострой, в котором использую Yandex Dictionary Api. В процессе разработки решил поделиться опытом создания асинхронного интерфейса к интернет-сервису.

Если у вас есть интерес, как реализовать такой клиент с помощью Qt C++, то этот пост для вас.

Я не стал заострять внимания на тех моментах Qt, которые и так хорошо описаны. В статье я попытался раскрыть, как создавать асинхронные классы в Qt на базе конкретного примера.

Пример программы

Программа посылает на сервер Yandex Dictioanary Api запросы на перевод слов и затем, по мере поступления ответов от сервиса, выводит их на экран.

#include "Precompiled.h" #include <QtYandexApi/QtYandexApi.h>  int main(int argc, char *argv[]) {     QCoreApplication a(argc, argv);      QtYandexDictionary yandexDictionary(QtYandexApi::getYandexKeyFromFile("dictKey"));     QObject::connect(&yandexDictionary, &QtYandexDictionary::translated,                      [](const QtYaWordTranslation& wordTranslation)     {         if (wordTranslation.isError())             qDebug() << wordTranslation.errorString();         else {             QtYaWord wordForTranslation = wordTranslation.wordForTranslation();             QtYaTranslatedWord translatedWord = wordTranslation.translatedWord();             qDebug() << "\n***************";             qDebug() << "Word: " << wordForTranslation.wordName();             qDebug() << "Direction: " << wordForTranslation.fromLanguage() << "-" << wordForTranslation.toLanguage();             qDebug() << "Main translation: " << translatedWord.mainTranslation();             qDebug() << "Synonyms: " << translatedWord.synonyms();             qDebug() << "Examples: ";             for (const auto& example : translatedWord.examples()) {                 qDebug() << example.first << "-" << example.second;             }         }     });      QStringList russianWords, englishWords;     russianWords << "дом" << "время" << "легенда" << "ключ" << "клавиатура" << "монитор" << "случай" << "один" << "два" << "три" << "четыре" << "пять" << "шесть";     englishWords << "home" << "time" << "legend" << "key" << "keyboard" << "monitor" << "infection" << "one" << "two" << "three" << "four" << "five" << "success";      for (const QString& word : russianWords) {         yandexDictionary.translate(QtYaWord(word, "ru", "en"));     }     for (const QString& word : englishWords) {         yandexDictionary.translate(QtYaWord(word, "en", "ru"));     }      return a.exec(); }  


Результат работы программы:

***************
Word: «время»
Direction: «ru» — «en»
Main translation: «time»
Synonyms: («while»)
Examples:
«тромбопластиновое время» — «thromboplastin time»
«принять промежуток времени» — «take a while»

***************
Word: «клавиатура»
Direction: «ru» — «en»
Main translation: «keyboard»
Synonyms: («keypad», «pad»)
Examples:
«экранная клавиатура» — «onscreen keyboard»
«ЖКИ-клавиатура» — «LCD keypad»
«цифровая клавиатура» — «numeric pad»

***************
Word: «ключ»
Direction: «ru» — «en»
Main translation: «key»
Synonyms: («turnkey», «clue», «wrench», «dongle»)
Examples:
«деблокировочный ключ» — «unblocking key»
«решение под ключ» — «turnkey solution»
«иметь никакой ключ» — «have no clue»
«шестигранный ключ» — «hexagonal wrench»

***************
Word: «монитор»
Direction: «ru» — «en»
Main translation: «monitor»
Synonyms: ()
Examples:
«ЭЛТ-монитор» — «CRT monitor»

***************
Word: «четыре»
Direction: «ru» — «en»
Main translation: «four»
Synonyms: ()
Examples:
«двадцать четыре часа» — «twenty four hours»

***************
Word: «один»
Direction: «ru» — «en»
Main translation: «one»
Synonyms: ()
Examples:
«одна иота» — «one jot»

***************
Word: «два»
Direction: «ru» — «en»
Main translation: «two»
Synonyms: ()
Examples:
«две несмешивающиеся жидкости» — «two immiscible liquids»

***************
Word: «легенда»
Direction: «ru» — «en»
Main translation: «legend»
Synonyms: («myth»)
Examples:
«древнегреческая легенда» — «ancient Greek legend»
«городская легенда» — «urban myth»

***************
Word: «три»
Direction: «ru» — «en»
Main translation: «three»
Synonyms: ()
Examples:
«три мушкетера» — «three musketeers»

***************
Word: «случай»
Direction: «ru» — «en»
Main translation: «case»
Synonyms: («event», «occasion», «chance», «occurrence»)
Examples:
«невзвешенный случай» — «unweighted case»
«несчастный случай» — «unfortunate event»
«тот редкий случай» — «that rare occasion»
«слепой случай» — «blind chance»
«редкий случай» — «rare occurrence»

***************
Word: «пять»
Direction: «ru» — «en»
Main translation: «five»
Synonyms: ()
Examples:
«семьдесят пять лет» — «seventy five year»

***************
Word: «home»
Direction: «en» — «ru»
Main translation: «дом»
Synonyms: ()
Examples:
«ancestral home» — «отчий дом»

***************
Word: «шесть»
Direction: «ru» — «en»
Main translation: «six»
Synonyms: ()
Examples:
«шесть сигм» — «six Sigma»

***************
Word: «time»
Direction: «en» — «ru»
Main translation: «время»
Synonyms: («раз», «тайм»)
Examples:
«thromboplastin time» — «тромбопластиновое время»
«umpteenth time» — «энный раз»
«second time» — «второй тайм»

***************
Word: «key»
Direction: «en» — «ru»
Main translation: «ключевой»
Synonyms: ()
Examples:
«key informants» — «ключевые информанты»

***************
Word: «monitor»
Direction: «en» — «ru»
Main translation: «монитор»
Synonyms: («видеомонитор»)
Examples:
«LCD monitor» — «жидкокристаллический монитор»

***************
Word: «infection»
Direction: «en» — «ru»
Main translation: «инфекция»
Synonyms: («зараза»)
Examples:
«cholangiogenic infection» — «холангиогенная инфекция»
«this infection» — «эта зараза»

***************
Word: «one»
Direction: «en» — «ru»
Main translation: «один»
Synonyms: ()
Examples:
«one jot» — «одна иота»

***************
Word: «keyboard»
Direction: «en» — «ru»
Main translation: «клавиатура»
Synonyms: ()
Examples:
«QWERTY keyboard» — «клавиатура QWERTY»

***************
Word: «three»
Direction: «en» — «ru»
Main translation: «три»
Synonyms: ()
Examples:
«three musketeers» — «три мушкетера»

***************
Word: «four»
Direction: «en» — «ru»
Main translation: «четыре»
Synonyms: ()
Examples:
«twenty-four» — «двадцать четыре»

***************
Word: «two»
Direction: «en» — «ru»
Main translation: «два»
Synonyms: ()
Examples:
«two immiscible liquids» — «две несмешивающиеся жидкости»

***************
Word: «five»
Direction: «en» — «ru»
Main translation: «пять»
Synonyms: ()
Examples:
«ninety-five» — «девяносто пять»

***************
Word: «legend»
Direction: «en» — «ru»
Main translation: «легенда»
Synonyms: («предание», «сказание», «поверье»)
Examples:
«ancient Greek legend» — «древнегреческая легенда»
«ancient legend» — «древнее предание»
«epic legends» — «эпические сказания»
«folk legend» — «народное поверье»

***************
Word: «success»
Direction: «en» — «ru»
Main translation: «успех»
Synonyms: («успешность», «удача»)
Examples:
«resounding success» — «оглушительный успех»
«financial success» — «финансовая успешность»
«bring success» — «приносить удачу»

***************
Word: «дом»
Direction: «ru» — «en»
Main translation: «house»
Synonyms: ()
Examples:
«дом Стенбока» — «Stenbock house»

Код этого примера, а так же реализация самого клиента — bitbucket.org/milovidov/qtyandexapi.

Реализация клиента

В данном разделе я постараюсь осветить некоторые моменты, специфичные для Qt и позволяющие легко реализовывать асинхронные клиенты.
Интерфейс класса QtYandexDictionary выглядит следующим образом:

#ifndef QTYANDEXDICTIONARY_H #define QTYANDEXDICTIONARY_H  #include <QObject>  #include "qtyandexapi_global.h"  #include "QtYaWord.h" #include "QtYaWordTranslation.h" #include "QtYaLanguages.h"  class QNetworkAccessManager;  class QTYANDEXAPISHARED_EXPORT QtYandexDictionary : public QObject {     Q_OBJECT public:     explicit QtYandexDictionary(const QString& yandexApiKey, QObject *pParent = 0);      void translate(const QtYaWord& word);     void getLanguages();  signals:     void translated(const QtYaWordTranslation& translation) const;     void languagesGot(const QtYaLanguages& langs) const;  private:     QString m_yandexApiKey;     QNetworkAccessManager* m_pNetworkAccessManager; };  #endif // QTYANDEXDICTIONARY_H 

Методы translate и getLanguages асинхронные. Они формируют и отправляют соответствующие запросы к Yandex Dictionary Api и возвращают управление потоку из которого были вызваны.

Сигналы translated и languagesGot эмитятся после получения какого либо результата от Yandex Dictionary Api, либо при невозможности подключиться к этому сервису. В аргументах этих сигналов содержится информация полученная от Яндекса или же текст ошибки при невозможности подключиться к нему.

Если посмотреть на код функции translate класса QtYandexDictionary, то она выглядит следующим образом:

void QtYandexDictionary::translate(const QtYaWord &word) {     QtYaTranslationGettor* pYandexTranslationGettor = new QtYaTranslationGettor(m_yandexApiKey, word, m_pNetworkAccessManager, this);     connect(pYandexTranslationGettor, &QtYaTranslationGettor::translated, this, &QtYandexDictionary::translated);     connect(pYandexTranslationGettor, &QtYaTranslationGettor::translated, pYandexTranslationGettor, &QtYaTranslationGettor::deleteLater); } 

Здесь мы видим, что для каждого слова переданного в функцию translate создается уникальный экземпляр класса QtYaTranslationGettor. Этот класс непосредственно занимается переводом слов через Yandex Dictionary Api и так же эмитит сигнал translated. Этот же сигнал, без каких либо изменений пробрасывается наверх к QtYandexDictionary. После эмита этого сигнала экземпляр класса QtYaTranslationGettor удаляется за счет коннекта к его же слоту deleteLater.

Как видим, никаких очередей запросов, никакого контроля создаваемых объектов. Каждый объект имеет свое слово для перевода и сам удаляется после завершения своей работы и отправки результата.

Метода getLanguages реализован аналогично:

void QtYandexDictionary::getLanguages() {     QtYandexLanguagesGettor* pYandexLanguagesGettor = new QtYandexLanguagesGettor(m_yandexApiKey, m_pNetworkAccessManager, this);     connect(pYandexLanguagesGettor, &QtYandexLanguagesGettor::languagesGot, this, &QtYandexDictionary::languagesGot);     connect(pYandexLanguagesGettor, &QtYandexLanguagesGettor::languagesGot, pYandexLanguagesGettor, &QtYandexLanguagesGettor::deleteLater); } 

Реализация классов QtYaTranslationGettor и QtYandexLanguagesGettor содержит в себе стандартную практику работы с классом QNetworkAccessManager и работу с JSON в Qt5. Поэтому не буду заострять внимания на деталях их реализации.

Тестирование клиента

В данном разделе я покажу, как можно тестировать асинхронные Qt классы.

Пример тестирования асинхронной функции translate выглядит так:

void TQtYandexApi::testTranslation() {     const QString rusHome = QString("Home");     QSignalSpy signalSpy(m_pYandexDictionary, SIGNAL(translated(QtYaWordTranslation)));     QMetaObject::Connection connection = connect(m_pYandexDictionary, &QtYandexDictionary::translated,                                                  [&](const QtYaWordTranslation& translation)     {         QVERIFY2(translation.isError() == false, translation.errorString().toStdString().c_str());         QVERIFY(translation.wordForTranslation().wordName() == rusHome);         QVERIFY(translation.wordForTranslation().fromLanguage() == "en");         QVERIFY(translation.wordForTranslation().toLanguage() == "ru");         QVERIFY(translation.translatedWord().mainTranslation().toLower() == QString::fromUtf8("дом"));     });     m_pYandexDictionary->translate(QtYaWord(rusHome, "en", "ru"));     END_ASYNC_FUNCTION(signalSpy, connection) } 

В начале создаем слово для перевода с английского на русский — переменная rusHome.

Затем создаем signalSpy — это объект будет ждать сигнала от m_pYandexDictionary.

Далее создается обработчик сигнала translated в виде лямбды выражения. В ней проверяется правильность перевода слова.

После этого отправляется запрос на перевод и вызывается макрос END_ASYNC_FUNCTION.

Код этого макроса:

#define END_ASYNC_FUNCTION(signalSpy, connection) \     QVERIFY(signalSpy.wait()); \     QObject::disconnect(connection); 

Сначала ждем прихода сигнала. По умолчанию время ожидания сигнала 5 секунд. Если он придет, то начнет выполняться код обработчика, который мы задали в лямбда выражении ранее. Если нет, то тест зафейлится на этой строке.

После отключаем коннект с лямбда выражением, т.к. иначе он сохраниться и после выхода из функции.

Выводы

В статье я привел простой способ создания асинхронных функций и раскрыл способ их тестирования.

ссылка на оригинал статьи http://habrahabr.ru/post/233099/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *