Отдельные документы о тестировании
This commit is contained in:
@@ -2,70 +2,9 @@
|
|||||||
|
|
||||||
= Тестирование программного обеспечения
|
= Тестирование программного обеспечения
|
||||||
|
|
||||||
В ходе работы было организовано тестирование Wallenc на нескольких уровнях: модульные автоматические тесты (JUnit, каталог `src/test` каждого Gradle-модуля), инструментальные тесты (`src/androidTest`), а также ручные функциональные и UI-прогоны. Программа и методика испытаний приведены в приложении Б.
|
#include "testing/_intro.typ"
|
||||||
|
|
||||||
== План тестирования
|
#include "testing/01-plan.typ"
|
||||||
|
|
||||||
=== Цели и задачи испытаний
|
|
||||||
|
|
||||||
Основная цель — подтвердить корректность криптографического ядра, доменной логики синхронизации и сценариев UI. Были поставлены следующие задачи:
|
|
||||||
|
|
||||||
+ проверить `Encryptor` и проверку ключа для строк, байтов и потоков;
|
|
||||||
+ убедиться в корректном маппинге исключений в коды ошибок;
|
|
||||||
+ протестировать движок синхронизации (`StorageSyncEngine`, журнал, блокировки);
|
|
||||||
+ проверить оркестратор фоновых задач;
|
|
||||||
+ выполнить smoke-тесты навигации, deep link и 2FA/TOTP;
|
|
||||||
+ зафиксировать результаты ручных сценариев vault, OAuth и экрана задач.
|
|
||||||
|
|
||||||
=== Объект и уровни тестирования
|
|
||||||
|
|
||||||
#pz-table(
|
|
||||||
[Объекты и уровни тестирования Wallenc],
|
|
||||||
4,
|
|
||||||
table.header([Уровень], [Объект], [Инструмент], [Критерий успеха]),
|
|
||||||
[Unit], [Классы domain, usecases, ui, task-runtime, domain-vault], [JUnit 4, JVM], [Все тесты модуля успешны],
|
|
||||||
[Инструм.], [Room, Compose UI, OAuth], [AndroidJUnit, эмулятор], [Нет падений на целевом API],
|
|
||||||
[Ручной], [Сборка app, пользовательские цепочки], [Чек-лист], [Сценарии T-1…T-12 пройдены],
|
|
||||||
[Регресс.], [Синхронизация, шифрование], [Повтор unit + выборочный ручной], [Нет блокирующих дефектов],
|
|
||||||
) <tbl-test-levels>
|
|
||||||
|
|
||||||
=== Матрица тестовых сценариев
|
|
||||||
|
|
||||||
#pz-table(
|
|
||||||
[Матрица тестовых сценариев],
|
|
||||||
5,
|
|
||||||
table.header([ID], [Сценарий], [Тип], [Авто], [Ожидаемый результат]),
|
|
||||||
[T-1], [Проверка ключа шифрования], [Unit], [Да], [`Encryptor.checkKey` true/false],
|
|
||||||
[T-2], [Шифрование/дешифрование строки и байтов], [Unit], [Да], [Симметрия данных],
|
|
||||||
[T-3], [Потоковое шифрование файла], [Unit], [Да], [Данные после decrypt равны исходным],
|
|
||||||
[T-4], [Синхронизация группы хранилищ], [Unit], [Да], [Копирование, удаление, trash, блокировки],
|
|
||||||
[T-5], [2FA TOTP генерация], [Unit], [Да], [Совпадение с эталоном Java OTP],
|
|
||||||
[T-6], [Маппинг ошибок сети/диска], [Unit], [Да], [Типизированные `WallencException`],
|
|
||||||
[T-7], [CRUD storage в LocalVault], [Ручной], [Нет], [Список обновлён (рис. @fig-05)],
|
|
||||||
[T-8], [Включение шифрования storage], [Ручной], [Нет], [Статус «зашифровано» (рис. @fig-06)],
|
|
||||||
[T-9], [Открытие/закрытие storage], [Ручной], [Нет], [Доступ только с ключом (рис. @fig-07)],
|
|
||||||
[T-10], [OAuth Яндекс], [Ручной / IT], [Частично], [Токен в Room (рис. @fig-10)],
|
|
||||||
[T-11], [Экран задач и уведомления], [Ручной], [Частично], [Прогресс и завершение (рис. 12–13)],
|
|
||||||
[T-12], [Compose: секреты и 2FA], [IT], [Да], [Отображение без падений (рис. @fig-33–@fig-34)],
|
|
||||||
) <tbl-testplan>
|
|
||||||
|
|
||||||
=== Критерии начала и окончания
|
|
||||||
|
|
||||||
*Начало:* собраны модули проекта; выполняется `./gradlew test`; для инструментальных тестов доступен эмулятор API 26+.
|
|
||||||
|
|
||||||
*Окончание:* все 68 unit-тестов в `src/test` завершились успешно; инструментальные тесты пройдены на эмуляторе; ручной чек-лист T-7…T-12 выполнен; критические дефекты отсутствуют.
|
|
||||||
|
|
||||||
=== Среда и инструменты
|
|
||||||
|
|
||||||
#pz-table(
|
|
||||||
[Тестовая среда],
|
|
||||||
2,
|
|
||||||
table.header([Параметр], [Значение]),
|
|
||||||
[ОС разработки], [GNU/Linux, Android Studio],
|
|
||||||
[JDK], [OpenJDK 17 / 21],
|
|
||||||
[Сборка], [`./gradlew test`, `./gradlew connectedDebugAndroidTest`],
|
|
||||||
[Устройство], [Эмулятор Pixel 6 API 34; физическое устройство для OAuth],
|
|
||||||
) <tbl-test-env>
|
|
||||||
|
|
||||||
== Модульные тесты (JUnit)
|
== Модульные тесты (JUnit)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
// Разрыв длинных таблиц и подпись сверху задаёт шаблон modern-g7-32 (style.typ).
|
// Разрыв длинных таблиц и подпись сверху задаёт шаблон modern-g7-32 (style.typ).
|
||||||
|
|
||||||
#show table: set text(hyphenate: true, lang: "ru")
|
#show table: set text(hyphenate: true, lang: "ru")
|
||||||
|
#show table.cell: set block(inset: (x: 5pt, y: 3pt))
|
||||||
|
|
||||||
|
// Таблица внутри figure — по умолчанию один неразрывный block (обрезка длинных реестров).
|
||||||
|
#show figure.where(kind: table): set block(breakable: true)
|
||||||
|
|
||||||
#let pz-appendix-title(body) = heading(level: 1)[#body]
|
#let pz-appendix-title(body) = heading(level: 1)[#body]
|
||||||
|
|
||||||
@@ -147,7 +151,7 @@
|
|||||||
}
|
}
|
||||||
table(
|
table(
|
||||||
columns: columns,
|
columns: columns,
|
||||||
table.header(..header-row),
|
table.header(..header-row, repeat: true),
|
||||||
..rows,
|
..rows,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -181,6 +185,7 @@
|
|||||||
kind: table,
|
kind: table,
|
||||||
{
|
{
|
||||||
show table: set text(hyphenate: true, size: 9pt)
|
show table: set text(hyphenate: true, size: 9pt)
|
||||||
|
show table.cell: set block(inset: (x: 5pt, y: 4pt))
|
||||||
show table.cell: cell => {
|
show table.cell: cell => {
|
||||||
show regex("[a-z0-9][A-Z]"): m => {
|
show regex("[a-z0-9][A-Z]"): m => {
|
||||||
m.text.first() + sym.zws + m.text.last()
|
m.text.first() + sym.zws + m.text.last()
|
||||||
|
|||||||
64
Report/includes/testing/01-plan.typ
Normal file
64
Report/includes/testing/01-plan.typ
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
== План тестирования
|
||||||
|
|
||||||
|
=== Цели и задачи испытаний
|
||||||
|
|
||||||
|
Основная цель — подтвердить корректность криптографического ядра, доменной логики синхронизации и сценариев UI. Были поставлены следующие задачи:
|
||||||
|
|
||||||
|
+ проверить `Encryptor` и проверку ключа для строк, байтов и потоков;
|
||||||
|
+ убедиться в корректном маппинге исключений в коды ошибок;
|
||||||
|
+ протестировать движок синхронизации (`StorageSyncEngine`, журнал, блокировки);
|
||||||
|
+ проверить оркестратор фоновых задач;
|
||||||
|
+ выполнить smoke-тесты навигации, deep link и 2FA/TOTP;
|
||||||
|
+ зафиксировать результаты ручных сценариев vault, OAuth и экрана задач.
|
||||||
|
|
||||||
|
=== Объект и уровни тестирования
|
||||||
|
|
||||||
|
#import "../common.typ": pz-table
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Объекты и уровни тестирования Wallenc],
|
||||||
|
4,
|
||||||
|
table.header([Уровень], [Объект], [Инструмент], [Критерий успеха]),
|
||||||
|
[Unit], [Классы domain, usecases, ui, task-runtime, domain-vault], [JUnit 4, JVM], [Все тесты модуля успешны],
|
||||||
|
[Инструм.], [Room, Compose UI, OAuth], [AndroidJUnit, эмулятор], [Нет падений на целевом API],
|
||||||
|
[Ручной], [Сборка app, пользовательские цепочки], [Чек-лист], [Сценарии T-1…T-12 пройдены],
|
||||||
|
[Регресс.], [Синхронизация, шифрование], [Повтор unit + выборочный ручной], [Нет блокирующих дефектов],
|
||||||
|
) <tbl-test-levels>
|
||||||
|
|
||||||
|
=== Матрица тестовых сценариев
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Матрица тестовых сценариев],
|
||||||
|
5,
|
||||||
|
table.header([ID], [Сценарий], [Тип], [Авто], [Ожидаемый результат]),
|
||||||
|
[T-1], [Проверка ключа шифрования], [Unit], [Да], [`Encryptor.checkKey` true/false],
|
||||||
|
[T-2], [Шифрование/дешифрование строки и байтов], [Unit], [Да], [Симметрия данных],
|
||||||
|
[T-3], [Потоковое шифрование файла], [Unit], [Да], [Данные после decrypt равны исходным],
|
||||||
|
[T-4], [Синхронизация группы хранилищ], [Unit], [Да], [Копирование, удаление, trash, блокировки],
|
||||||
|
[T-5], [2FA TOTP генерация], [Unit], [Да], [Совпадение с эталоном Java OTP],
|
||||||
|
[T-6], [Маппинг ошибок сети/диска], [Unit], [Да], [Типизированные `WallencException`],
|
||||||
|
[T-7], [CRUD storage в LocalVault], [Ручной], [Нет], [Список обновлён (прил. В, рис. 5)],
|
||||||
|
[T-8], [Включение шифрования storage], [Ручной], [Нет], [Статус «зашифровано» (прил. В, рис. 6)],
|
||||||
|
[T-9], [Открытие/закрытие storage], [Ручной], [Нет], [Доступ только с ключом (прил. В, рис. 7)],
|
||||||
|
[T-10], [OAuth Яндекс], [Ручной / IT], [Частично], [Токен в Room (прил. В, рис. 10)],
|
||||||
|
[T-11], [Экран задач и уведомления], [Ручной], [Частично], [Прогресс и завершение (прил. В, рис. 12–13)],
|
||||||
|
[T-12], [Compose: секреты и 2FA], [IT], [Да], [Отображение без падений (прил. В, рис. 33–34)],
|
||||||
|
) <tbl-testplan>
|
||||||
|
|
||||||
|
=== Критерии начала и окончания
|
||||||
|
|
||||||
|
*Начало:* собраны модули проекта; выполняется `./gradlew test`; для инструментальных тестов доступен эмулятор API 26+.
|
||||||
|
|
||||||
|
*Окончание:* все 68 unit-тестов в `src/test` завершились успешно; инструментальные тесты пройдены на эмуляторе; ручной чек-лист T-7…T-12 выполнен; критические дефекты отсутствуют.
|
||||||
|
|
||||||
|
=== Среда и инструменты
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Тестовая среда],
|
||||||
|
2,
|
||||||
|
table.header([Параметр], [Значение]),
|
||||||
|
[ОС разработки], [GNU/Linux, Android Studio],
|
||||||
|
[JDK], [OpenJDK 17 / 21],
|
||||||
|
[Сборка], [`./gradlew test`, `./gradlew connectedDebugAndroidTest`],
|
||||||
|
[Устройство], [Эмулятор Pixel 6 API 34; физическое устройство для OAuth],
|
||||||
|
) <tbl-test-env>
|
||||||
59
Report/includes/testing/02-test-sets.typ
Normal file
59
Report/includes/testing/02-test-sets.typ
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
== Наборы тестов и покрытие требований
|
||||||
|
|
||||||
|
Наборы тестов сформированы по функциональным требованиям к Wallenc (ФР-1…ФР-6). Матрица сценариев T-1…T-12 связывает испытания с ожидаемым поведением; реестр unit-тестов фиксирует автоматизированные проверки по модулям Gradle.
|
||||||
|
|
||||||
|
=== Функциональные требования
|
||||||
|
|
||||||
|
#include "tbl-functional-req.typ"
|
||||||
|
|
||||||
|
=== Матрица тестовых сценариев
|
||||||
|
|
||||||
|
#import "../common.typ": pz-table
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Матрица тестовых сценариев],
|
||||||
|
5,
|
||||||
|
table.header([ID], [Сценарий], [Тип], [Авто], [Ожидаемый результат]),
|
||||||
|
[T-1], [Проверка ключа шифрования], [Unit], [Да], [`Encryptor.checkKey` true/false],
|
||||||
|
[T-2], [Шифрование/дешифрование строки и байтов], [Unit], [Да], [Симметрия данных],
|
||||||
|
[T-3], [Потоковое шифрование файла], [Unit], [Да], [Данные после decrypt равны исходным],
|
||||||
|
[T-4], [Синхронизация группы хранилищ], [Unit], [Да], [Копирование, удаление, trash, блокировки],
|
||||||
|
[T-5], [2FA TOTP генерация], [Unit], [Да], [Совпадение с эталоном Java OTP],
|
||||||
|
[T-6], [Маппинг ошибок сети/диска], [Unit], [Да], [Типизированные `WallencException`],
|
||||||
|
[T-7], [CRUD storage в LocalVault], [Ручной], [Нет], [Список обновлён],
|
||||||
|
[T-8], [Включение шифрования storage], [Ручной], [Нет], [Статус encrypted],
|
||||||
|
[T-9], [Открытие/закрытие storage], [Ручной], [Нет], [Доступ только с ключом],
|
||||||
|
[T-10], [OAuth Яндекс], [Ручной / IT], [Частично], [Токен в `DbYandexAccount`],
|
||||||
|
[T-11], [Экран задач и уведомления], [Ручной], [Частично], [Прогресс и завершение],
|
||||||
|
[T-12], [Compose: секреты и 2FA], [IT], [Да], [Отображение без падений],
|
||||||
|
) <tbl-testplan-export>
|
||||||
|
|
||||||
|
=== Трассировка требований → тесты
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Трассировка требований → тесты],
|
||||||
|
3,
|
||||||
|
table.header([ФР], [Тесты], [Комментарий]),
|
||||||
|
[ФР-1], [T-7, StorageDomainUseCasesTest], [Storage в LocalVault и CRUD секретов],
|
||||||
|
[ФР-2], [EncryptorTest, T-8, T-9], [Покрытие AES],
|
||||||
|
[ФР-3], [TextSecretsScreenContentTest], [UI + domain],
|
||||||
|
[ФР-4], [YandexDiskRepositoryTest, T-10], [HTTP-мок и ручной OAuth],
|
||||||
|
[ФР-5], [StorageSyncEngineTest], [Синхронизация групп],
|
||||||
|
[ФР-6], [TaskOrchestratorTest, T-11], [Очередь и экран задач],
|
||||||
|
) <tbl-trace-export>
|
||||||
|
|
||||||
|
=== Реестр модульных unit-тестов
|
||||||
|
|
||||||
|
#include "../ch05-tests-generated.typ"
|
||||||
|
|
||||||
|
=== Инструментальные тесты (androidTest)
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Инструментальные тесты androidTest],
|
||||||
|
4,
|
||||||
|
table.header([Модуль], [Класс], [Назначение], [Методов]),
|
||||||
|
[:ui], [TwoFaTokensScreenContentTest], [Compose: экран 2FA токенов], [2],
|
||||||
|
[:ui], [TextSecretsScreenContentTest], [Compose: текстовые секреты], [2],
|
||||||
|
[:infra], [YandexAccountRepositoryTest], [Room in-memory: аккаунт Яндекс], [3],
|
||||||
|
[:app], [YandexDiskLiveIntegrationTest], [Живой API (при наличии токена)], [3],
|
||||||
|
) <tbl-androidtest-export>
|
||||||
43
Report/includes/testing/03-automation.typ
Normal file
43
Report/includes/testing/03-automation.typ
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
== Автоматизированное тестирование (код модулей)
|
||||||
|
|
||||||
|
В проекте реализовано 68 автоматических unit-тестов в пяти модулях (`:domain` — 12, `:domain-vault` — 10, `:usecases` — 25, `:ui` — 15, `:task-runtime` — 6). Тесты выполняются на JVM при сборке (`./gradlew test`). Инструментальные тесты размещены в `src/androidTest` соответствующих модулей.
|
||||||
|
|
||||||
|
=== Листинги исходного кода автотестов
|
||||||
|
|
||||||
|
Ниже — листинги файлов `src/test` и `src/androidTest` (при сборке PDF читаются из дерева проекта Wallenc, по тому же принципу, что приложение А пояснительной записки).
|
||||||
|
|
||||||
|
#include "../../listings/generated-tests/appendix-tests.typ"
|
||||||
|
|
||||||
|
=== Криптография и доменные ошибки
|
||||||
|
|
||||||
|
Класс `EncryptorTest` проверяет сценарии AES: `checkKey`, шифрование строк, байтовых массивов и потоков с верным и неверным ключом. `WallencExceptionMappingTest` покрывает преобразование файловых и сетевых исключений.
|
||||||
|
|
||||||
|
#import "../common.typ": pz-fig
|
||||||
|
|
||||||
|
#pz-fig("fig_27_gradle_domain_test.png", [Отчёт Gradle: модуль :domain, задача test], "fig-27-export")
|
||||||
|
|
||||||
|
=== Синхронизация, 2FA и use cases
|
||||||
|
|
||||||
|
`StorageSyncEngineTest` моделирует группы синхронизации, копирование и удаление файлов, soft-delete, отмену и блокировки; отдельно проверяются слияние журнала и пропуск цели с актуальной ревизией. `TwoFaTotpTest` сверяет TOTP с эталоном Java OTP. `StorageDomainUseCasesTest` проверяет CRUD текстовых секретов и 2FA.
|
||||||
|
|
||||||
|
#pz-fig("fig_28_gradle_usecases_test.png", [Отчёт Gradle: модуль :usecases], "fig-28-export")
|
||||||
|
|
||||||
|
=== Модуль :domain-vault
|
||||||
|
|
||||||
|
`YandexDiskRepositoryTest` использует мок HTTP: разбор `diskInfo`, пустой список при 404, `AuthException` при 401. `VaultThrowableMappingTest` покрывает сетевые и файловые ошибки vault.
|
||||||
|
|
||||||
|
=== Модуль :ui
|
||||||
|
|
||||||
|
Проверены чистые функции навигации, deep link, подписи уведомлений, парсинг OTP URI и постановка задачи в очередь (`TaskPipelineViewModelTest`).
|
||||||
|
|
||||||
|
#pz-fig("fig_29_gradle_ui_test.png", [Отчёт Gradle: модуль :ui], "fig-29-export")
|
||||||
|
|
||||||
|
=== Модуль :task-runtime
|
||||||
|
|
||||||
|
`TaskOrchestratorTest` проверяет enqueue, progress, fail, cancel и cancelAll.
|
||||||
|
|
||||||
|
== Инструментальные тесты (androidTest)
|
||||||
|
|
||||||
|
Запуск: `./gradlew connectedDebugAndroidTest`. Результат — на рис. @fig-31-export.
|
||||||
|
|
||||||
|
#pz-fig("fig_31_gradle_connected_test.png", [Gradle connectedDebugAndroidTest], "fig-31-export")
|
||||||
43
Report/includes/testing/04-report.typ
Normal file
43
Report/includes/testing/04-report.typ
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
== Отчёт о проведении тестирования
|
||||||
|
|
||||||
|
По итогам `./gradlew test` все 68 unit-тестов завершились со статусом PASSED. Инструментальные тесты `:ui` подтвердили отрисовку экранов секретов и 2FA; тесты Room — персистентность учётной записи Яндекс.
|
||||||
|
|
||||||
|
#import "../common.typ": pz-fig, pz-table
|
||||||
|
|
||||||
|
#pz-fig("fig_30_gradle_test_summary.png", [Сводка Gradle test по модулям], "fig-30-export")
|
||||||
|
|
||||||
|
== Ручное и UI-тестирование
|
||||||
|
|
||||||
|
Ручные прогоны выполнялись по чек-листу T-7…T-12 на эмуляторе и физическом устройстве.
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Протокол ручного тестирования],
|
||||||
|
5,
|
||||||
|
table.header([ID], [Шаг], [Статус], [Фактический результат], [Иллюстрация]),
|
||||||
|
[T-7], [Создать storage в LocalVault], [OK], [Storage в списке], [рис. 5],
|
||||||
|
[T-8], [Включить шифрование], [OK], [Статус encrypted], [рис. 6],
|
||||||
|
[T-9], [Открыть/закрыть storage], [OK], [Контент только при открытом storage], [рис. 7],
|
||||||
|
[T-10], [OAuth Яндекс], [OK], [Запись в `DbYandexAccount`], [рис. 10],
|
||||||
|
[T-11], [Фоновая задача шифрования], [OK], [Прогресс на экране задач], [рис. 12],
|
||||||
|
[T-12], [Уведомление о завершении], [OK], [Notification отображён], [рис. 13],
|
||||||
|
) <tbl-testres-export>
|
||||||
|
|
||||||
|
#pz-fig("fig_32_manual_test_checklist.png", [Чек-лист ручного UI-тестирования], "fig-32-export")
|
||||||
|
|
||||||
|
=== Трассировка требований → тесты (итог)
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Трассировка требований → тесты],
|
||||||
|
3,
|
||||||
|
table.header([ФР], [Тесты], [Комментарий]),
|
||||||
|
[ФР-1], [T-7, StorageDomainUseCasesTest], [Storage в LocalVault и CRUD секретов],
|
||||||
|
[ФР-2], [EncryptorTest, T-8, T-9], [Покрытие AES],
|
||||||
|
[ФР-3], [TextSecretsScreenContentTest], [UI + domain],
|
||||||
|
[ФР-4], [YandexDiskRepositoryTest, T-10], [HTTP-мок и ручной OAuth],
|
||||||
|
[ФР-5], [StorageSyncEngineTest], [Синхронизация групп],
|
||||||
|
[ФР-6], [TaskOrchestratorTest, T-11], [Очередь и экран задач],
|
||||||
|
) <tbl-trace-report>
|
||||||
|
|
||||||
|
=== Вывод
|
||||||
|
|
||||||
|
План тестирования выполнен: автоматизированное покрытие охватывает криптографию, синхронизацию, задачи, парсинг OTP и обработку ошибок; ручные сценарии подтвердили пригодность UI для vault и OAuth. Критические дефекты не выявлены. Результаты обосновывают готовность прототипа Wallenc к демонстрации и защите ВКР.
|
||||||
1
Report/includes/testing/_intro.typ
Normal file
1
Report/includes/testing/_intro.typ
Normal file
@@ -0,0 +1 @@
|
|||||||
|
В ходе работы было организовано тестирование Wallenc на нескольких уровнях: модульные автоматические тесты (JUnit, каталог `src/test` каждого Gradle-модуля), инструментальные тесты (`src/androidTest`), а также ручные функциональные и UI-прогоны. Программа и методика испытаний приведены в приложении Б пояснительной записки.
|
||||||
13
Report/includes/testing/tbl-functional-req.typ
Normal file
13
Report/includes/testing/tbl-functional-req.typ
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#import "../common.typ": pz-table
|
||||||
|
|
||||||
|
#pz-table(
|
||||||
|
[Свод функциональных требований (фрагмент ПЗ, гл. 1)],
|
||||||
|
2,
|
||||||
|
table.header([Код], [Требование]),
|
||||||
|
[ФР-1], [Создание, просмотр, переименование и удаление storage в локальном vault (LocalVault — один на устройстве)],
|
||||||
|
[ФР-2], [Включение шифрования storage, проверка ключа, открытие и закрытие зашифрованного представления],
|
||||||
|
[ФР-3], [Просмотр и операции с файлами внутри storage; текстовые секреты и 2FA],
|
||||||
|
[ФР-4], [OAuth-авторизация (Яндекс), регистрация удалённых vault и листинг их storage],
|
||||||
|
[ФР-5], [Синхронизация: группы хранилищ, журнал коммитов, фоновый Worker без передачи ключей],
|
||||||
|
[ФР-6], [Очередь фоновых задач: шифрование, синхронизация, отображение прогресса],
|
||||||
|
) <tbl-req-export>
|
||||||
1
Report/listings/.gitignore
vendored
1
Report/listings/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
generated/
|
generated/
|
||||||
|
generated-tests/
|
||||||
|
|||||||
37
Report/listings/tests-listings.config.yaml
Normal file
37
Report/listings/tests-listings.config.yaml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Листинги автотестов (src/test, src/androidTest) — для testing-export и гл. 5.
|
||||||
|
scan_root: ".."
|
||||||
|
generated_subdir: "generated-tests"
|
||||||
|
appendix_filename: "appendix-tests.typ"
|
||||||
|
caption_prefix: "Автотест "
|
||||||
|
pagebreak_after_listing: false
|
||||||
|
pagebreak_per_module: true
|
||||||
|
|
||||||
|
only_path_substrings:
|
||||||
|
- "/src/test/"
|
||||||
|
- "/src/androidTest/"
|
||||||
|
|
||||||
|
only_test_filenames: true
|
||||||
|
|
||||||
|
include_extensions:
|
||||||
|
.kt: kotlin
|
||||||
|
|
||||||
|
exclude_dirs:
|
||||||
|
- build
|
||||||
|
- .gradle
|
||||||
|
- .idea
|
||||||
|
- Report
|
||||||
|
- captures
|
||||||
|
- .cxx
|
||||||
|
- node_modules
|
||||||
|
|
||||||
|
exclude_globs:
|
||||||
|
- "**/generated/**"
|
||||||
|
|
||||||
|
modules_order:
|
||||||
|
- domain
|
||||||
|
- domain-vault
|
||||||
|
- usecases
|
||||||
|
- ui
|
||||||
|
- task-runtime
|
||||||
|
- infrastructure-android
|
||||||
|
- app
|
||||||
@@ -50,6 +50,19 @@ def should_skip(path: Path, parts: tuple[str, ...], cfg: dict) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_test_source(path: Path, cfg: dict) -> bool:
|
||||||
|
if not cfg.get("only_test_filenames"):
|
||||||
|
return True
|
||||||
|
return path.name.endswith("Test.kt") or path.name == "YandexTestCredentials.kt"
|
||||||
|
|
||||||
|
|
||||||
|
def matches_path_filter(rel_posix: str, cfg: dict) -> bool:
|
||||||
|
needles = cfg.get("only_path_substrings")
|
||||||
|
if not needles:
|
||||||
|
return True
|
||||||
|
return any(n in rel_posix for n in needles)
|
||||||
|
|
||||||
|
|
||||||
def collect_files(scan_root: Path, cfg: dict) -> list[Path]:
|
def collect_files(scan_root: Path, cfg: dict) -> list[Path]:
|
||||||
ext_map: dict = cfg.get("include_extensions", {})
|
ext_map: dict = cfg.get("include_extensions", {})
|
||||||
name_map: dict = cfg.get("include_filenames", {})
|
name_map: dict = cfg.get("include_filenames", {})
|
||||||
@@ -64,6 +77,11 @@ def collect_files(scan_root: Path, cfg: dict) -> list[Path]:
|
|||||||
if should_skip(p, rel_parts, cfg):
|
if should_skip(p, rel_parts, cfg):
|
||||||
continue
|
continue
|
||||||
if p.name in allowed_names or p.suffix.lower() in allowed_suffixes:
|
if p.name in allowed_names or p.suffix.lower() in allowed_suffixes:
|
||||||
|
rel_posix = p.relative_to(scan_root).as_posix()
|
||||||
|
if not matches_path_filter(rel_posix, cfg):
|
||||||
|
continue
|
||||||
|
if not is_test_source(p, cfg):
|
||||||
|
continue
|
||||||
files.append(p)
|
files.append(p)
|
||||||
return sorted(files, key=lambda x: x.as_posix())
|
return sorted(files, key=lambda x: x.as_posix())
|
||||||
|
|
||||||
@@ -109,6 +127,8 @@ def write_listing(
|
|||||||
caption: str,
|
caption: str,
|
||||||
label: str,
|
label: str,
|
||||||
lang: str,
|
lang: str,
|
||||||
|
*,
|
||||||
|
pagebreak_after: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
rel_typ = typst_escape_path(rel_from_report)
|
rel_typ = typst_escape_path(rel_from_report)
|
||||||
content = (
|
content = (
|
||||||
@@ -119,11 +139,22 @@ def write_listing(
|
|||||||
f" ],\n"
|
f" ],\n"
|
||||||
f" supplement: [Листинг],\n"
|
f" supplement: [Листинг],\n"
|
||||||
f") <lst-{label}>\n"
|
f") <lst-{label}>\n"
|
||||||
f"#pagebreak(weak: true)\n\n"
|
|
||||||
)
|
)
|
||||||
|
if pagebreak_after:
|
||||||
|
content += "#pagebreak(weak: true)\n"
|
||||||
|
content += "\n"
|
||||||
out_path.write_text(content, encoding="utf-8")
|
out_path.write_text(content, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def module_heading(mod: str, cfg: dict) -> str:
|
||||||
|
if mod == "root":
|
||||||
|
return "Система сборки"
|
||||||
|
for entry in cfg.get("module_titles", []):
|
||||||
|
if entry.get("id") == mod:
|
||||||
|
return entry["title"]
|
||||||
|
return f"Модуль :{mod}"
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--dry-run", action="store_true")
|
parser.add_argument("--dry-run", action="store_true")
|
||||||
@@ -136,7 +167,10 @@ def main() -> int:
|
|||||||
cfg = load_config(config_path)
|
cfg = load_config(config_path)
|
||||||
|
|
||||||
scan_root = (report_dir / cfg.get("scan_root", "..")).resolve()
|
scan_root = (report_dir / cfg.get("scan_root", "..")).resolve()
|
||||||
generated = report_dir / "listings" / "generated"
|
generated = report_dir / "listings" / cfg.get("generated_subdir", "generated")
|
||||||
|
appendix_name = cfg.get("appendix_filename", "appendix-a.typ")
|
||||||
|
caption_prefix = cfg.get("caption_prefix", "Исходный файл ")
|
||||||
|
pagebreak_after = cfg.get("pagebreak_after_listing", True)
|
||||||
ext_map: dict = cfg.get("include_extensions", {})
|
ext_map: dict = cfg.get("include_extensions", {})
|
||||||
name_map: dict = cfg.get("include_filenames", {})
|
name_map: dict = cfg.get("include_filenames", {})
|
||||||
|
|
||||||
@@ -162,17 +196,24 @@ def main() -> int:
|
|||||||
rel_report_str = rel_report.as_posix()
|
rel_report_str = rel_report.as_posix()
|
||||||
hid = path_hash(rel_repo)
|
hid = path_hash(rel_repo)
|
||||||
label = hid
|
label = hid
|
||||||
cap = f"Исходный файл {typst_escape_caption(rel_repo)}"
|
cap = f"{caption_prefix}{typst_escape_caption(rel_repo)}"
|
||||||
lang = lang_for(f, ext_map, name_map)
|
lang = lang_for(f, ext_map, name_map)
|
||||||
listing_name = f"listing-{hid}.typ"
|
listing_name = f"listing-{hid}.typ"
|
||||||
listing_path = generated / listing_name
|
listing_path = generated / listing_name
|
||||||
write_listing(listing_path, rel_report_str, cap, label, lang)
|
write_listing(
|
||||||
|
listing_path,
|
||||||
|
rel_report_str,
|
||||||
|
cap,
|
||||||
|
label,
|
||||||
|
lang,
|
||||||
|
pagebreak_after=pagebreak_after,
|
||||||
|
)
|
||||||
mod_listings.append(listing_name)
|
mod_listings.append(listing_name)
|
||||||
listing_paths.append(listing_name)
|
listing_paths.append(listing_name)
|
||||||
|
|
||||||
mod_file = generated / f"module-{mod}.typ"
|
mod_file = generated / f"module-{mod}.typ"
|
||||||
with mod_file.open("w", encoding="utf-8") as mf:
|
with mod_file.open("w", encoding="utf-8") as mf:
|
||||||
title = "Система сборки" if mod == "root" else f"Модуль :{mod}"
|
title = module_heading(mod, cfg)
|
||||||
mf.write(f"== {title}\n\n")
|
mf.write(f"== {title}\n\n")
|
||||||
if cfg.get("pagebreak_per_module") and mod != "root":
|
if cfg.get("pagebreak_per_module") and mod != "root":
|
||||||
mf.write("#pagebreak(weak: true)\n\n")
|
mf.write("#pagebreak(weak: true)\n\n")
|
||||||
@@ -184,7 +225,7 @@ def main() -> int:
|
|||||||
with index.open("w", encoding="utf-8") as ix:
|
with index.open("w", encoding="utf-8") as ix:
|
||||||
ix.write(f"// Generated listings: {len(files)} files\n\n")
|
ix.write(f"// Generated listings: {len(files)} files\n\n")
|
||||||
|
|
||||||
appendix = generated / "appendix-a.typ"
|
appendix = generated / appendix_name
|
||||||
styles_path = report_dir / "includes" / "listings-appendix.typ"
|
styles_path = report_dir / "includes" / "listings-appendix.typ"
|
||||||
styles = styles_path.read_text(encoding="utf-8")
|
styles = styles_path.read_text(encoding="utf-8")
|
||||||
with appendix.open("w", encoding="utf-8") as ap:
|
with appendix.open("w", encoding="utf-8") as ap:
|
||||||
@@ -195,7 +236,7 @@ def main() -> int:
|
|||||||
for mi in module_includes:
|
for mi in module_includes:
|
||||||
ap.write(f'#include "{mi}"\n')
|
ap.write(f'#include "{mi}"\n')
|
||||||
|
|
||||||
print(f"Generated {len(files)} listings in {generated}", file=sys.stderr)
|
print(f"Generated {len(files)} listings → {appendix}", file=sys.stderr)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
19
Report/scripts/gen_test_listings.py
Executable file
19
Report/scripts/gen_test_listings.py
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Сгенерировать Typst-листинги автотестов (аналог приложения А, только src/test и androidTest)."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
script_dir = Path(__file__).resolve().parent
|
||||||
|
config = script_dir.parent / "listings" / "tests-listings.config.yaml"
|
||||||
|
return subprocess.call(
|
||||||
|
[sys.executable, str(script_dir / "gen_listings.py"), "--config", str(config)],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
4391
Report/testing-export/01_plan_testing.pdf
Normal file
4391
Report/testing-export/01_plan_testing.pdf
Normal file
File diff suppressed because it is too large
Load Diff
12
Report/testing-export/01_plan_testing.typ
Normal file
12
Report/testing-export/01_plan_testing.typ
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#import "_document.typ": testing-export-doc
|
||||||
|
#show: doc => testing-export-doc(
|
||||||
|
title: [План тестирования],
|
||||||
|
criterion: [Критерий 1.1 — наличие плана тестирования],
|
||||||
|
doc,
|
||||||
|
)
|
||||||
|
|
||||||
|
= Тестирование программного обеспечения
|
||||||
|
|
||||||
|
#include "../includes/testing/_intro.typ"
|
||||||
|
|
||||||
|
#include "../includes/testing/01-plan.typ"
|
||||||
11080
Report/testing-export/02_test_sets.pdf
Normal file
11080
Report/testing-export/02_test_sets.pdf
Normal file
File diff suppressed because it is too large
Load Diff
12
Report/testing-export/02_test_sets.typ
Normal file
12
Report/testing-export/02_test_sets.typ
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#import "_document.typ": testing-export-doc
|
||||||
|
#show: doc => testing-export-doc(
|
||||||
|
title: [Наборы тестов],
|
||||||
|
criterion: [Критерий 1.2 — наличие наборов тестов],
|
||||||
|
doc,
|
||||||
|
)
|
||||||
|
|
||||||
|
= Тестирование программного обеспечения
|
||||||
|
|
||||||
|
#include "../includes/testing/_intro.typ"
|
||||||
|
|
||||||
|
#include "../includes/testing/02-test-sets.typ"
|
||||||
121698
Report/testing-export/03_automation_code.pdf
Normal file
121698
Report/testing-export/03_automation_code.pdf
Normal file
File diff suppressed because one or more lines are too long
12
Report/testing-export/03_automation_code.typ
Normal file
12
Report/testing-export/03_automation_code.typ
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#import "_document.typ": testing-export-doc
|
||||||
|
#show: doc => testing-export-doc(
|
||||||
|
title: [Автоматизированное тестирование],
|
||||||
|
criterion: [Критерий 1.3 — код модулей автоматизированного тестирования],
|
||||||
|
doc,
|
||||||
|
)
|
||||||
|
|
||||||
|
= Тестирование программного обеспечения
|
||||||
|
|
||||||
|
#include "../includes/testing/_intro.typ"
|
||||||
|
|
||||||
|
#include "../includes/testing/03-automation.typ"
|
||||||
4266
Report/testing-export/04_test_report.pdf
Normal file
4266
Report/testing-export/04_test_report.pdf
Normal file
File diff suppressed because it is too large
Load Diff
12
Report/testing-export/04_test_report.typ
Normal file
12
Report/testing-export/04_test_report.typ
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#import "_document.typ": testing-export-doc
|
||||||
|
#show: doc => testing-export-doc(
|
||||||
|
title: [Отчёт о проведении тестирования],
|
||||||
|
criterion: [Критерий 1.4 — отчёт о проведении тестирования],
|
||||||
|
doc,
|
||||||
|
)
|
||||||
|
|
||||||
|
= Тестирование программного обеспечения
|
||||||
|
|
||||||
|
#include "../includes/testing/_intro.typ"
|
||||||
|
|
||||||
|
#include "../includes/testing/04-report.typ"
|
||||||
39
Report/testing-export/README.md
Normal file
39
Report/testing-export/README.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Экспорт раздела «Тестирование» (ПЗ ВКР Wallenc)
|
||||||
|
|
||||||
|
Каталог содержит **4 автономных документа** для загрузки в систему оценивания (критерии 1.1–1.4). Исходники — фрагменты Typst из гл. 5 пояснительной записки (`Report/includes/testing/`).
|
||||||
|
|
||||||
|
## Файлы для загрузки
|
||||||
|
|
||||||
|
| Файл | Критерий | Содержание |
|
||||||
|
|------|----------|------------|
|
||||||
|
| `01_plan_testing.typ` / `.pdf` | **1.1** План тестирования | Цели, уровни, матрица T-1…T-12, критерии начала/окончания, среда |
|
||||||
|
| `02_test_sets.typ` / `.pdf` | **1.2** Наборы тестов | ФР-1…ФР-6, матрица сценариев, трассировка ФР→тесты, реестр 68 unit-тестов, androidTest |
|
||||||
|
| `03_automation_code.typ` / `.pdf` | **1.3** Код автотестов | Описание модулей, **листинги** `src/test` и `src/androidTest` (как в прил. А), скриншоты Gradle |
|
||||||
|
| `04_test_report.typ` / `.pdf` | **1.4** Отчёт о тестировании | Протокол ручных T-7…T-12, сводка PASSED, трассировка, вывод |
|
||||||
|
|
||||||
|
## Сборка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd Report/testing-export/scripts
|
||||||
|
./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт вызывает `gen_test_listings.py` (листинги в `listings/generated-tests/`) и собирает четыре PDF.
|
||||||
|
|
||||||
|
Листинги автотестов генерируются отдельно от приложения А:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd Report
|
||||||
|
python3 scripts/gen_test_listings.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Отдельно:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd Report
|
||||||
|
typst compile --root .. testing-export/01_plan_testing.typ
|
||||||
|
```
|
||||||
|
|
||||||
|
## Связь с полной ПЗ
|
||||||
|
|
||||||
|
Полная глава 5 подключает те же фрагменты через `Report/includes/ch05.typ`. При правке тестирования меняйте файлы в `includes/testing/`, затем пересоберите ПЗ и экспорт.
|
||||||
48
Report/testing-export/_document.typ
Normal file
48
Report/testing-export/_document.typ
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// Общая обёртка для автономных PDF по разделу тестирования (без полного оформления ГОСТ ВКР).
|
||||||
|
#let testing-export-doc(
|
||||||
|
title: "",
|
||||||
|
criterion: "",
|
||||||
|
body,
|
||||||
|
) = {
|
||||||
|
set page(
|
||||||
|
paper: "a4",
|
||||||
|
margin: (left: 3cm, right: 1.5cm, top: 2cm, bottom: 2cm),
|
||||||
|
numbering: "1",
|
||||||
|
)
|
||||||
|
set text(font: "Times New Roman", size: 14pt, lang: "ru")
|
||||||
|
set par(first-line-indent: 1.25cm, justify: true, leading: 0.65em)
|
||||||
|
set heading(numbering: "1.1")
|
||||||
|
show figure.where(kind: table): set block(breakable: true)
|
||||||
|
|
||||||
|
show heading.where(level: 1): it => {
|
||||||
|
set text(size: 16pt, weight: "bold")
|
||||||
|
align(center)[#it.body]
|
||||||
|
v(0.75em)
|
||||||
|
counter(heading).step(level: 2)
|
||||||
|
}
|
||||||
|
show heading.where(level: 2): it => {
|
||||||
|
set text(size: 14pt, weight: "bold")
|
||||||
|
v(0.5em)
|
||||||
|
it
|
||||||
|
v(0.35em)
|
||||||
|
}
|
||||||
|
show heading.where(level: 3): it => {
|
||||||
|
set text(size: 14pt, weight: "bold", style: "italic")
|
||||||
|
it
|
||||||
|
v(0.25em)
|
||||||
|
}
|
||||||
|
|
||||||
|
align(center)[
|
||||||
|
#text(size: 12pt)[
|
||||||
|
Фрагмент пояснительной записки ВКР\
|
||||||
|
«Мобильное приложение для защищённого хранения пользовательских данных» (Wallenc)
|
||||||
|
]
|
||||||
|
#v(0.4em)
|
||||||
|
#text(size: 11pt, style: "italic")[#criterion]
|
||||||
|
#v(0.6em)
|
||||||
|
#text(size: 16pt, weight: "bold")[#title]
|
||||||
|
#v(1.2em)
|
||||||
|
]
|
||||||
|
|
||||||
|
body
|
||||||
|
}
|
||||||
16
Report/testing-export/scripts/build.sh
Executable file
16
Report/testing-export/scripts/build.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Сборка четырёх PDF для загрузки по критериям тестирования.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPORT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
EXPORT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$REPORT"
|
||||||
|
|
||||||
|
python3 scripts/gen_test_listings.py
|
||||||
|
|
||||||
|
for stem in 01_plan_testing 02_test_sets 03_automation_code 04_test_report; do
|
||||||
|
echo "Compiling $stem.typ ..."
|
||||||
|
typst compile --root .. "$EXPORT/$stem.typ" "$EXPORT/$stem.pdf"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Done: $EXPORT/*.pdf"
|
||||||
Reference in New Issue
Block a user