В прошлый раз я рассказывал о генераторе тестовых сценариев. В этом посте я бы хотел рассказать о дальнейшем развитии проекта, а точнее как добавлял машинное обучение.
Машинное обучение - понятие весьма широкое, которое в общем означает использование алгоритмов анализа данных, получения выводов и выноса решения или предсказания в отношении чего-либо. В данном случае дело касается генерации тестовых сценариев для новой функциональности с учетом уже существующих тест-кейсов.
Для этого вполне достаточно подхода на основе байесовской сети и цепей Маркова.
Цепи Маркова могут быть весьма эффективны в генерации текста.
Давайте посмотрим, как это работает на примере нескольких предложений.
- Мама мыла раму.
- Кот пролез в раму.
- Вчера вставили новую раму.
- Мама пошла в магазин.
- Кот заглянул в магазин.
Цепи Маркова - это связи между словами, извлеченные из данных. При этом выбор следующего слова зависит только от предыдущего (на самом деле это только для цепей 1-го порядка, для цепей 2-го порядка следующее слово будет зависеть от 2-х предыдущих и т.д.):
Как минимум, машина уже знает, какие есть слова и какие есть связи между ними. Но само обучение появляется тогда, когда машина начинает учитывать, сколько раз в анализируемых данных встречаются цепочки слов, т.е. назначает веса, чтобы затем принимать взвешенное решение:
Тогда, расширяя базу знаний машины новыми и новыми предложениями, мы будем учить ее принимать все более взвешенные решения, и генерировать более правдоподобные предложения.
Взвешенное решение при генерации тестовых сценариев в проекте glace-testgen работает по схожему принципу:
- из обучающих данных (существующих тестов) извлекаются дуплеты и триплеты шагов и вычисляются веса их использования;
- во время генерации тестов рассчитывается вес теста в соответствии со знаниями о весах существующих шагов;
- сгенерированные тесты сортируются по убыванию их весов и возвращается результат.
Это код обучающего модуля генератора. И давайте посмотрим, как это работает.
Представим, что:
- у нас есть тесты на существующую функциональность (что-нибудь связанное с браузером, куда же без него нынче);
- у нас есть декларация шагов для существующей функциональности;
- мы добавляем новую функциональность, например, чтобы закрыть текущую вкладку;
- мы хотим генерировать новые тесты, используя знания о существующих тестах;
Сперва установим генератор:
npm i -g glace-testgen
Сохраним тесты для существующей функциональности в файл for_training.yml
:
Test case 1:
- launch_chrome
- open_page
- open_app
- restart_chrome
- open_url
- close_chrome
Test case 2:
- launch_chrome
- open_url
- restart_chrome
- close_chrome
- launch_global_proxy
- stop_global_proxy
Test case 3:
- launch_chrome
- open_url
- restart_chrome
- close_chrome
- launch_http_proxy
- stop_http_proxy
Test case 4:
- launch_chrome
- open_url
- restart_chrome
- launch_http_proxy
- stop_http_proxy
- close_chrome
Test case 5:
- launch_chrome
- open_url
- restart_chrome
- open_app
- open_page
- close_chrome
Test case 6:
- launch_chrome
- open_url
- restart_chrome
- open_page
- open_app
- close_chrome
Test case 7:
- launch_global_proxy
- launch_chrome
- close_chrome
- stop_global_proxy
- launch_http_proxy
- stop_http_proxy
Test case 8:
- launch_global_proxy
- launch_chrome
- close_chrome
- launch_http_proxy
- stop_http_proxy
- stop_global_proxy
Test case 9:
- launch_global_proxy
- launch_chrome
- restart_global_proxy
- restart_chrome
- close_chrome
- stop_global_proxy
Test case 10:
- launch_global_proxy
- launch_chrome
- restart_global_proxy
- open_app
- close_chrome
- stop_global_proxy
Test case 11:
- launch_global_proxy
- launch_chrome
- restart_global_proxy
- open_page
- close_chrome
- stop_global_proxy
Test case 12:
- launch_global_proxy
- launch_chrome
- restart_global_proxy
- open_url
- close_chrome
- stop_global_proxy
Test case 13:
- launch_chrome
- open_page
- launch_http_proxy
- open_url
- stop_http_proxy
- close_chrome
Test case 14:
- launch_global_proxy
- launch_chrome
- launch_http_proxy
- stop_http_proxy
- close_chrome
- stop_global_proxy
Test case 15:
- launch_global_proxy
- launch_chrome
- open_app
- close_chrome
- restart_global_proxy
- stop_global_proxy
Test case 16:
- launch_global_proxy
- launch_chrome
- open_app
- restart_chrome
- close_chrome
- stop_global_proxy
Test case 17:
- launch_global_proxy
- launch_chrome
- open_app
- restart_global_proxy
- close_chrome
- stop_global_proxy
Test case 18:
- launch_global_proxy
- launch_chrome
- open_app
- open_page
- close_chrome
- stop_global_proxy
Test case 19:
- launch_global_proxy
- launch_chrome
- open_app
- open_url
- close_chrome
- stop_global_proxy
Test case 20:
- launch_global_proxy
- launch_chrome
- open_page
- close_chrome
- restart_global_proxy
- stop_global_proxy
Сохраним декларацию шагов (включая новый шаг close_current_tab
) в файл steps.yml
:
-
name: launch_chrome
incomplete:
- chrome
income:
chrome: false
outcome:
chrome: true
-
name: close_chrome
complete:
- chrome
income:
chrome: true
outcome:
chrome: false
-
name: restart_chrome
income:
chrome: true
outcome:
chrome: true
chrome restarted: true
-
name: launch_global_proxy
incomplete:
- global proxy
income:
global proxy: false
outcome:
global proxy: true
-
name: stop_global_proxy
complete:
- global proxy
income:
global proxy: true
outcome:
global proxy: false
-
name: restart_global_proxy
income:
global proxy: true
outcome:
global proxy: true
-
name: launch_http_proxy
incomplete:
- http proxy
income:
http proxy: false
outcome:
http proxy: true
-
name: stop_http_proxy
complete:
- http proxy
income:
http proxy: true
outcome:
http proxy: false
-
name: restart_http_proxy
income:
http proxy: true
outcome:
http proxy: true
-
name: open_app # открыть конкретное веб приложение
income:
chrome: true
outcome:
opened tab: true
-
name: open_page # открыть внутреннюю страницу веб приложения
income:
chrome: true
outcome:
opened tab: true
-
name: open_url # открыть какой-то URL
income:
chrome: true
outcome:
opened tab: true
-
name: close_current_tab
income:
chrome: true
chrome restarted: false
opened tab: true
Сгенерируем сперва тесты для новой функциональности без учета знаний о существующих тестах:
$ test-gen steps.yml --gen-steps-uniq 4 --gen-steps-limit 6 --gen-steps-filter close_current_tab --gen-tests-max 10 --gen-output-file result
Generating tests from steps...
10 tests are generated during 987ms
Результат сохранен в файл result.yml
:
Test case 1:
- launch_chrome
- open_page
- open_app
- open_url
- close_current_tab
- close_chrome
Test case 2:
- launch_global_proxy
- launch_chrome
- open_app
- close_current_tab
- close_chrome
- stop_global_proxy
Test case 3:
- launch_global_proxy
- launch_chrome
- open_page
- close_current_tab
- close_chrome
- stop_global_proxy
Test case 4:
- launch_global_proxy
- launch_chrome
- open_url
- close_current_tab
- close_chrome
- stop_global_proxy
Test case 5:
- launch_chrome
- launch_global_proxy
- stop_global_proxy
- open_app
- close_current_tab
- close_chrome
Test case 6:
- launch_chrome
- launch_global_proxy
- stop_global_proxy
- open_page
- close_current_tab
- close_chrome
Test case 7:
- launch_chrome
- launch_global_proxy
- stop_global_proxy
- open_url
- close_current_tab
- close_chrome
Test case 8:
- launch_chrome
- launch_global_proxy
- open_app
- stop_global_proxy
- close_current_tab
- close_chrome
Test case 9:
- launch_chrome
- launch_global_proxy
- open_app
- close_current_tab
- stop_global_proxy
- close_chrome
Test case 10:
- launch_chrome
- launch_global_proxy
- open_page
- stop_global_proxy
- close_current_tab
- close_chrome
Запустим генератор в режиме обучения (результат будет сохранен в файл train-result.json
):
$ test-gen --gen-train for_training.yml
Training model...
Model is trained during 17ms
Сгенерируем тесты для новой функциональности с учетом обучения:
$ test-gen steps.yml --gen-steps-uniq 4 --gen-steps-limit 6 --gen-steps-filter close_current_tab --gen-tests-max 10 --gen-output-file result --gen-load-train train-result.json
Generating tests from steps...
10 tests are generated during 977ms
И посмотрим на результат (result.yml
):
Test case 1:
- launch_global_proxy
- launch_chrome
- open_app
- close_current_tab
- close_chrome
- stop_global_proxy
Test case 2:
- launch_global_proxy
- launch_chrome
- open_page
- close_current_tab
- close_chrome
- stop_global_proxy
Test case 3:
- launch_chrome
- open_app
- close_current_tab
- close_chrome
- launch_global_proxy
- stop_global_proxy
Test case 4:
- launch_chrome
- open_url
- close_current_tab
- close_chrome
- launch_global_proxy
- stop_global_proxy
Test case 5:
- launch_chrome
- open_page
- close_current_tab
- close_chrome
- launch_global_proxy
- stop_global_proxy
Test case 6:
- launch_chrome
- open_app
- open_url
- close_current_tab
- open_page
- close_chrome
Test case 7:
- launch_chrome
- open_app
- close_current_tab
- restart_chrome
- open_url
- close_chrome
Test case 8:
- launch_chrome
- open_page
- close_current_tab
- open_app
- open_url
- close_chrome
Test case 9:
- launch_chrome
- open_url
- close_current_tab
- open_app
- restart_chrome
- close_chrome
Test case 10:
- launch_chrome
- open_app
- close_current_tab
- open_url
- restart_chrome
- close_chrome
Кажется, что тесты стали более адекватными в том плане, что между началом теста и шагом close_current_tab
стало меньше ненужных шагов. Но на самом деле реальную пользу от машинного обучения и генерации тестов можно будет понять, только начав использовать для обучения реальные кейсы. Это и планируется сделать на следующем этапе по попытке внедрения генератора в реальный рабочий процесс. Идея в следующем:
- выбрать пару десятков реальных тестов;
- представить их в машино-читаемом формате;
- обучить на них генератор;
- сгенерировать тесты для новой функциональности;
- выбрать наиболее интересные тесты;
- добавить их к обучающей выборке;
- повторить действия для новой функциональности;
Будем посмотреть как пойдет.