Санкт-Петербургский государственный политехнический университет
Институт компьютерных наук и технологий
PolyChat
Кроссплатформенный чат для локальной сети, основанный на разработке QT
Содержание
- Требования
- Функциональная спецификация
- HLD
- Тестирование
- 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 | При нажатии на кнопку полного экрана окно разворачивается на весь экран |
Мы используем диаграмму Use Case для представления архитектуры программного обеспечения PolyChat:
В PolyChat существует 3 класса: список групп, группа и пользователь. Их взаимосвязь показана на диаграмме ниже:
Это приложение реализует следующую функциональность: в пределах локальной сети пользователи могут добавить (создать) групповой чат, и когда групповой чат создан, список групповых чатов на других хостах синхронизируется и обновляется (через UDP).
После добавления каждого пользователя в групповой чат, в правой части экрана группового чата может быть отображен список онлайн-пользователей, показывающий имя пользователя, номер класса и ip-адрес соответственно. Слева от формы отображается содержание чата, т.е. групповой чат. Каждый человек может ввести текст (и изменить формат текста) и отправить его с экрана ввода чата.
UDP-сообщения передаются по локальной сети с помощью механизма Broadcast
и для удобства использования дополнены ShareAddress
и ReuseAddressHint
, которые описаны ниже.
-
Broadcast
: широковещательная передача на все порты текущей локальной сети -
ShareAddress
: позволяет другим службам связываться с тем же адресом и портом. Это полезно, когда несколько процессов разделяют нагрузку на службу, прослушивая один и тот же адрес и порт (например, веб-сервер с несколькими предварительно вилочными слушателями может значительно улучшить время отклика). Однако, поскольку любой службе разрешено перепривязываться, этот вариант должен учитывать некоторые вопросы безопасности. Поэтому, комбинируя эту опцию сReuseAddressHint
. -
ReuseAddressHint
: он должен попытаться перепривязать сервис, даже если адрес и порт уже привязаны другим сокетом.
Для достижения вышеуказанной функциональности и обеспечения последующей расширяемости программы. Используется индивидуальный протокол связи, в котором 8 блоков.
-
Блок 1
: типы сообщений. В настоящее время существует 6 типов.ChatExist
: текущее окно группового чата существует и транслируется через регулярные промежутки времениChatDestory
: удаляет текущий групповой чат, отправляется, когда все (последние) участники группового чата покинули его.Msg
: обычное текстовое сообщениеFile
: запрос на отправку файлаUserJoin
: пользователь присоединяется к текущему групповому чатуUserLeft
: пользователь покидает текущий групповой чат
-
Блок 2
: название текущего группового чата -
Блок 3
: порт, на котором находится текущий групповой чат -
Блок 4
: имя пользователя, отправившего это сообщение -
Блок 5
: группы пользователя, отправившего это сообщение -
Блок 6
: IP-адрес пользователя, отправившего это сообщение -
Блок 7
: содержание сообщения (переменной длины) -
Блок 8
: содержание сообщения (переменной длины, используется для расширения параграфа 7)
Как показано на диаграмме ниже:
**Примечание: Система чата в PolyChat похожа на P2P, в том смысле, что пользователь может выступать как в роли сервера, так и в роли клиента. Один пользователь может выступать в качестве и сервера, и клиента. **
После создания UDP-сокета и привязки его к фиксированному порту, сервер отслеживает входящие данные, используя сигналы и слоты. Если появляется сообщение, он получает данные и анализирует тип сообщения. Если сообщение является входом нового пользователя, сервер обновляет список пользователей и добавляет уведомление о новом пользователе онлайн в окно отображения чата; аналогично, если пользователь находится в автономном режиме, сервер удаляет пользователя из списка пользователей и отображает уведомление об автономном режиме в окне отображения чата; если это сообщение чата, он получает сообщение и отображает его в окне. Технологическая схема выглядит следующим образом.
**Примечание: Система чата в PolyChat похожа на P2P, в том смысле, что пользователь может выступать как в роли сервера, так и в роли клиента. Один пользователь может выступать в качестве и сервера, и клиента. **
Когда пользователь присоединяется к групповому чату, имя пользователя, номер группы, имя хоста и ip-адрес получаются и передаются на LAN-сервер (ChatBoxWidget) для обновления списка пользователей справа. Затем, когда клиенту нужно отправить сообщение, он вводит его в поле ввода чата и нажимает кнопку отправки, чтобы отправить чат, одновременно транслируя различные сообщения из локальной системы. Технологическая схема выглядит следующим образом:
TCP использует специализированный протокол связи, в котором есть 4 части.
Части с 1 по 3 - это заголовки файлов, а часть 4 - собственно данные.
Часть 1
: Имя файлаЧасть 2
: размер файла (байт)Часть 3
: MD5 файлаЧасть 4
: данные (4 Кб на блок)
При передаче сервер сначала отправляет заголовок файла. Для предотвращения "липких" пакетов TCP, подождите 20 мс перед началом отправки блока данных
Сторона отправителя берет на себя роль сервера.
Нажмите на кнопку Отправить, выберите файл для отправки на локальном компьютере, нажмите на кнопку Отправить, появится экран Отправителя, затем индикатор выполнения покажет информацию о файле, который в данный момент отправляется, ход передачи, подключенного в данный момент клиента и другую информацию. Если вы хотите закрыть процесс отправки, нажмите кнопку Закрыть. Технологическая схема выглядит следующим образом:
Сторона получателя берет на себя роль клиента.
В интерфейсе появится диалоговое окно с вопросом, хотите ли вы принять сообщение о передаче файла от конкретного пользователя, если да, нажмите кнопку Yes, в противном случае нажмите кнопку No.
После того как вы выбрали каталог и имя файла, в котором вы хотите получить файл, вы начнете получать файл, в процессе также будет отображаться ход передачи файла, информация о получателе и клиенте. Интерфейс на принимающей стороне показан ниже:
Технологическая схема выглядит следующим образом:
Мы используем диаграмму потоков данных для представления обработки сообщений, полученных в окне группового чата PolyChat:
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 инициализация онлайн-расчета численности в правильном формате. |
Результаты прохождения тестов и оценка покрытия кода тестами:
Интеграционные тесты были проведены на следующих модулях: 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 инициализируются правильным содержимым через интерфейс. |
Результаты интеграционного тестирования:
Более подробную информацию о непрерывной интеграции и настройке триггеров в 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:
Название тестирования | Тип тестирования | Связанные модули | Описание (ожидаемые результаты) | |
---|---|---|---|---|
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 |
Результаты всех тестов:
Тестирование проводилось на MacBook Pro (чип Apple M1 Pro) с установленной операционной системой MacOS 14.
CI/CD строится через Github Action. Данный процесс CI/CD можно разделить на две части: Continuous Integration (CI) и Continuous Deployment (CD).
CI (непрерывная интеграция) - первая часть процесса. Она отвечает за автоматическую сборку и тестирование вашего кода при каждом коммите или запросе на объединение (pull request). Это помогает выявить и устранить проблемы в коде на ранних этапах разработки, обеспечивая стабильность и качество вашего приложения.
CD (непрерывная доставка/развертывание) - вторая часть процесса. Когда CI успешно завершается, CD берет на себя автоматическое развертывание вашего приложения на целевых серверах или платформах. Это обеспечивает быструю и надежную поставку новых версий вашего приложения конечным пользователям.
-
События, на которые реагирует CI:
Использует сервер непрерывной интеграции GitHub для запуска триггеров при внесении изменений в код и запуска автоматической компиляции и автоматического тестирования.
-
MacOS
- Пуш изменений в репозиторий в следующих директориях: 'App/', 'Tester/', '.github/workflows/macos.yml'
- Открытие 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
- Пуш изменений в репозиторий в следующих директориях: 'App/', 'Tester/', '.github/workflows/windows.yml', 'scripts/'
- Открытие 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/**'
-
-
Задача (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-приложения.
-
-
-
Шаги CI:
-
Подготовка окружения: Обновление и установка необходимых компонентов для macOS 11.0 или Windows 2019.
-
Установка Qt: Загрузка и установка выбранной версии Qt.
-
Получение исходного кода: Клонирование репозитория и получение исходного кода.
-
Тестирование на macOS/Windows: Сборка и запуск тестов с использованием QTest.
-
Сборка на macOS/Windows: Сборка версии приложения для macOS.
-
Упаковка: Упаковка приложения в dmg/zip-файл.
-
Загрузка артефактов: Загрузка созданных файлов как артефактов CI.
-
Непрерывный выпуск осуществляется как на платформе MacOS, так и на платформе Windows.
-
Условие для выполнения CD:
- Событие создания нового тега (версии) в репозитории.
-
Шаги CD:
- Загрузка релиза: Загрузка dmg/zip-файла в релизе GitHub, связанного с созданным тегом (версией) репозитория.
Для MacOS:
-
Упаковка (package): На MacOS можем удобно упаковать проект в файл формата .dmg с помощью
macdeployqt
- name: Package on MacOS run: | cd ./${QtApplicationName} macdeployqt ${QtApplicationName}.app -qmldir=. -verbose=1 -dmg
-
Загрузка артефакта (artifact) на сервер GitHub Actions
- uses: actions/upload-artifact@v2 with: name: ${{ env.targetName }}_${{ matrix.os }}_${{matrix.qt_ver}}.zip path: ${{ env.QtApplicationName }}/${{ env.QtApplicationName }}.app
-
Если событие является событием тегов (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:
-
Упаковка нескольких версий под 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
-
Загрузка артефакта (artifact) на сервер GitHub Actions
- uses: actions/upload-artifact@v2 with: name: ${{ env.targetName }}_${{ steps.package.outputs.packageName }} path: ${{ steps.package.outputs.packageName }}
-
Если событие является событием тегов (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.
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
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