Как я начал генерировать функциональные тесты для фреймворка glace-js и сэкономил кучу времени на их разработке

Всем привет!
Я бы хотел рассказать о том, как стал использовать генерацию функциональных сценариев, и показать, как это выглядит на реальном проекте.
Итак, в основном функциональное тестирование состоит из комбинаций действий (шагов), вроде таких:

Н-р такой сценарий:

- Launch browser chrome
- Open facebook page `https://facebook.com`
- Enter login `alex@gmail.com` to login field
- Enter password `alex123456` to submit field
- Click button `Enter`
- Check that user account page is opened

Число возможных комбинаций шагов - это факториал шагов n!, при условии что шаг встречается в тесте один раз. Для картинки выше это будет 8! = 40320. С одной стороны факториал дает неимоверно большое количество вариантов даже при небольшом количестве шагов, а с другой стороны не все последовательности шагов являются валидными, н-р:

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

Пример с headless режимом:

И пример для glace-js плагина glace-proxy:

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

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

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

Исходный код генератора GitHub - glacejs/glace-testgen: Documentation https://glacejs.github.io/glace-testgen.
Пример декларирования glace-js шагов, которые будут обработаны генератором:
https://github.com/glacejs/glace-js/blob/master/tests/e2e.steps.yaml.

Пояснения к ключевым словам декларирования:

  • income - состояние системы, которое требуется для выполнения шага. Например, чтобы открыть веб-страницу, требуется чтобы браузер был запущен.
  • outcome - результат изменения системы после выполнения шага. Например после запуска браузера, состояние системы будет иметь запущенный браузер.
  • incomplete - список состояний системы, которые должны быть завершены в тесте, в противном случае тест будет исключен из результата генерации. Н-р в тесте, который включает запуск браузера, должен присутствовать шаг, чтобы закрыть браузер.
  • complete - список завершающих состояний системы для incomplete. Если incomplete содержит несколько состояний в процессе генерации, н-р selenium server; launched browser, то валидными будут тесты в которых complete будет иметь обратный порядок launched browser; selenium server.

Некоторые используемые опции генератора:

  • --gen-steps-uniq - Последовательность шагов, которая должна быть уникальной среди тестов. Н-р если --gen-steps-uniq=2, то среди тестов:
Test #1:
- open https://yandex.ru
- open https://github.com
- open https://google.com

Test #2:
- open https://google.com
- open https://yandex.ru
- open https://github.com

Test #3:
- open https://github.com
- open https://google.com
- open https://yandex.ru

Test #3 будет исключен из результата, т.к. он содержит последовательности 2-х шагов, которые уже встречаются в предыдущих тестах:

- open https://github.com
- open https://google.com

- open https://google.com
- open https://yandex.ru

Но все эти 3 теста будут рассматриваться как уникальные при --gen-steps-uniq=3 или выше. По умолчанию этот параметр не ограничен. Это означает, что все шаги в тесте рассматриваются как уникальная последовательность. Практически достаточно задавать этот параметр в диапазоне от 1 до 5.

  • --gen-steps-limit - Максимальное число шагов на один тест. По умолчанию не ограничено. Практически лучше настраивать после настройки --gen-tests-limit и требуется редко.
  • --gen-tests-limit - Максимальное количество тестов, которые могут существовать на каждой итерации генерирования. Обычно конечный результат тестов включает меньшее количество после применения фильтров. Играя с этим параметром можно значительно сократить время генерации. Однако очень малое значение может привести к тому, что генератор не сможет построить граф шагов и вернет ошибку и список неиспользуемых шагов.
  • --gen-steps-shuffle - Перемешивать тесты во время генерации. Повышает рандомизацию последовательности шагов в тестах, но тесты будут отличаться от генерации к генерации. Практически вместе с этим флагом хорошо бы увеличить --gen-tests-limit как минимум в 2 раза.

Несколько моментов, которые я понял, работая с генераторов автотестов:

  • Это экономит мне кучу времени.
  • Было достаточно использовать ключевые слова income, outcome, incomplete, complete с простым описанием при декларировании, чтобы покрыть проект.
  • Не проблема задекларировать множество шагов, если они не имеют много зависимостей между собой.
  • Я определенно доверяю сгенерированным автотестам, т.к. машина работает беспристрастно, надежно и помогла мне найти баги, которые я пропустил при ручном написании автотестов, в том числе баг в хроме (или в хромдрайвере).

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

Оригинал статьи Integration Tests Generating — Production Usage | by Sergei | Medium

P.S.

Быстрая генерация большого количества уникальных сценариев

21:07:23 user@comp ~/vega/projects/glace/glace-js (git::master)
;) ./node_modules/glace-testgen/bin/test-gen ./tests/e2e.steps.yaml --gen-steps-uniq 5 --gen-tests-limit 10000 --gen-output-file result
Generating tests from steps...
6260 tests are generated during 1m 28.9s

А так выглядит лог сгенерированного и выполненного автотеста:

2018-04-02T16:32:18.423Z - info: Selenium drivers are installed already
2018-04-02T16:32:18.424Z - info: Starting Xvfb...
2018-04-02T16:32:18.451Z - info: Xvfb is started
2018-04-02T16:32:18.452Z - info: Starting http proxy...
2018-04-02T16:32:18.452Z - info: Http proxy is started
2018-04-02T16:32:18.453Z - info: Starting global proxy...
2018-04-02T16:32:18.460Z - info: Global proxy is started
2018-04-02T16:32:18.460Z - info: Starting selenium server...
2018-04-02T16:32:19.382Z - info: Selenium server is started with PID 17627
2018-04-02T16:32:19.383Z - warn: Sleep 0.1 sec, reason: small sleep
2018-04-02T16:32:19.485Z - info: Launching browser via selenium...
2018-04-02T16:32:20.609Z - info: Setting browser viewport to [width=1066, height=600]...
2018-04-02T16:32:20.755Z - info: Browser viewport size is set
2018-04-02T16:32:20.755Z - info: Browser is launched
2018-04-02T16:32:20.761Z - info: Making screenshot with selenium...
2018-04-02T16:32:20.943Z - info: Screenshot is saved to /home/user/vega/projects/glace/glace-js/reports/test-case-3/screenshots/a7e60a04-ef82-49e3-ad87-61c554f1c122.png
2018-04-02T16:32:20.984Z - info: Enabling proxy cache...
2018-04-02T16:32:20.984Z - info: Proxy cache is enabled
2018-04-02T16:32:20.984Z - info: Openning URL https://github.com in browser...
2018-04-02T16:32:21.560Z - debug: Compare current URL https://github.com/ with expected https://github.com
2018-04-02T16:32:21.560Z - info: URL is opened
2018-04-02T16:32:21.561Z - info: Openning page 'index' in browser...
2018-04-02T16:32:21.562Z - info: Openning URL http://comp:38355/ in browser...
2018-04-02T16:32:21.582Z - debug: [cache] <<<< GET_comp:38355/
2018-04-02T16:32:21.616Z - debug: [cache] <<<< GET_yastatic.net/jquery/2.1.4/jquery.min.js
2018-04-02T16:32:21.627Z - debug: [cache] <<<< GET_yastatic.net/www/_/W/t/N_5RN5c2xfl3ktRMJTYZskdzU.js
2018-04-02T16:32:21.658Z - debug: [cache] <<<< GET_yastatic.net/lego/_/La6qi18Z8LwgnZdsAr1qy1GwCwo.gif
2018-04-02T16:32:21.663Z - debug: [cache] <<<< GET_yastatic.net/www/_/G/z/X-Uf2Fe6_qA9fD12e9Xz0ccTk.js
2018-04-02T16:32:21.665Z - debug: [cache] <<<< GET_yastatic.net/www/_/o/p/gUBBX8WZqmSesz-PjnRuK7msk.svg
2018-04-02T16:32:21.666Z - debug: [cache] <<<< GET_yastatic.net/islands/_/nJL92_8XGrQ8WN7LePOnzmpHzd4.svg
2018-04-02T16:32:21.666Z - debug: [cache] <<<< GET_yastatic.net/www/_/x/Q/xk8YidkhGjIGOrFm_dL5781YA.svg
2018-04-02T16:32:21.673Z - debug: [cache] <<<< GET_yastatic.net/www/_/k/8/gsSjWTalZ0Zl-yefsGisz0YJA.svg
2018-04-02T16:32:21.739Z - debug: [cache] <<<< GET_yastatic.net/s3/home/fonts/ys/1/text-regular.woff2
2018-04-02T16:32:21.746Z - debug: [cache] <<<< GET_yastatic.net/s3/home/fonts/ys/1/text-medium.woff2
2018-04-02T16:32:21.753Z - debug: [cache] <<<< GET_yastatic.net/s3/home/fonts/ys/1/text-bold.woff2
2018-04-02T16:32:21.762Z - debug: [cache] <<<< GET_yastatic.net/www/_/U/1/o7ckruVIskZcRWhFoDDc5rung.svg
2018-04-02T16:32:21.763Z - debug: [cache] <<<< GET_yastatic.net/www/_/I/D/GmxoaNBGBwZJQ_L6OxNu-vPTM.svg
2018-04-02T16:32:22.119Z - debug: [cache] <<<< GET_yastatic.net/www/_/j/N/Ot_3Gd2XFXa_hgeO0hMJKeGJk.css
2018-04-02T16:32:22.134Z - debug: [cache] <<<< GET_www.tns-counter.ru/V13a**9fb9b58fefe1406df3f42e73a0b740ba**yandex_ru/ru/CP1251/tmsec=yandex_main/0
2018-04-02T16:32:22.149Z - debug: [cache] <<<< GET_yastatic.net/www/_/B/c/up8XzIL8p_yNYKn2dSBPcPw3I.css
2018-04-02T16:32:22.219Z - debug: [cache] <<<< GET_kiks.yandex.ru/system/fc07.swf
2018-04-02T16:32:22.235Z - debug: Compare current URL http://comp:38355/ with expected http://comp:38355/
2018-04-02T16:32:22.235Z - info: URL is opened
2018-04-02T16:32:22.235Z - info: Page is opened
2018-04-02T16:32:22.236Z - info: Disabling proxy cache...
2018-04-02T16:32:22.236Z - info: Proxy cache is disabled
2018-04-02T16:32:22.238Z - info: Starting video recording...
2018-04-02T16:32:22.270Z - warn: Sleep 1 sec, reason: it needs a time to start recording
2018-04-02T16:32:23.272Z - info: Video recording is started
2018-04-02T16:32:23.273Z - info: Setting browser viewport to [width=1066, height=600]...
2018-04-02T16:32:23.415Z - info: Browser viewport size is set
2018-04-02T16:32:23.425Z - info: Starting to measure proxy responses...
2018-04-02T16:32:23.425Z - info: Proxy responses are measuring
2018-04-02T16:32:23.426Z - info: Stopping to measure proxy responses...
2018-04-02T16:32:23.426Z - info: Proxy responses are not measured
2018-04-02T16:32:23.426Z - info: Closing browser...
2018-04-02T16:32:23.481Z - warn: Sleep 1 sec, reason: webdriver process will be stopped
2018-04-02T16:32:24.483Z - info: Browser is closed
2018-04-02T16:32:24.483Z - info: Launching browser via selenium...
2018-04-02T16:32:24.836Z - info: Setting browser viewport to [width=1066, height=600]...
2018-04-02T16:32:24.973Z - info: Browser viewport size is set
2018-04-02T16:32:24.974Z - info: Browser is launched
2018-04-02T16:32:24.974Z - info: Stopping video recording...
2018-04-02T16:32:24.975Z - warn: Sleep 1 sec, reason: it needs a time to gather latest frames
2018-04-02T16:32:26.106Z - debug: avconv was stopped with code 255 and signal null
2018-04-02T16:32:26.107Z - info: Video recording is stopped
2018-04-02T16:32:26.107Z - info: Limiting proxy speed to 512 ...
2018-04-02T16:32:26.108Z - info: Proxy speed is limited
2018-04-02T16:32:26.108Z - info: Openning URL http://comp:38355 in browser...
2018-04-02T16:32:36.582Z - debug: Compare current URL http://comp:38355/ with expected http://comp:38355
2018-04-02T16:32:36.582Z - info: URL is opened
2018-04-02T16:32:36.584Z - info: Removing recorded video file...
2018-04-02T16:32:36.584Z - info: Recorded video file is removed
2018-04-02T16:32:36.585Z - info: Unlimiting proxy speed...
2018-04-02T16:32:36.586Z - info: Proxy speed is unlimited
2018-04-02T16:32:36.587Z - info: Closing browser...
2018-04-02T16:32:36.642Z - warn: Sleep 1 sec, reason: webdriver process will be stopped
2018-04-02T16:32:37.644Z - info: Browser is closed
2018-04-02T16:32:37.644Z - info: Stopping selenium server...
2018-04-02T16:32:37.974Z - debug: Selenium server was stopped with code 130 and signal null
2018-04-02T16:32:37.974Z - info: Selenium server is stopped
2018-04-02T16:32:37.976Z - info: Stopping global proxy...
2018-04-02T16:32:37.977Z - info: Global proxy is stopped
2018-04-02T16:32:37.978Z - info: Stopping http proxy...
2018-04-02T16:32:37.979Z - info: Http proxy is stopped
2018-04-02T16:32:37.981Z - info: Stopping Xvfb...
2018-04-02T16:32:37.989Z - info: Xvfb is stopped
4 лайка

То есть самую интеллектуальную работу, о том как нужно тестировать вы переложили на ~тупой~ почти не тупой перебор? Это в вашем понимании будет покрытие?
А теперь главный вопрос. В СиАй системе ваши гордые миллион тестов вы как запускаете? Или по старинке, как наши деды, на ночь ставите?

1 лайк

Ага, это обеспечивает быстрое и большее покрытие, чем то, что вы считаете “интеллектуальной работой”. Возможно вам с работой повезло, и как Quality Assistance engineer на работе вы действительно проявляете весь потенциал творчества, во что я безусловно не верю. Тестирование как процесс составления и прохождения кейсов является весьма скучной рутиной, которую можно и нужно перекладывать на плечи машины, а самому заниматься исследованием, изучением, реализацией новых возможностей, а не тупо изо дня в день педалить одни и те же кейсы.
Кроме того, машинная генерация позволяет генерировать случайные кейсы каждый раз при прогоне, в том время как человек обычно проходит одни и те же сценарии.

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

Зачем вы запускаете тесты? Чтобы они проверили за вас регрессию (и сообщали разработчику, что он ничего не поломал). Когда возможна регрессия? При любом изменении (по факту каждый пуш в Гит). То есть тесты должны запускаться на каждый пуш в репозитарий и сообщать/блокировать возможность мержа для разработчика.
Как же работает у вас сейчас. Разработчик своим ПРом что-то ломает. Другие разработчики делают новые фиче бранчи уже с багом. А вы когда собственно им сообщаете о баге?
Про тест кейсы думаю вести дискуссию бессмысленно, слишком большой у нас геп с вами.

Вопрос в том, что тесты бегут долго и код уже в репе к тому моменту когда тест падает или что-то другое? Так много всего написано.
Ок по флоу:

  • есть юнит-тесты и быстрые интеграционные, которые пробегают для каждого компонента/плагина на каждый пуш в репозиторий. Соответственно пока нет успешного прогона, нет мержа. Тестирование логики функций, параметризация, ветки условий, циклы, оценка покрытия - это здесь.
  • есть большие интеграционные и функциональные тесты, цель которых проверить взаимодействие компонентов и плагинов, финальную бизнес-логику, и которые не запускаются на каждый пуш, а бегают с периодичностью. Падения тестов разбираются, выясняя, проблема в коде теста или в проекта, и если в проекте, то создается баг-репорт.

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

Я вас и подвожу, что проблему с тем, что тесты бегут много вы создали сами бездумно создавая стопятстот тестов. Очень важно, чтобы код ни в коем случае не был в репе (ПР не был смерджен), до того как все тесты пробегут. Вы должны сесть и подумать, какие тесты вы считаете достаточными, чтобы считать, что этот код стабилен для вас, какие тесты покрывают аксептанс критерии. Я также понимаю, что вы сейчас будете говорить о дефолтных проблемах, как время, долго ждать, сложности с flaky тестами и т.д. Но это нормально :slight_smile: Эти проблемы решаемы.
Я использовал gap.
По поводу тест кейсов. Я сейчас буду говорить только об приемочных тестов. Есть например фича. Вы пишете пусть 5-10 сценариев, которые покрывают базовые сценарии. Эти тесты обязаны быть пассед, если хотя бы один фейлед, то нужно блокировать ПР. Поэтому и к тестам нужно подходить осторожно. Особенно на уровне е2е.

Здравствуйте! Я, признаться, тоже не понял в чем «уникальность» сгенерированных таким образом сценариев?

1 лайк

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

Я думаю достаточным будет не количество тестов, а количество времени сколько вы готовы ждать, и если тесты проходят быстро (н-р юниты), то пусть их бегает как можно больше (лучше, все), при условии их уникальности.
А по поводу “сесть и подумать” в конце я писал: “Подводя итог, генератор тестов не может полностью заменить ручную работу в тестовом дизайне. Но это очень быстрый помощник, который может выполнять рутинную работу и дает мне возможность работать над творческими задачами.”

Честно говоря не сильно приблизило к понимаю, в каком контексте вы его использовали :slight_smile:

Да, эти тесты, если их мало, можно описать и руками.

Доброго дня! Честно говоря не понял при чем здесь термин “уникальность”. Была и реализована идея получить систему, которая будет делать рутину по композированию кейсов и проверке валидных вариантов, которые человек не станет делать т.к. он слишком медленный, склонный ошибаться и уставать. При этом работа сгенерированных тестов не блокирует работу человека.

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

Я абсолютно с вами не согласен. Машинное обучение сейчас не сделало и шагу в тестировании. Если у вас есть чем возразить, буду рад посмотреть как машинное обучение внедрилось куда-либо.
По поводу тестов и времени их прогона. Вы должны сначала описать цель тестов. Сейчас я вижу ее вот так: прогнать тесты, которые будут выполнятся не более 5 минут, например. А должно быть: какие проверки я должен выполнить, чтобы быть уверенным, что фича реализована корректно. А проблема со временем решается.
Gap - это разрыв между чем-то. Пространство между словами например. Или в Вашем и моем взгляде :slight_smile:

Уникальность тестов в данном случае сомнительна. Поймите правильно, мне нравится сама идея переложить рутину на плечи машины, но количество не равно качество.

2 лайка

Это штампованная фраза, не так ли. В тестировании качественное покрытие в том числе выражается количеством запущенных тестов. Вряд 10 тестов, пусть и для самых ожидаемых поведений, дадут вам качественное покрытие. Вопрос только во времени, сколько вам хочется ждать результата. И при приемлемом времени и потреблении ресурсов вы будете стремиться запускать как можно больше тестов, используя параллелизацию н-р.
Честно говоря, не могу понять, почему ручное составление кейсов человеком считается чем-то уникальным, интеллектуальным. Человек тоже следует правилам делать что-то если это разрешено, при составлении кейсов, Критерии качественной выборки тест-кейсов весьма субъективны, при том что выборка не содержит всей полноты возможных вариантов (да может быть наиболее часто используемые, однако на большом количестве пользователей системы менее часто используемые кейсы становятся также значимыми).
И к тому же кейсы, которые человек считает наиболее важными, можно закодить руками, а машине доверить остальные. Машинного времени не так жалко, как человеческого ресурса.

Машина сделает другой рывок, она будет писать рабочий качественный код сразу, т.к. ей не будут своественны слабости человека.
И вот пример, как стараются внедрить машинный интеллект, повышая качество кода, https://www.kitguru.net/channel/generaltech/matthew-wilson/ubisoft-is-using-machine-learning-to-spot-bugs-before-they-make-it-into-the-final-game-code/.

Ну вот, можно же выражаться просто и понятно, без всяких аббревиатур :slight_smile:

Уникальность - здесь имеется виду непохожесть сценария на другие сценарии. Поскольку генератор генерирует непохожие друг на друга сценарии, то ничего сомнительного здесь нет :slight_smile:

И да, сейчас это слишком накладно. Проще нанять с десяток студентов, или людей из смежных профессий, которые умеют думать логически, посадить кликать по кнопкам и текстовым полям, дать им зарплату, чтобы хватало на хлеб и чуть-чуть на масло, обозвать их гордым названием Quality Assurance, объявить их задачи невероятно важными, и отвественными, последний бастеон и все такое; вместо того чтобы тратиться на дорогое оборудование и безумно дорогих специалистов в области МЛ.

Автору зачёт.
Серьёзная работы была проделана.

У меня когда то была необходимость, при переписывании legacy системы на новую, заавтоматизировать кучу готовых сценариев из старой системы на новую для выполнения контрактных обязательств с клиентом.
Генерировал при помощи FreeMarker

@Vjacheslav_Lukashevich спасибо. А не подскажите, я правильно нагуглил исходники FreeMaker? К сожалению сейчас нет возможности в презентацию углубиться.

1 лайк

Пардон - не FreeMaker а FreeMarker ( https://freemarker.apache.org/ )

Ага точно :slight_smile: нагуглил я правильно, а в названии опечатался, спасибо