Skip to content

Latest commit

 

History

History
1156 lines (881 loc) · 112 KB

README_RU.md

File metadata and controls

1156 lines (881 loc) · 112 KB

ogo_building_spbstu

Санкт-Петербургский государственный политехнический университет
Институт компьютерных наук и технологий

PolyChat

Кроссплатформенный чат для локальной сети, основанный на разработке QT

简体中文 English

License

MacOS CI/CD Windows CI/CD

Содержание

Требования

Системные требования

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

PolyChat - это кроссплатформенное программное обеспечение для обмена мгновенными сообщениями, предназначенное для решения вышеперечисленных проблем и повышения продуктивности работы преподавателей и студентов. Оно имеет следующие основные характеристики:

  • Благодаря закрытому характеру локальной сети, для входа в систему пользователям необходимо указать только свое имя и номер группы, чтобы обеспечить простоту использования и безопасность

  • Возможность пользователей быстро создавать групповые чаты и присоединяться к ним, а также отправлять в них сообщения и файлы

  • Редактируемое форматирование текстовых сообщений

  • Сохранение содержимого сообщения в txt-файл

  • Очищение содержимого чата

Технические требования

Основные технические потребности (требования) следующие:

  • Для обеспечения кроссплатформенности разработка будет вестись в Qt

  • Чтобы соответствовать философии "высокая сплоченность, низкая связанность", разработка будет вестись с использованием трехуровневой архитектуры, разделяющей уровень представления (UI), уровень бизнес-логики (BLL) и уровень доступа к данным (DAL)

  • Qt будет использоваться для кроссплатформенности и масштабируемости

  • Для обмена текстовыми сообщениями используется протокол UDP, а для передачи файлов - протокол TCP

  • Когда пользователь отправляет сообщение, оно транслируется на порт

  • Socket UDP привязывается за фиксированным портом, а сигнал и слот используются для прослушивания входящих данных

  • Автоматизация тестирования, сборки, упаковки и выпуска с помощью CI/CD для Action на платформе GitHub

Функциональная спецификация

Идентификатор Требование
Login_Deficient Если имя и/или номер группы не введены, будет выведено уведомление с текстом ошибки
Login_Minimize При нажатии на кнопку минимизации окно сворачивается
Login_Exit При нажатии на кнопку закрытия происходит выход из основной программы
DialogList_Load При успешном входе пользователя в систему будет вызван список чатов (групповой чат в локальной сети) и загружен соответствующий файл ресурсов
DialogList_Add_Chat Пользователи могут нажать на кнопку "Плюс", чтобы указать имя группового чата во всплывающем диалоговом окне, и будет создан групповой чат с таким именем. После создания нового группового чата список групповых чатов будет обновлен, чтобы добавить только что созданный групповой чат
DialogList_Add_Chat_Clash Если групповой чат с определенным именем уже существует, при попытке создать его снова появится уведомление с сообщением об ошибке
DialogList_View Пользователи могут использовать колесико мыши для просмотра содержимого списка
DialogList_Select Пользователи могут выбрать групповой чат из списка чатов
DialogList_Search Пользователи могут искать групповой чат по его названию через поисковую строку
DialogList_Join При нажатии (выборе) группового чата в списке чатов, пользователь присоединится к групповому чату и появится окно чата
DialogList_Join_Clash Когда пользователь присоединился к групповому чату, при повторном присоединении будет выведено уведомление с текстом ошибки
DialogList_Minimize При нажатии на кнопку минимизации окно сворачивается
DialogList_Maximize При нажатии на кнопку полного экрана окно разворачивается на весь экран
DialogList_Exit При нажатии на кнопку закрытия происходит выход из основной программы
ChatWidget_Text_Font_Type (Выделить текст) Изменить шрифт в раскрывающемся списке. Шрифты, которые можно изменять, - это шрифты, уже установленные на компьютере
ChatWidget_Text_Font_Size (Выделить текст) Изменить размер текста в раскрывающемся списке, размер шрифта от 12 до 28
ChatWidget_Text_Font_B (Выделить текст) Нажатие на кнопку "Жирный" сделает текст жирным
ChatWidget_Text_Font_I (Выделить текст) Нажатие на кнопку "Курсив" сделает текст наклонным
ChatWidget_Text_Font_L (Выделить текст) Нажатие на кнопку "Подчеркивание" добавит подчеркивание в нижнюю часть текста
ChatWidget_Mess_View Пользователи могут использовать колесико мыши для просмотра журналов чата
ChatWidget_Mess_Clean Нажатие на кнопку "Очистить чат" очистит все журналы чата в своем окне, содержимое окон других пользователей не затрагивается
ChatWidget_Mess_Save Нажатие на кнопку "Сохранить" сохранит журнал чата в виде txt-файла
ChatWidget_Mess_Send Нажатие на кнопку "Отправить" отправит текст в поле ввода в групповой чат
ChatWidget_Mess_Send_Empty Когда поле ввода текста пустое, нажатие на кнопку "Отправить" ничего не отправляет
ChatWidget_File_Send Пользователи могут выбрать файл и отправить его в групповой чат
ChatWidget_File_Send_Large Если размер файла превышает 1 Гб, он не будет отправлен
ChatWidget_File_Receive Когда пользователь отправляет файл, другие пользователи могут выбирать, принимать его или нет
ChatWidget_File_Receive_Path Если пользователь решит получить файл, то он может выбрать, где его сохранить
ChatWidget_User_In Когда пользователь присоединяется к групповому чату, все пользователи получают сообщение
ChatWidget_User_Out Пользователь, нажавший на кнопку выхода, выйдет из группового чата, в то время как другие пользователи не пострадают
ChatWidget_User_Out_Mess Когда пользователь покидает групповой чат, все получают сообщение о том, что пользователь покинул чат
ChatWidget_Minimize При нажатии кнопки минимизации окно сворачивается
ChatWidget_Maximize При нажатии на кнопку полного экрана окно разворачивается на весь экран

HLD

Дизайн GUI

Окно авторизации

widget_login

Окно списка групповых чатов

chat_list

Окно чата

chat_widget

Архитектура

Мы используем диаграмму Use Case для представления архитектуры программного обеспечения PolyChat:

architecture

Диаграмма классов

В PolyChat существует 3 класса: список групп, группа и пользователь. Их взаимосвязь показана на диаграмме ниже:

class

Раздел группового чата UDP и список групп

Это приложение реализует следующую функциональность: в пределах локальной сети пользователи могут добавить (создать) групповой чат, и когда групповой чат создан, список групповых чатов на других хостах синхронизируется и обновляется (через UDP).

После добавления каждого пользователя в групповой чат, в правой части экрана группового чата может быть отображен список онлайн-пользователей, показывающий имя пользователя, номер класса и ip-адрес соответственно. Слева от формы отображается содержание чата, т.е. групповой чат. Каждый человек может ввести текст (и изменить формат текста) и отправить его с экрана ввода чата.

UDP-сообщения передаются по локальной сети с помощью механизма Broadcast и для удобства использования дополнены ShareAddress и ReuseAddressHint, которые описаны ниже.

  • Broadcast: широковещательная передача на все порты текущей локальной сети

  • ShareAddress: позволяет другим службам связываться с тем же адресом и портом. Это полезно, когда несколько процессов разделяют нагрузку на службу, прослушивая один и тот же адрес и порт (например, веб-сервер с несколькими предварительно вилочными слушателями может значительно улучшить время отклика). Однако, поскольку любой службе разрешено перепривязываться, этот вариант должен учитывать некоторые вопросы безопасности. Поэтому, комбинируя эту опцию с ReuseAddressHint.

  • ReuseAddressHint: он должен попытаться перепривязать сервис, даже если адрес и порт уже привязаны другим сокетом.

ui_ChatList_ChatBox

Собственный протокол взаимодействия UDP

Для достижения вышеуказанной функциональности и обеспечения последующей расширяемости программы. Используется индивидуальный протокол связи, в котором 8 блоков.

  • Блок 1: типы сообщений. В настоящее время существует 6 типов.

    • ChatExist: текущее окно группового чата существует и транслируется через регулярные промежутки времени
    • ChatDestory: удаляет текущий групповой чат, отправляется, когда все (последние) участники группового чата покинули его.
    • Msg: обычное текстовое сообщение
    • File: запрос на отправку файла
    • UserJoin: пользователь присоединяется к текущему групповому чату
    • UserLeft: пользователь покидает текущий групповой чат
  • Блок 2: название текущего группового чата

  • Блок 3: порт, на котором находится текущий групповой чат

  • Блок 4: имя пользователя, отправившего это сообщение

  • Блок 5: группы пользователя, отправившего это сообщение

  • Блок 6: IP-адрес пользователя, отправившего это сообщение

  • Блок 7: содержание сообщения (переменной длины)

  • Блок 8: содержание сообщения (переменной длины, используется для расширения параграфа 7)

Как показано на диаграмме ниже:

udp-msg

Сервер

**Примечание: Система чата в PolyChat похожа на P2P, в том смысле, что пользователь может выступать как в роли сервера, так и в роли клиента. Один пользователь может выступать в качестве и сервера, и клиента. **

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

udp_server

Клиент

**Примечание: Система чата в PolyChat похожа на P2P, в том смысле, что пользователь может выступать как в роли сервера, так и в роли клиента. Один пользователь может выступать в качестве и сервера, и клиента. **

Когда пользователь присоединяется к групповому чату, имя пользователя, номер группы, имя хоста и ip-адрес получаются и передаются на LAN-сервер (ChatBoxWidget) для обновления списка пользователей справа. Затем, когда клиенту нужно отправить сообщение, он вводит его в поле ввода чата и нажимает кнопку отправки, чтобы отправить чат, одновременно транслируя различные сообщения из локальной системы. Технологическая схема выглядит следующим образом:

udp_client

Секция передачи файлов TCP

Собственный протокол взаимодействия TCP

TCP использует специализированный протокол связи, в котором есть 4 части.

Части с 1 по 3 - это заголовки файлов, а часть 4 - собственно данные.

  • Часть 1: Имя файла
  • Часть 2: размер файла (байт)
  • Часть 3: MD5 файла
  • Часть 4: данные (4 Кб на блок)

При передаче сервер сначала отправляет заголовок файла. Для предотвращения "липких" пакетов TCP, подождите 20 мс перед началом отправки блока данных

tcp_data

Сторона отправителя

Сторона отправителя берет на себя роль сервера.

ui_tcp_file_sender

Нажмите на кнопку Отправить, выберите файл для отправки на локальном компьютере, нажмите на кнопку Отправить, появится экран Отправителя, затем индикатор выполнения покажет информацию о файле, который в данный момент отправляется, ход передачи, подключенного в данный момент клиента и другую информацию. Если вы хотите закрыть процесс отправки, нажмите кнопку Закрыть. Технологическая схема выглядит следующим образом:

tcp_sender

Сторона получателя

Сторона получателя берет на себя роль клиента.

В интерфейсе появится диалоговое окно с вопросом, хотите ли вы принять сообщение о передаче файла от конкретного пользователя, если да, нажмите кнопку Yes, в противном случае нажмите кнопку No.

ui_tcp_file_getter_req

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

ui_tcp_file_getter

Технологическая схема выглядит следующим образом:

tcp_getter

Потоки данных

Мы используем диаграмму потоков данных для представления обработки сообщений, полученных в окне группового чата PolyChat:

chat_port

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

Описание

PolyChat будет тестироваться с помощью QTest.

QTestlib — это среда модульного тестирования, предоставляемая Qt для программ или библиотек, написанных на основе Qt. QTestLib предоставляет базовые функции среды модульного тестирования и расширенные функции для тестирования графического интерфейса. Ниже перечислены особенности QTestlib:

A. Легкость: QTestlib содержит только 6000 строк кода и 60 экспортируемых символов.

B. Автономность: для тестов без графического интерфейса QTestlib требуется только несколько символов базовой библиотеки Qt.

C. Быстрота тестирования: QTestlib не требует специальной исполнительной программы тестирования и не требует специальной регистрации для тестирования.

D. Тестирование на основе данных: тестовая программа может выполняться несколько раз на разных наборах тестовых данных.

E. Базовое тестирование GUI: QTestlib предоставляет функцию имитации событий мыши и клавиатуры.

F. Контрольное тестирование: QTestlib поддерживает контрольное тестирование и предоставляет различные средства измерения.

G. Совместимость с IDE: выходные данные QTestlib могут быть проанализированы Visual Studio и KDevelop.

H. Безопасность потоков: отчеты об ошибках являются потокобезопасными и атомарными.

J. Безопасность типов: шаблон расширен для предотвращения ошибок, вызванных неявным преобразованием типов.

K. Простота расширения: определяемые пользователем типы могут быть легко добавлены к тестовым данным и тестовым результатам.

Тесты должны выполняться автоматически, когда проект собирается с помощью компоновщика qmake, создающего исполняемый файл. Используя CI/CD на GitHub, триггеры будут автоматически собирать и тестировать код на платформах Windows и macOS при изменении кода (одновременное тестирование на обеих платформах обеспечивает кроссплатформенный характер PolyChat).

Процедура расширения тестового набора

При добавлении нового кода необходимо расширить тестовый набор, добавив новые тест-кейсы со следующими атрибутами:

  • Название тестирования (идентификатор), включающее уровень тестирования, связанные модули и объект тестирования
  • Связанные модули, которые затрагиваются тестированием
  • Ожидаемые результаты, описывающие состояния/действия и реакцию на них приложения

Если новый код также изменил поведение продукта, необходимо проверить и переписать все тест-кейсы, связанные с затронутыми модулями продукта, в соответствии с новым поведением.

Модульное тестирование

Были применены несколько техник тест-дизайна:

  • классы эквивалентности

    void PolyChatTester::ut_login_login_empty()
    {
        bool isSuccInitLocalUser = DAL::initLocalUser("", "3530904/90102");
        QCOMPARE(isSuccInitLocalUser, false);
    }
  • граничные условия

    void PolyChatTester::ut_chatbox_cbxFontSize_min_max()
    {
        ChatBoxWidget chatBox(nullptr, "3530409/90102", 2333);
        QComboBox* cbox = chatBox.findChild<QComboBox*>("cbxFontSize");
        qDebug() << " cbox->count();" << cbox->count();
        QCOMPARE(cbox->itemText(0).toInt(), 10);
        QCOMPARE(cbox->itemText(cbox->count() - 1).toInt(), 28);
    }
  • попарное тестирование

    void PolyChatTester::ut_chatbox_btnBold()
    {
        ChatBoxWidget chatBox(nullptr, "3530409/90102", 2333);
        QToolButton* button = chatBox.findChild<QToolButton*>("btnBold");
    
        QCOMPARE(button->isCheckable(), true);
        QCOMPARE(button->toolButtonStyle(), Qt::ToolButtonStyle::ToolButtonIconOnly);
    }
Название тестирования Связанные модули Описание (ожидаемые результаты)
1 ut_login_login_empty Login Сбой входа в систему (имя пользователя не может быть пустым).
2 ut_login_group_empty Login Сбой входа в систему (номер группы не может быть пустым).
3 ut_login_login_group_empty Login Сбой входа в систему (имя пользователя и номер группы не могут быть пустыми).
4 ut_login_init_login Login Локальная информация инициализируется правильно путем передачи имени пользователя в правильном формате.
5 ut_login_init_group Login Локальная информация инициализируется правильно путем передачи номера группы в правильном формате.
6 ut_login_btnlogin Login btnlogin не является отмечаемой кнопкой (Checkable-QToolButton), а отображение icon отключено.
7 ut_login_btnlogin_emit Login Вход в систему. Сигнал (нажатие кнопки) входа в систему каждый раз срабатывает правильно и единожды.
8 ut_login_btnInfo_emit Login Каждый раз, когда пользователь нажимает на btnInfo, сигнал (нажатие кнопки) гарантированно срабатывает правильно и единожды.
9 ut_login_window Login Размер Login отключен и составляет 400x250.
10 ut_addchat_btnCancelAddChat Add Chat Каждый раз, когда пользователь нажимает кнопку отмены, сигнал (нажатие кнопки) срабатывает правильно и единожды.
11 ut_addchat_btnAddChat Add Chat Каждый раз, когда пользователь нажимает кнопку добавления, сигнал (нажатие кнопки) срабатывает правильно и единожды.
12 ut_chat_init Chat(Class) Правильно инициализируется (используя конструктор) объект Chat.
13 ut_chatlist_init ChatList Правильно инициализируется (используя конструктор) объект ChatList.
14 ut_chatlist_btnNewChat ChatList btnNewChat не является отмечаемой кнопкой (Checkable-QToolButton), а отображение текста отключено.
15 ut_chatlist_btnNewChat_emit ChatList Каждый раз, когда пользователь нажимает кнопку, чтобы добавить групповой чат, сигнал (нажатие кнопки) срабатывает правильно и единожды.
16 ut_chatlist_leSearch ChatList Моделируется то, как пользователь нажимает и печатает на клавиатуре, чтобы убедиться, что вводимое содержимое правильно отображается в поле поиска leSearch.
17 ut_chatlist_lbName ChatList Имя пользователя текущего пользователя правильно отображается в lbName.
18 ut_chatlist_lbGroupNumber ChatList Номер группы текущего пользователя правильно отображается в lbGroupNumber.
19 ut_chatlist_lbIP ChatList IP-адрес текущего пользователя корректно отображается в lbIP.
20 ut_addchat_port_exist ChatList Возвращает true (групповой чат уже существует на текущем порту).
21 ut_addchat_port_not_exist ChatList Возвращает false (групповой чат не существует на текущем порту).
22 ut_addchat_chat_exist ChatList Возвращает true (групповой чат с таким названием уже существует).
23 ut_addchat_chat_not_exist ChatList Возвращает false (групповой чат с таким названием не существует).
24 ut_chatlist_chat_open ChatList Возвращает true (групповой чат с таким названием уже открыт).
25 ut_chatlist_chat_not_open ChatList Возвращает false (групповой чат с таким названием не открыт).
26 ut_chatlist_getRandomPort ChatList Генерирует случайные порты между PORT_MIN и PORT_MAX.
27 ut_chatlist_setChatState ChatList Окно чата открыто или закрыто.
28 ut_tcpclient_lbClientIP TcpClient Содержимое lbClientIP правильно анализируется и отображается.
29 ut_tcpclient_lbClientPort TcpClient Содержимое lbClientPort правильно анализируется и отображается.
30 ut_tcpclient_lbServerIP TcpClient Содержимое lbServerIP правильно анализируется и отображается.
31 ut_tcpclient_lbServerPort TcpClient Содержимое lbServerPort правильно анализируется и отображается.
32 ut_tcpclient_lbFileName TcpClient Содержимое lbFileName правильно анализируется и отображается.
33 ut_tcpclient_lbFileSize TcpClient Содержимое lbFileSize правильно анализируется и отображается.
34 ut_tcpclient_progressBar TcpClient progressBar инициализирован 0.
35 ut_tcpclient_btnCancel TcpClient btnCancel не является отмечаемой кнопкой (Checkable-QToolButton).
36 ut_tcpclient_btnCancel_emit TcpClient Каждый раз, когда пользователь нажимает кнопку отмены, сигнал (нажатие кнопки) срабатывает правильно и единожды.
37 ut_tcpclient_btnSave TcpClient btnSave не является отмечаемой кнопкой (Checkable-QToolButton).
38 ut_tcpclient_btnSave_emit TcpClient Каждый раз, когда пользователь нажимает кнопку сохранения, сигнал (нажатие кнопки) срабатывает правильно и единожды.
39 ut_tcpclient_closeEvent_emit TcpClient Запускает событие закрытия, когда пользователь закрывает окно.
40 ut_tcpclient_textBrowser TcpClient textBrowser доступен только для чтения, запись запрещена.
41 ut_tcpserver_lbClientIP TcpServer Содержимое lbClientIP правильно анализируется и отображается.
42 ut_tcpserver_lbClientPort TcpServer Содержимое lbClientPort правильно анализируется и отображается.
43 ut_tcpserver_lbServerIP TcpServer Содержимое lbServerIP правильно анализируется и отображается.
44 ut_tcpserver_lbServerPort TcpServer Содержимое lbServerPort правильно анализируется и отображается.
45 ut_tcpserver_lbFilePath TcpServer Содержимое lbFilePath правильно анализируется и отображается.
46 ut_tcpserver_lbFileSize TcpServer Содержимое lbFileSize правильно анализируется и отображается.
47 ut_tcpserver_btnCancel TcpServer btnCancel не является отмечаемой кнопкой (Checkable-QToolButton).
48 ut_tcpserver_btnCancel_emit TcpServer Каждый раз, когда пользователь нажимает кнопку отмены, сигнал (нажатие кнопки) срабатывает правильно и единожды.
49 ut_tcpserver_progressBar TcpServer progressBar инициализирован 0.
50 ut_tcpserver_closeEvent_emit TcpServer Запускает событие закрытия, когда пользователь закрывает окно.
51 ut_tcpserver_textBrowser TcpServer textBrowser доступен только для чтения, запись запрещена.
52 ut_chatbox_title ChatBox Заголовок окна ChatBox инициализирован корректно (соответствует формату).
53 ut_chatbox_btnBold ChatBox btnBold является отмечаемой кнопкой (Checkable-QToolButton), а отображение текста отключено.
54 ut_chatbox_btnItalic ChatBox btnItalic является отмечаемой кнопкой (Checkable-QToolButton), а отображение текста отключено.
55 ut_chatbox_btnUnderLine ChatBox btnUnderLine является отмечаемой кнопкой (Checkable-QToolButton), а отображение текста отключено.
56 ut_chatbox_btnColor ChatBox btnColor не является отмечаемой кнопкой (Checkable-QToolButton), а отображение текста отключено.
57 ut_chatbox_btnFileSend ChatBox btnFileSend не является отмечаемой кнопкой (Checkable-QToolButton), а отображение текста отключено.
58 ut_chatbox_btnSave ChatBox btnSave не является отмечаемой кнопкой (Checkable-QToolButton), а отображение текста отключено.
59 ut_chatbox_btnClean ChatBox btnClean не является отмечаемой кнопкой (Checkable-QToolButton), а отображение текста отключено.
60 ut_chatbox_btnExit ChatBox btnExit не является отмечаемой кнопкой (Checkable-QToolButton).
61 ut_chatbox_btnSend ChatBox btnSend не является отмечаемой кнопкой (Checkable-QToolButton).
62 ut_chatbox_btnBold_emit ChatBox Нажатие btnBold активирует сигнал (нажатие кнопки), и состояние изменяется на «включено»; нажатие еще раз возвращает в исходное состояние.
63 ut_chatbox_btnItalic_emit ChatBox Нажатие btnItalic активирует сигнал (нажатие кнопки), и состояние изменяется на «включено»; нажатие еще раз возвращает в исходное состояние.
64 ut_chatbox_btnUnderLine_emit ChatBox Нажатие btnUnderLine активирует сигнал (нажатие кнопки), и состояние изменяется на «включено»; нажатие еще раз возвращает в исходное состояние.
65 ut_chatbox_btnColor_emit ChatBox Нажатие btnColor запускает сигнал (нажатие кнопки).
66 ut_chatbox_btnFileSend_emit ChatBox Нажатие btnFileSend запускает сигнал (нажатие кнопки).
67 ut_chatbox_btnSave_emit ChatBox Нажатие кнопки btnSave запускает сигнал (нажатие кнопки).
68 ut_chatbox_btnClean_emit ChatBox Нажатие кнопки btnClean запускает сигнал (нажатие кнопки).
69 ut_chatbox_btnExit_emit ChatBox Нажатие кнопки btnExit запускает сигнал (нажатие кнопки).
70 ut_chatbox_btnSend_emit ChatBox Нажатие кнопки btnSend запускает сигнал (нажатие кнопки).
71 ut_chatbox_msgTextBrowser ChatBox msgTextBrowser доступен только для чтения.
72 ut_chatbox_msgTextEdit ChatBox msgTextEdit позволяет писать.
73 ut_chatbox_init_cbxFontSize ChatBox Начальный шрифт 12.
74 ut_chatbox_cbxFontSize_min_max ChatBox Минимальный размер шрифта 10, максимальный размер 28.
75 ut_chatbox_closeEvent_emit ChatBox Событие close срабатывает, когда пользователь закрывает окно (выход).
76 ut_chatbox_tbUser ChatBox Ширина списка пользователей составляет 150-350.
77 ut_chatbox_lbNumberOnlineUse ChatBox UI инициализация онлайн-расчета численности в правильном формате.

Результаты прохождения тестов и оценка покрытия кода тестами:

image-20230218125527470

Интеграционное тестирование

Интеграционные тесты были проведены на следующих модулях: Login, Add Chat, ChatList, ChatBox, TcpClient, TcpServer. В Qt инициализируется (вызывается) одно окно, чтобы изолировать внешнюю среду и исключить возможное влияние других окон. А в некоторых из этих тестов была проведена имитация использования пользователем клавиатурного ввода, чтобы отразить реалистичный сценарий использования.

Были применены несколько техник тест-дизайна:

  • классы эквивалентности

    void PolyChatTester::mt_chatlist_leSearch_change_emit()
    {
        DAL::initLocalUser("Fox", "3530904/90102");
        ChatList widget(nullptr, DAL::getLocalUserName(), DAL::getLocalUserGroupNumber(), DAL::getLocalIpAddress());
        QLineEdit* lineEdit = widget.findChild<QLineEdit*>("leSearch");
    
        QSignalSpy spy(lineEdit, &QLineEdit::textEdited);
        QTest::keyClicks(lineEdit, "90111");
        QCOMPARE(spy.count(), 5);
    }
  • попарное тестирование

    void PolyChatTester::mt_chatbox_save_empty()
    {
        QTimer* timer = new QTimer(this);
        connect(timer, &QTimer::timeout, this, [=](){
            QWidgetList topWidgets = QApplication::topLevelWidgets();
            foreach (QWidget *w, topWidgets) {
                if (QMessageBox *mb = qobject_cast<QMessageBox *>(w)) {
                    QTest::keyClick(mb, Qt::Key_Enter);
                } else if (QFileDialog* dialog = qobject_cast<QFileDialog *>(w)) {
                    QTest::keyClick(dialog, Qt::Key_Cancel);
                } else if (QColorDialog* dialog = qobject_cast<QColorDialog *>(w)) {
                    QTest::keyClick(dialog, Qt::Key_Enter);
                } else {
                    w->close();
                }
            }
        });
      
        timer->start(50);
        ChatBoxWidget chatBox(nullptr, "3530409/90102", 2333);
    
        QTextBrowser* msgTextBrowser = chatBox.findChild<QTextBrowser*>("msgTextBrowser");
        QCOMPARE(msgTextBrowser->toPlainText(), "");
    
    
        QToolButton* button = chatBox.findChild<QToolButton*>("btnSave");
        QTest::mouseClick(button, Qt::LeftButton);
        timer->stop();
    }

Тестирования:

Название тестирования Связанные модули Описание (ожидаемые результаты)
78 mt_login_init_success Login Имитация использования пользователем клавиатуры для ввода текста в поле ввода, а затем нажатие кнопки входа в систему (вход выполнен успешно, и информация о локальном пользователе правильно инициализирована).
79 mt_login_leUserName Login Имитация щелчка пользователя и ввода с клавиатуры, чтобы убедиться, что вводимый контент правильно отображается в поле ввода leUserName.
80 mt_login_leUserGroupNumber Login Имитация щелчка пользователя и ввода с клавиатуры, чтобы убедиться, что вводимый контент правильно отображается в поле ввода leUserGroupNumber.
81 mt_addchat_leNameNewChat Add Chat Имитация щелчка пользователя и ввода с клавиатуры, чтобы убедиться, что вводимое содержимое правильно отображается в поле ввода leNameNewChat.
82 mt_chatlist_leSearch_change_emit ChatList Сигнал инициируется нужное количество раз, когда пользователь меняет содержимое в поле поиска.
83 mt_chatlist_getNewBtn ChatList В соответствии с переданными параметрами создается новый объект кнопки с правильной информацией.
84 mt_chatlist_btnchat_exist ChatList ui интерфейс обновляется корректно при добавлении нового группового чата.
85 mt_chatbox_userjoin_list ChatBox Пользователь присоединился (список пользователей обновляется корректно).
86 mt_chatbox_userjoin_counter ChatBox Пользователь присоединился (счетчик правильно увеличивается и ui отображается в правильном формате и с правильным содержанием).
87 mt_chatbox_userjoin_msgTextBrowser ChatBox Пользователь присоединился (msgTextBrowser обновляется).
88 mt_chatbox_userleft_list ChatBox Пользователь вышел (список пользователей обновляется корректно).
89 mt_chatbox_userleft_counter ChatBox Пользователь вышел (счетчик правильно увеличивается и ui отображается в правильном формате и с правильным содержанием).
90 mt_chatbox_userleft_msgTextBrowser ChatBox Пользователь вышел (msgTextBrowser обновляется).
91 mt_chatbox_clean ChatBox Предупреждающее сообщение при очистке окна чата.
92 mt_chatbox_save_empty ChatBox Имитация нажатия симулированным пользователем кнопки "Save", когда в пустом поле чата нет содержимого (с предупреждающим сообщением).
93 mt_chatbox_send_success ChatBox Имитация ввода пользователем текста в поле чата с помощью клавиатуры и последующее нажатие кнопки отправки. Введенная информация правильно отображается в сводке окна сообщения (сообщение успешно отправлено).
94 mt_tcpclient TcpClient Все элементы управления интерфейса ui инициализируются правильным содержимым через интерфейс.
95 mt_tcpserver TcpServer Все элементы управления интерфейса ui инициализируются правильным содержимым через интерфейс.

Результаты интеграционного тестирования:

image-20230316201833405

Системное/End-to-End тестирование

Более подробную информацию о непрерывной интеграции и настройке триггеров в GitHub см. в разделе "Интеграционное тестирование".

Название тестирования Сценарии пользователя и ожидаемые результаты
96 e2e_add_new_chat 1. Открытие программы пользователем
2. Пользователь щелкает мышью по полю ввода имени пользователя и вводит содержимое с помощью клавиатуры
3. Пользователь щелкает мышью по полю ввода номера группы и вводит содержимое с помощью клавиатуры
4. Пользователь щелкает мышью по кнопке входа в систему
5. Пользователь щелкает мышью по кнопке "Добавить новый групповой чат"
6. Пользователь вводит текст в появившемся диалоговом окне 'Add chat'
7. Пользователь нажимает кнопку Подтвердить, чтобы добавить новый групповой чат
Результат: данные пользователя успешно инициализированы и список чатов обновлен
97 e2e_join_chat 1-4. Пользователь входит в систему
5. Пользователь щелкает мышью по кнопке чата в списке чатов
Результат: у пользователя открыто окно, он находится в списке пользователей чата и сообщение о его входе отправлено в чат
98 e2e_search_chat 1-4. Пользователь входит в систему
5. Пользователь щелкает мышью по строке поиска чатов и вводит название с помощью клавиатуры
6. Пользователь щелкает мышью по кнопке чата в списке найденных чатов
Результат: поле поиска послало правильное количество сигналов, список групповых чатов обновлен и пользователь вошел в найденный им чат
99 e2e_send_message 1-4. Пользователь входит в систему
5. Пользователь щелкает мышью по кнопке чата в списке чатов
6. Пользователь щелкает по полю чата и вводит содержимое с помощью клавиатуры
7. Пользователь нажимает кнопку Отправить
Результат: в чате появилось сообщение, отправленное пользователем
100 e2e_send_special_message 1-4. Пользователь входит в систему
5. Пользователь щелкает мышью по кнопке чата в списке чатов
6. Пользователь щелкает по полю чата и вводит содержимое с помощью клавиатуры
7. Пользователь нажимает на кнопку изменения стиля текста (жирный, наклонный и т.д.)
8. Пользователь нажимает кнопку Отправить
Результат: в чате появилось особое сообщение, отправленное пользователем, с теми параметрами, которые указал пользователь
101 e2e_send_file 1-4. Пользователь входит в систему
5. Пользователь щелкает мышью по кнопке чата в списке чатов
6. Пользователь нажимает кнопку Отправить файл
7. Пользователь выбирает файл для отправки
8. Пользователь нажимает кнопку Отправить
Результат: у пользователя появилось окно отправки с корректной информацией о файле
102 e2e_receive_file 1-4. Пользователь входит в систему
5. Пользователь щелкает мышью по кнопке чата в списке чатов
6. Пользователь нажимает кнопку Да, когда появляется диалоговое окно с вопросом о приеме файла
7. Пользователь выбирает каталог и имя файла, куда сохранится полученный файл
8. Пользователь нажимает кнопку Сохранить
Результат: у пользователя появилось окно получения с корректной информацией о файле и сигнал от кнопки Сохранить успешно отправлен
103 e2e_clean_chat 1-4. Пользователь входит в систему
5. Пользователь щелкает мышью по кнопке чата в списке чатов
6. Пользователь нажимает кнопку Очистить чат
Результат: чат очищен
104 e2e_save_chat 1-4. Пользователь входит в систему
5. Пользователь щелкает мышью по кнопке чата в списке чатов
6. Пользователь нажимает кнопку Сохранить чат
Результат: вся история сообщений сохранена и сигнал от кнопки Сохранить успешно отправлен
105 e2e_leave_chat 1-4. Пользователь входит в систему
5. Пользователь щелкает мышью по кнопке чата в списке чатов
6. Пользователь щелкает по полю чата и вводит содержимое с помощью клавиатуры
7. Пользователь нажимает кнопку Отправить
8. Другой пользователь входит в групповой чат
9. Другой пользователь выходит из группового чата
10. Пользователь нажимает кнопку выхода из чата
Результат: сообщение о выходе пользователя появилось в чате, пользователь пропал из списка пользователей и у пользователя закрылось окно чата

Результаты E2E тестирования на GitHub:

image-20230316204636445

Другое тестирование

Название тестирования Тип тестирования Связанные модули Описание (ожидаемые результаты)
106 pt_Login_load Тестирование производительности Login Производительность загрузки/вызова окон.
107 pt_AddChat_load Тестирование производительности Add Chat Производительность загрузки/вызова окон.
108 pt_ChatList_load Тестирование производительности ChatList Производительность загрузки/вызова окон.
109 pt_TcpClient_load Тестирование производительности TcpClient Производительность загрузки/вызова окон.
110 pt_TcpServer_load Тестирование производительности ChatBox Производительность загрузки/вызова окон.
111 pt_ChatBox_load Тестирование производительности ChatBox Производительность загрузки/вызова окон.
112 pt_ChatBox_userjoin Тестирование производительности ChatBox Пользователи входят в групповой чат.
113 pt_ChatBox_userjoin_left Тестирование производительности ChatBox Пользователи входят в групповой чат и выходят из него вместе с другими пользователями.
114 pt_ChatBox_msgTextEdit_input Тестирование производительности ChatBox Имитация ввода пользователем с клавиатуры 100 символов в msgTextEdit и последующее нажатие кнопки отправить.
115 pt_Login_to_system Тестирование производительности Login/ChatList Пользователи вводят свое имя и номер группы с клавиатуры, а затем нажимают кнопку входа для доступа к системе (ChatList).
116 pt_AddChat_ui Тестирование производительности Add Chat/ChatBox Добавление нового группового чата путем имитации ввода пользователем команды Add Chat и последующего нажатия кнопки Подтвердить (Тестирование производительности при создании нового окна группового чата).
117 lt_ChatBox_x100 Нагрузочное тестирование ChatBox Пользователь вступает в 100 групповых чатов.
118 lt_ChatBox_200user Нагрузочное тестирование ChatBox Гарантированная возможность подключения 200 пользователей в один чат.
119 lt_ChatBox_2000char Нагрузочное тестирование ChatBox Пользователь отправляет 2000 символов.
120 lt_ChatBox_msg_change Нагрузочное тестирование ChatBox Пользователь вводит сообщение, а затем изменяет стиль шрифта (bold, italic).
121 lt_TcpServer_x10 Нагрузочное тестирование TcpServer Пользователь вызывает 10 окон отправки файлов TcpServer (отправляет 10 файлов).
122 lt_TcpClient_x10 Нагрузочное тестирование TcpClient Пользователь вызывает 10 окон отправки файлов TcpClient (получает 10 файлов).
123 ct_ChatBox_code_normal Тестирование на совместимость ChatBox Имитация ввода пользователем английского, китайского и русского языков в msgTextEdit с клавиатуры. При этом ui отображается корректно, без искажений.
124 GitHub CI Кросс-платформенное тестирование (автоматизированное тестирование) ALL Гарантированная компиляция и работа на macOS и Windows.
125 GitHub CI Автоматическое тестирование ALL Автоматизированная компиляция и тестирование на macOS и Windows.
126 GitHub CI Тестирование на совместимость ALL Скомпилируйте в Qt6.2.2 и убедитесь, что минимальная версия для macOS - 10.12; минимальная версия для Windows - 2019.

Количество тестов:

Тип тестирования Количество
Модульное тестирование 77
Интеграционное тестирование 18
Системное/End-to-End тестирование 10
Другое тестирование 21
Всего 126

Результаты всех тестов:

image-20230316204255954

image-20230316221004870

Результаты локального тестирования

Тестирование проводилось на MacBook Pro (чип Apple M1 Pro) с установленной операционной системой MacOS 14.

image-20231024155247741

CI/CD

CI/CD строится через Github Action. Данный процесс CI/CD можно разделить на две части: Continuous Integration (CI) и Continuous Deployment (CD).

CI (непрерывная интеграция) - первая часть процесса. Она отвечает за автоматическую сборку и тестирование вашего кода при каждом коммите или запросе на объединение (pull request). Это помогает выявить и устранить проблемы в коде на ранних этапах разработки, обеспечивая стабильность и качество вашего приложения.

CD (непрерывная доставка/развертывание) - вторая часть процесса. Когда CI успешно завершается, CD берет на себя автоматическое развертывание вашего приложения на целевых серверах или платформах. Это обеспечивает быструю и надежную поставку новых версий вашего приложения конечным пользователям.

CI (Непрерывная интеграция)

  1. События, на которые реагирует CI:

    Использует сервер непрерывной интеграции GitHub для запуска триггеров при внесении изменений в код и запуска автоматической компиляции и автоматического тестирования.

    • MacOS

      1. Пуш изменений в репозиторий в следующих директориях: 'App/', 'Tester/', '.github/workflows/macos.yml'
      2. Открытие pull и pull_request с изменениями в тех же директориях.
      name: macOS Build and Test
      on: 
        push:
          paths:
            - 'App/**'
            - 'Tester/**'
            - '.github/workflows/macos.yml'
        pull_request:
          paths:
            - 'App/**'
            - 'Tester/**'
            - '.github/workflows/macos.yml' 
    • Windows

      1. Пуш изменений в репозиторий в следующих директориях: 'App/', 'Tester/', '.github/workflows/windows.yml', 'scripts/'
      2. Открытие pull и pull_request с изменениями в тех же директориях.
      name: Windows CI/CD
      on: 
        push:
          paths:
            - 'App/**'
            - 'Tester/**'
            - '.github/workflows/windows.yml'
            - 'scripts/**'
        pull_request:
          paths:
            - 'App/**'
            - 'Tester/**'
            - '.github/workflows/windows.yml'
            - 'scripts/**'
  2. Задача (Job) CI:

    • MacOS

      • Наименование: macOS-CI-CD

      • Выполнение на операционной системе: macOS 11.0

      • Стратегия: Матрица, включающая разные версии Qt (6.2.2 и 6.6.0) и архитектуры (clang_64)

      • Переменные окружения: Настройки для сборки Qt-приложения.

    • Windows

      • Наименование: Windows-CI-CD

      • Выполнение на операционной системе: win64_msvc2019_64

      • Стратегия: Матрица, включающая разные версии Qt (6.2.2 и 6.6.0) и архитектуры (msvc2019_64)

      • Переменные окружения: Настройки для сборки Qt-приложения.

  3. Шаги CI:

    • Подготовка окружения: Обновление и установка необходимых компонентов для macOS 11.0 или Windows 2019.

    • Установка Qt: Загрузка и установка выбранной версии Qt.

    • Получение исходного кода: Клонирование репозитория и получение исходного кода.

    • Тестирование на macOS/Windows: Сборка и запуск тестов с использованием QTest.

    • Сборка на macOS/Windows: Сборка версии приложения для macOS.

    • Упаковка: Упаковка приложения в dmg/zip-файл.

    • Загрузка артефактов: Загрузка созданных файлов как артефактов CI.

iShot_2023-10-24_16.07.04

image-20231024164656277

image-20231024160959353

CD (непрерывная доставка)

Непрерывный выпуск осуществляется как на платформе MacOS, так и на платформе Windows.

  • Условие для выполнения CD:

    • Событие создания нового тега (версии) в репозитории.
  • Шаги CD:

    • Загрузка релиза: Загрузка dmg/zip-файла в релизе GitHub, связанного с созданным тегом (версией) репозитория.

Для MacOS:

  1. Упаковка (package): На MacOS можем удобно упаковать проект в файл формата .dmg с помощью macdeployqt

    - name: Package on MacOS
      run: |
        cd ./${QtApplicationName}
        macdeployqt ${QtApplicationName}.app -qmldir=. -verbose=1 -dmg
  2. Загрузка артефакта (artifact) на сервер GitHub Actions

    - uses: actions/upload-artifact@v2
      with:
        name: ${{ env.targetName }}_${{ matrix.os }}_${{matrix.qt_ver}}.zip
        path: ${{ env.QtApplicationName }}/${{ env.QtApplicationName }}.app
  3. Если событие является событием тегов (tag), загрузите продукт сборки (файл .dmg) на GitHub Release

    - name: Upload Release
      if: startsWith(github.event.ref, 'refs/tags/')
      uses: svenstaro/upload-release-action@v2
      with:
        repo_token: ${{ secrets.GITHUB_TOKEN }}
        file: ${{ env.QtApplicationName }}/${{ env.QtApplicationName }}.dmg
        asset_name: ${{ env.targetName }}_${{ matrix.os }}_${{ matrix.qt_ver }}.dmg
        tag: ${{ github.ref }}
        overwrite: true

Для Windows:

  1. Упаковка нескольких версий под Windows осуществляется с помощью скрипта windows-publish.ps1, который мы написали

    - name: package
      id: package
      env:
        archiveName: ${{ matrix.qt_ver }}-${{ matrix.qt_target }}-${{ matrix.qt_arch }}
        msvcArch: ${{ matrix.msvc_arch }}          
      shell: pwsh
      run: |
        & scripts\windows-publish.ps1 ${env:archiveName} ${env:QtApplicationName}
        # Запишите название пакета для последующего шага
        $name = ${env:archiveName}
        echo "::set-output name=packageName::$name"

    скрипт windows-publish.ps1

    [CmdletBinding()]
    param (
        [string] $archiveName, [string] $targetName
    )
    # К переменным внешней среды относятся:
    # archiveName: ${{ matrix.qt_ver }}-${{ matrix.qt_arch }}
    # winSdkDir: ${{ steps.build.outputs.winSdkDir }}
    # winSdkVer: ${{ steps.build.outputs.winSdkVer }}
    # vcToolsInstallDir: ${{ steps.build.outputs.vcToolsInstallDir }}
    # vcToolsRedistDir: ${{ steps.build.outputs.vcToolsRedistDir }}
    # msvcArch: ${{ matrix.msvc_arch }}
    
    
    # winSdkDir: C:\Program Files (x86)\Windows Kits\10\ 
    # winSdkVer: 10.0.19041.0\ 
    # vcToolsInstallDir: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29333\ 
    # vcToolsRedistDir: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Redist\MSVC\14.28.29325\ 
    # archiveName: 6.0.0-win64_msvc2019
    # msvcArch: x64
    
    $scriptDir = $PSScriptRoot
    $currentDir = Get-Location
    Write-Host "currentDir" $currentDir
    Write-Host "scriptDir" $scriptDir
    
    function Main() {
    
        New-Item -ItemType Directory $archiveName
    
        # Копирование exe
        Copy-Item .\App\release\$targetName $archiveName\
        Write-Host "[INFO] Copy-Item from .\App\release\" $targetName " to " $archiveName "done"
    
        # Копирование зависимостей
        windeployqt --qmldir . --plugindir $archiveName\plugins --no-translations --compiler-runtime $archiveName\$targetName
        Write-Host "[INFO] windeployqt done"
    
        # Удаление ненужных файлов
        $excludeList = @("*.qmlc", "*.ilk", "*.exp", "*.lib", "*.pdb")
        Remove-Item -Path $archiveName -Include $excludeList -Recurse -Force
        Write-Host "[INFO] Remove-Item done"
    
        # Копирование vcRedist dll
        $redistDll="{0}{1}\*.CRT\*.dll" -f $env:vcToolsRedistDir.Trim(),$env:msvcArch
        Copy-Item $redistDll $archiveName\
        Write-Host "[INFO] Copy-Item vcRedist dll done"
    
        # Копирование WinSDK dll
        $sdkDll="{0}Redist\{1}ucrt\DLLs\{2}\*.dll" -f $env:winSdkDir.Trim(),$env:winSdkVer.Trim(),$env:msvcArch
        Copy-Item $sdkDll $archiveName\
        Write-Host "[INFO] Copy-Item WinSDK dll done"
    
        # Упаковка в виде zip
        Compress-Archive -Path $archiveName $archiveName'.zip'
        Write-Host "[INFO] Compress-Archive done"
    }
    
    if ($null -eq $archiveName || $null -eq $targetName) {
        Write-Host "args missing, archiveName is" $archiveName ", targetName is" $targetName
        return
    }
    Main
  2. Загрузка артефакта (artifact) на сервер GitHub Actions

    - uses: actions/upload-artifact@v2
      with:
        name: ${{ env.targetName }}_${{ steps.package.outputs.packageName }}
        path: ${{ steps.package.outputs.packageName }}
  3. Если событие является событием тегов (tag), загрузите продукт сборки (файл .zip) на GitHub Release

    - name: uploadRelease
      if: startsWith(github.event.ref, 'refs/tags/')
      uses: svenstaro/upload-release-action@v2
      with:
        repo_token: ${{ secrets.GITHUB_TOKEN }}
        file: ${{ steps.package.outputs.packageName }}.zip
        asset_name: ${{ env.targetName }}_${{ steps.package.outputs.packageName }}.zip
        tag: ${{ github.ref }}
        overwrite: true

Эти процессы автоматизирует сборку, тестирование, упаковку и выпуск приложения на macOS/Windows. Новый релиз создается автоматически при создании нового тега, и dmg/zip-файл приложения загружается в релиз, что позволяет легко распространять приложение пользователям.

Как видно на изображении ниже, приложение было успешно упаковано и опубликовано как на MacOS, так и на Windows.

image-20231024165220391

image-20231024170159500

image-20231025141122572

Код

MacOS

name: macOS CI/CD
on: 
  push:
    paths:
      - 'App/**'
      - 'Tester/**'
      - '.github/workflows/macos.yml'
  pull_request:
    paths:
      - 'App/**'
      - 'Tester/**'
      - '.github/workflows/macos.yml' 
jobs:
  build:
    name: macOS-CI-CD
    runs-on: ${{ matrix.os }}
    strategy:
      # Матрица, включающая разные версии Qt (6.2.2 и 6.6.0) и архитектуры (clang_64)
      matrix:
        os: [macos-11.0]
        qt_ver: [6.2.2, 6.6.0]
        qt_arch: [clang_64]
    env:
      targetName: PolyChat
      # TARGET в файле Qt pro
      QtApplicationName: App
    steps:
      # изменилось окружение по умолчанию macos 11.0, нужно указать
      - name: prepare env
        if: ${{ matrix.os == 'macos-11.0' }}
        run: |
          softwareupdate --all --install --force
          sudo xcode-select --print-path
          sudo xcode-select --switch /Library/Developer/CommandLineTools
      # https://ddalcino.github.io/aqt-list-server/
      - name: Install Qt
        uses: jurplel/install-qt-action@v3
        with:
          version: ${{ matrix.qt_ver }}
          cached: 'false'
          aqtversion: '==3.1.*'
          # host: 'mac'
          # modules: 'qtnetworkauth'
          # tools: 'tools_qtcreator'
      - uses: actions/checkout@v2
        with:
          fetch-depth: 1
      # Тестирование под MacOS (с использованием QTest)
      - name: Test on macOS 
        run: |
          echo '-------------------'
          cd ./Tester
          echo '-------------------'
          qmake CONFIG+=debug
          make
          ls
          ./PolyChatTester -v2 -txt
          echo '\n\n==============================./PolyChatTester  -txt==============================\n\n'
          ./PolyChatTester -txt
      # Скомпилировать версию `Release`
      - name: Build on macOS 
        run: |
          ls
          cd ./${QtApplicationName}
          qmake
          make
      # Упаковка
      - name: Package on MacOS
        run: |
          cd ./${QtApplicationName}
          # mv ./${QtApplicationName}/${QtApplicationName}.app .
          echo '------------------'
          ls
          macdeployqt ${QtApplicationName}.app -qmldir=. -verbose=1 -dmg
      # Загрузка artifacts
      - uses: actions/upload-artifact@v2
        with:
          name: ${{ env.targetName }}_${{ matrix.os }}_${{matrix.qt_ver}}.zip
          path: ${{ env.QtApplicationName }}/${{ env.QtApplicationName }}.app
      # загрузка тега Release
      - name: Upload Release
        if: startsWith(github.event.ref, 'refs/tags/')
        uses: svenstaro/upload-release-action@v2
        with:
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          file: ${{ env.QtApplicationName }}/${{ env.QtApplicationName }}.dmg
          asset_name: ${{ env.targetName }}_${{ matrix.os }}_${{ matrix.qt_ver }}.dmg
          tag: ${{ github.ref }}
          overwrite: true

Windows

name: Windows CI/CD
on: 
  push:
    paths:
      - 'App/**'
      - 'Tester/**'
      - '.github/workflows/windows.yml'
      - 'scripts/**'
  pull_request:
    paths:
      - 'App/**'
      - 'Tester/**'
      - '.github/workflows/windows.yml'
      - 'scripts/**'
jobs:
  build:
    name: Windows-CI-CD
    # справочный документ https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md
    runs-on: windows-2019
    strategy:
      # Стратегия: Матрица, включающая разные версии Qt (6.2.2 и 6.6.0) и архитектуры (msvc2019_64)
      matrix:
        include:
          - qt_ver: 6.2.2
            qt_arch: win64_msvc2019_64
            msvc_arch: x64
            qt_arch_install: msvc2019_64
          - qt_ver: 6.6.0
            qt_arch: win64_msvc2019_64
            msvc_arch: x64
            qt_arch_install: msvc2019_64
    env:
      targetName: PolyChat
      QtApplicationName: App.exe
    steps:
      - name: Install Qt
        uses: jurplel/install-qt-action@v3
        with:
          # Version of Qt to install
          version: ${{ matrix.qt_ver }}
          arch: ${{ matrix.qt_arch }}
          cached: 'false'
          # aqtversion: '==2.0.5'
          aqtversion: '==3.1.*'
          # host: 'windows'
          # target: 'desktop'
          # toolsOnly: 'true'
          # modules: 'qtnetworkauth'
          # tools: 'tools_qtcreator_gui'
      - uses: actions/checkout@v2
        with:
          fetch-depth: 1
      # Тестирование под Windows (с использованием QTest)
      - name: msvc-test
        id: test
        shell: cmd
        run: |
          call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.msvc_arch }}
          cd ./Tester
          qmake CONFIG+=debug
          nmake
          echo winSdkDir=%WindowsSdkDir% >> %GITHUB_ENV%
          echo winSdkVer=%WindowsSdkVersion% >> %GITHUB_ENV%
          echo vcToolsInstallDir=%VCToolsInstallDir% >> %GITHUB_ENV%
          echo vcToolsRedistDir=%VCToolsRedistDir% >> %GITHUB_ENV%
      # Скомпилировать версию `Release`
      - name: msvc-build
        id: build
        shell: cmd
        run: |
          call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.msvc_arch }}
          cd ./${QtApplicationName}
          qmake
          nmake
          echo winSdkDir=%WindowsSdkDir% >> %GITHUB_ENV%
          echo winSdkVer=%WindowsSdkVersion% >> %GITHUB_ENV%
          echo vcToolsInstallDir=%VCToolsInstallDir% >> %GITHUB_ENV%
          echo vcToolsRedistDir=%VCToolsRedistDir% >> %GITHUB_ENV%
          ls
          tree /F
      # Упаковка
      - name: package
        id: package
        env:
          archiveName: ${{ matrix.qt_ver }}-${{ matrix.qt_target }}-${{ matrix.qt_arch }}
          msvcArch: ${{ matrix.msvc_arch }}          
        shell: pwsh
        run: |
          tree D: /F
          echo '------- Run scripts\windows-publish.ps1'
          & scripts\windows-publish.ps1 ${env:archiveName} ${env:QtApplicationName}
          echo '------- Finish scripts windows-publish.ps1'
          $name = ${env:archiveName}
          echo "::set-output name=packageName::$name"
       # Загрузка artifacts
      - uses: actions/upload-artifact@v2
        with:
          name: ${{ env.targetName }}_${{ steps.package.outputs.packageName }}
          path: ${{ steps.package.outputs.packageName }}
      # загрузка тега Release
      - name: uploadRelease
        if: startsWith(github.event.ref, 'refs/tags/')
        uses: svenstaro/upload-release-action@v2
        with:
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          file: ${{ steps.package.outputs.packageName }}.zip
          asset_name: ${{ env.targetName }}_${{ steps.package.outputs.packageName }}.zip
          tag: ${{ github.ref }}
          overwrite: true

Windows scripts:

[CmdletBinding()]
param (
    [string] $archiveName, [string] $targetName
)
# archiveName: ${{ matrix.qt_ver }}-${{ matrix.qt_arch }}
# winSdkDir: ${{ steps.build.outputs.winSdkDir }}
# winSdkVer: ${{ steps.build.outputs.winSdkVer }}
# vcToolsInstallDir: ${{ steps.build.outputs.vcToolsInstallDir }}
# vcToolsRedistDir: ${{ steps.build.outputs.vcToolsRedistDir }}
# msvcArch: ${{ matrix.msvc_arch }}


# winSdkDir: C:\Program Files (x86)\Windows Kits\10\ 
# winSdkVer: 10.0.19041.0\ 
# vcToolsInstallDir: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29333\ 
# vcToolsRedistDir: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Redist\MSVC\14.28.29325\ 
# archiveName: 6.0.0-win64_msvc2019
# msvcArch: x64

$scriptDir = $PSScriptRoot
$currentDir = Get-Location
Write-Host "currentDir" $currentDir
Write-Host "scriptDir" $scriptDir

function Main() {

    New-Item -ItemType Directory $archiveName

    Copy-Item .\App\release\$targetName $archiveName\
    Write-Host "[INFO] Copy-Item from .\App\release\" $targetName " to " $archiveName "done"

    windeployqt --qmldir . --plugindir $archiveName\plugins --no-translations --compiler-runtime $archiveName\$targetName
    Write-Host "[INFO] windeployqt done"

    $excludeList = @("*.qmlc", "*.ilk", "*.exp", "*.lib", "*.pdb")
    Remove-Item -Path $archiveName -Include $excludeList -Recurse -Force
    Write-Host "[INFO] Remove-Item done"

    $redistDll="{0}{1}\*.CRT\*.dll" -f $env:vcToolsRedistDir.Trim(),$env:msvcArch
    Copy-Item $redistDll $archiveName\
    Write-Host "[INFO] Copy-Item vcRedist dll done"

    $sdkDll="{0}Redist\{1}ucrt\DLLs\{2}\*.dll" -f $env:winSdkDir.Trim(),$env:winSdkVer.Trim(),$env:msvcArch
    Copy-Item $sdkDll $archiveName\
    Write-Host "[INFO] Copy-Item WinSDK dll done"

    Compress-Archive -Path $archiveName $archiveName'.zip'
    Write-Host "[INFO] Compress-Archive done"
}

if ($null -eq $archiveName || $null -eq $targetName) {
    Write-Host "args missing, archiveName is" $archiveName ", targetName is" $targetName
    return
}
Main