Машинное обучение в генерации тестовых сценариев

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

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

  • Мама мыла раму.
  • Кот пролез в раму.
  • Вчера вставили новую раму.
  • Мама пошла в магазин.
  • Кот заглянул в магазин.

Цепи Маркова - это связи между словами, извлеченные из данных. При этом выбор следующего слова зависит только от предыдущего (на самом деле это только для цепей 1-го порядка, для цепей 2-го порядка следующее слово будет зависеть от 2-х предыдущих и т.д.):

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

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

Взвешенное решение при генерации тестовых сценариев в проекте glace-testgen работает по схожему принципу:

  • из обучающих данных (существующих тестов) извлекаются дуплеты и триплеты шагов и вычисляются веса их использования;
  • во время генерации тестов рассчитывается вес теста в соответствии со знаниями о весах существующих шагов;
  • сгенерированные тесты сортируются по убыванию их весов и возвращается результат.

Это код обучающего модуля генератора. И давайте посмотрим, как это работает.

Представим, что:

  1. у нас есть тесты на существующую функциональность (что-нибудь связанное с браузером, куда же без него нынче);
  2. у нас есть декларация шагов для существующей функциональности;
  3. мы добавляем новую функциональность, например, чтобы закрыть текущую вкладку;
  4. мы хотим генерировать новые тесты, используя знания о существующих тестах;

Сперва установим генератор:

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 стало меньше ненужных шагов. Но на самом деле реальную пользу от машинного обучения и генерации тестов можно будет понять, только начав использовать для обучения реальные кейсы. Это и планируется сделать на следующем этапе по попытке внедрения генератора в реальный рабочий процесс. Идея в следующем:

  • выбрать пару десятков реальных тестов;
  • представить их в машино-читаемом формате;
  • обучить на них генератор;
  • сгенерировать тесты для новой функциональности;
  • выбрать наиболее интересные тесты;
  • добавить их к обучающей выборке;
  • повторить действия для новой функциональности;

Будем посмотреть как пойдет.

14 лайков

Вот это уже, кажется, совсем другое дело. Очень, очень и очень интересно, как пойдет. Респект!

1 лайк

спасибо :slight_smile:

По ходу на этом портале я лайки только вам ставлю.
А так вобще жалко что js и только для веба. Такой бы генератор в отдельное решение, чтобы можно было применять не только к вебу, а и к тестированию драйверов, десктоп апликух, сигналов. А поверх решения уже ставить селениумы, гтесты, питесты… или не ставить

Спасибо :slight_smile: Вообще, генератор предназначен не только для веба, но для любых кейсов, которые можно описать структурно. Но текущие примеры, да, больше для веба, т.к. это связано с работой. Да, пока только на js, хотя идея этa возникла, когда рабочим языком был python, но тогда не было понимания, как делать. Возможно руки и дойдут портировать на python, но не уверен :slight_smile:

1 лайк