Пишем свой фреймворк для тестирования

Обновлено: 02.07.2024

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

Фреймворки модульного тестирования описывают тесты в человекочитаемом формате, чтобы те, кто не связан с технической сферой, смогли в них разобраться. Однако даже если вы являетесь представителем технической сферы, тесты в формате BDD могут значительно облегчить поиск проблемы.

Например, чтобы протестировать следующую функцию:

Нужно написать тест jasmine spec:

① Функция describe(string, function) определяет тестовый набор — набор индивидуальных спецификаций теста.

② Функция it(string, function) определяет отдельную спецификацию теста, которая содержит одно или несколько ожиданий теста.

③ Выражение expect(actual) — это фактическое значение в тесте. В сочетании с Matcher он описывает ожидаемый фрагмент поведения в приложении.

④ Выражение matcher(expected) — это Matcher. Он выполняет логическое сравнение ожидаемого значения с фактическим, переданным функции expect. Если они имеют ложное значение, спецификация не выполняется.

Setup и teardown

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

Эти действия называются setup и teardown (для очистки). В Jasmine есть несколько функций для упрощения этого процесса:

beforeAll вызывается один раз перед запуском всех спецификаций в тестовом наборе.

afterAll вызывается один раз после завершения всех спецификаций в тестовом наборе.

beforeEach вызывается перед каждой спецификацией теста, если функция запущена.

afterEach вызывается после выполнения каждой спецификации теста.

Использование в Node

В проекте Node файлы модульного теста определяются в папке test в одной директории с папкой src:

Тест содержит файлы спецификации, которые являются модульными тестами для файлов в папке src. В package.json test находится в разделе script.

Если в командной строке запущен npm run test, тестовый фреймворк jest запустит все файлы спецификации в папке test и отобразит результат в командной строке.

Переходим к созданию собственного тестового фреймворка, который будет работать на Node.

В нашем тестовом фреймворке будет CLI-часть, с помощью которой он будет запускаться из командной строки. Вторая часть — исходный код тестового фреймворка, который будет находиться в папке lib.

Начнем с создания проекта Node:

Устанавливаем зависимость chalk, с помощью которой мы будем раскрашивать результаты тестов: npm i chalk.

Создаем папку lib, в которой будут находиться файлы:

Создаем папку bin, поскольку фреймворк будет использоваться в качестве инструмента CLI Node:

Начнем с создания файла CLI.

Создаем файл kwuo в папке bin и добавляем следующее:

Устанавливаем шебанг с указанием на /usr/bin/env, чтобы запускать этот файл без команды node. Заголовок процесса устанавливаем на «kwuo» и запрашиваем файл «lib/cli/cli». Эти действия вызывают файл cli.js, который запускает процесс тестирования.

Переходим к установке и заполнению lib/cli/cli.js.

mkdir lib/cli
touch lib/cli/cli.js

Этот файл находит папку test, получает все файлы из нее и запускает их.

Прежде чем реализовывать «lib/cli/cli.js», нужно установить глобальные переменные. Функции describe, it, expect, afterEach, beforeEach, afterAll и beforeAll используются в тестовых файлах:

В начале работы мы добавили библиотеку chalk, чтобы обозначить неудачные тесты красным цветом, а пройденные — зеленым. Сокращаем console.log до log.

Затем устанавливаем массивы beforeEachs, afterEachs, afterAlls и beforeAlls. beforeEachs содержит функции, которые вызываются в начале функции it, к которой он прикреплен. afterEachs вызывается в конце it. beforeEachs и afterEachs вызываются в начале и конце describe.

Устанавливаем Totaltests, которая будет содержать количество запущенных тестов. passTests содержит количество пройденных тестов, а failTests — количество неудачных тестов.

stats собирает статистику каждой функции describe, а curDesc обозначает текущую функцию describe, запущенную для сбора данных тестирования. currIt содержит запущенную в данный момент функцию it, выполняющую сбор тестовых данных.

Устанавливаем функции beforeEach, afterEach, beforeAll и afterAll, которые передают аргумент функции в соответствующие массивы: afterAll — в массив afterAlls, beforeEach — в beforeEachs и т. д.

Также у нас есть функция expect. Она выполняет тестирование:

Функция expect принимает аргумент для тестирования и возвращает объект, который содержит функции matcher. В данном случае она возвращает объект с функциями toBe и toEqual с аргументом, который они используют для сопоставления с аргументом значения, предоставляемым функцией expect. toBe использует === для сопоставления аргумента значения с ожидаемым аргументом. toEqual использует == для проверки фактического значения с ожидаемым. Функции увеличивают переменные passedTests и failedTests в зависимости от результатов теста, а также записывают статистику в переменную currIt. Мы используем только две функции matcher, однако их гораздо больше:

Вы также можете реализовать их.

Переходим к функции it. Аргумент desc содержит имя описания теста, а fn — функцию. Сначала он обрабатывает beforeEachs, устанавливает статистику и вызывает функцию fn и afterEachs.

Функция describe выполняет те же действия, что и it, но вызывает beforeAlls и afterAlls в начале и в конце.

Функция showTestsResults анализирует массив stats и печатает пройденные и неудачные тесты на терминале.

Таким образом, мы реализовали и установили все функции в объект global, чтобы тестовые файлы могли вызывать их без ошибок.

Вернемся к «lib/cli/cli.js»:

Сначала он импортирует функцию showTestsResult из «lib/index.js», которая отобразит результат запуска тестовых файлов в терминале. Кроме того, импорт этого файла установит глобальные переменные.

Функция run является главной и запускает весь процесс. Она выполняет поиск папки test с помощью searchTestFolder, получает тестовые файлы в массиве — getTestFiles, а затем просматривает массив тестовых файлов и запускает их с помощью runTestFiles.

runTestFiles принимает файлы в массиве, просматривает их с помощью метода forEach и использует метод require для запуска каждого файла.


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

Фреймворки модульного тестирования описывают тесты в человекочитаемом формате, чтобы те, кто не связан с технической сферой, смогли в них разобраться. Однако даже если вы являетесь представителем технической сферы, тесты в формате BDD могут значительно облегчить поиск проблемы.

Например, чтобы протестировать следующую функцию:

Нужно написать тест jasmine spec:

① Функция describe(string, function) определяет тестовый набор — набор индивидуальных спецификаций теста.

② Функция it(string, function) определяет отдельную спецификацию теста, которая содержит одно или несколько ожиданий теста.

③ Выражение expect(actual) — это фактическое значение в тесте. В сочетании с Matcher он описывает ожидаемый фрагмент поведения в приложении.

④ Выражение matcher(expected) — это Matcher. Он выполняет логическое сравнение ожидаемого значения с фактическим, переданным функции expect. Если они имеют ложное значение, спецификация не выполняется.

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

Эти действия называются setup и teardown (для очистки). В Jasmine есть несколько функций для упрощения этого процесса:

beforeAll вызывается один раз перед запуском всех спецификаций в тестовом наборе.

afterAll вызывается один раз после завершения всех спецификаций в тестовом наборе.

beforeEach вызывается перед каждой спецификацией теста, если функция запущена.

afterEach вызывается после выполнения каждой спецификации теста.

В проекте Node файлы модульного теста определяются в папке test в одной директории с папкой src :

Тест содержит файлы спецификации, которые являются модульными тестами для файлов в папке src. В package.json test находится в разделе script .

Если в командной строке запущен npm run test , тестовый фреймворк jest запустит все файлы спецификации в папке test и отобразит результат в командной строке.

Переходим к созданию собственного тестового фреймворка, который будет работать на Node.

В нашем тестовом фреймворке будет CLI-часть, с помощью которой он будет запускаться из командной строки. Вторая часть — исходный код тестового фреймворка, который будет находиться в папке lib.

Начнем с создания проекта Node:

Устанавливаем зависимость chalk, с помощью которой мы будем раскрашивать результаты тестов: npm i chalk .

Создаем папку lib, в которой будут находиться файлы:

Создаем папку bin, поскольку фреймворк будет использоваться в качестве инструмента CLI Node:

Начнем с создания файла CLI.

Создаем файл kwuo в папке bin и добавляем следующее:

Устанавливаем шебанг с указанием на /usr/bin/env, чтобы запускать этот файл без команды node. Заголовок процесса устанавливаем на “kwuo” и запрашиваем файл “lib/cli/cli”. Эти действия вызывают файл cli.js, который запускает процесс тестирования.

Переходим к установке и заполнению lib/cli/cli.js.

Этот файл находит папку test, получает все файлы из нее и запускает их.

Прежде чем реализовывать “lib/cli/cli.js”, нужно установить глобальные переменные. Функции describe, it, expect, afterEach, beforeEach, afterAll и beforeAll используются в тестовых файлах:

Однако ни одна из них не определена в этих файлах. Каким образом файлы и функции работают без ReferenceError? Причина в том, что тестовый фреймворк реализовывает функции и устанавливает их как global перед запуском тестовых файлов.

Создаем файл index.js в папке lib:

Здесь мы устанавливаем глобальные переменные и реализуем функции describe , it , expect , afterEach , beforeEach , afterAll и beforeAll :

В начале работы мы добавили библиотеку chalk, чтобы обозначить неудачные тесты красным цветом, а пройденные — зеленым. Сокращаем console.log до log.

Затем устанавливаем массивы beforeEachs, afterEachs, afterAlls и beforeAlls. beforeEachs содержит функции, которые вызываются в начале функции it , к которой он прикреплен. afterEachs вызывается в конце it . beforeEachs и afterEachs вызываются в начале и конце describe .

Устанавливаем Totaltests , которая будет содержать количество запущенных тестов. passTests содержит количество пройденных тестов, а failTests - количество неудачных тестов.

stats собирает статистику каждой функции describe, а curDesc обозначает текущую функцию describe, запущенную для сбора данных тестирования. currIt содержит запущенную в данный момент функцию it, выполняющую сбор тестовых данных.

Устанавливаем функции beforeEach, afterEach, beforeAll и afterAll, которые передают аргумент функции в соответствующие массивы: afterAll — в массив afterAlls, beforeEach — в beforeEachs и т. д.

Также у нас есть функция expect. Она выполняет тестирование:

Функция expect принимает аргумент для тестирования и возвращает объект, который содержит функции matcher. В данном случае она возвращает объект с функциями toBe и toEqual с аргументом, который они используют для сопоставления с аргументом значения, предоставляемым функцией expect. toBe использует === для сопоставления аргумента значения с ожидаемым аргументом. toEqual использует == для проверки фактического значения с ожидаемым. Функции увеличивают переменные passedTests и failedTests в зависимости от результатов теста, а также записывают статистику в переменную currIt. Мы используем только две функции matcher, однако их гораздо больше:

Вы также можете реализовать их.

Переходим к функции it . Аргумент desc содержит имя описания теста, а fn - функцию. Сначала он обрабатывает beforeEachs, устанавливает статистику и вызывает функцию fn и afterEachs.

Функция describe выполняет те же действия, что и it , но вызывает beforeAlls и afterAlls в начале и в конце.

Функция showTestsResults анализирует массив stats и печатает пройденные и неудачные тесты на терминале.

Таким образом, мы реализовали и установили все функции в объект global , чтобы тестовые файлы могли вызывать их без ошибок.

Вернемся к “lib/cli/cli.js”:

Сначала он импортирует функцию showTestsResult из "lib/index.js", которая отобразит результат запуска тестовых файлов в терминале. Кроме того, импорт этого файла установит глобальные переменные.

Функция run является главной и запускает весь процесс. Она выполняет поиск папки test с помощью searchTestFolder , получает тестовые файлы в массиве - getTestFiles , а затем просматривает массив тестовых файлов и запускает их с помощью runTestFiles .

runTestFiles принимает файлы в массиве, просматривает их с помощью метода forEach и использует метод require для запуска каждого файла.

Структура папки kwuo выглядит следующим образом:


Попробуем протестировать наш фреймворк с реальным проектом Node.

Создаем папку src и добавляем add.js и sub.js:

Содержимое add.js и sub.js:

Создаем папку test и тестовые файлы:

Файлы спецификации будут проверять функции add и sub в add.js и sub.js:

Теперь воспользуемся “test” в разделе “scripts” в package.json для запуска тестового фреймворка:

Запускаем npm run test в командной строке:

Результат теста будет следующим:


В статистике указано количество пройденных тестов в общей сложности и список тестовых наборов с маркировкой пройден/неудачный. “add Hello + World” возвращает “HelloWorld”, однако ожидалось “Hello”. После исправления и повторного запуска, все тесты будут пройдены.

Мы продолжаем рассказывать о QA. Руководитель отдела тестирования Дмитрий Рак расскажет об архитектуре фреймворка для автоматизации тестирования.

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

  • Создать-то фреймворк − они создали, но не объяснили, зачем;
  • Может быть даже какие-то из компонентов забыли;
  • Мы получаем невероятно удовольствие от изобретения очередного колеса через муки и страдания.

Зачем автоматизировать тестирование?

Цели автоматизации тестирования QA Automation - Как создать фреймворк для автоматизации тестирования

Цели создания фреймворка для автоматизации:

  • Не хотим писать один и тот же код дважды;
  • После создания фреймворка – написание тестов должно свестись к простому составлению последовательности шагов, вытекающих в тестовые сценарии.

Разработчики каждый раз пугаются фразе «создать фреймворк», считая, что это похоже на разработку собственного приложения/библиотеки/модуля. Реальность отличается: фреймворк для автоматизации тестирования − это адаптация нескольких решений, имеющихся на рынке, под нужды конкретного проекта.

Ниже расскажем, из чего может состоять фреймворк и какие цели та или иная его часть может преследовать. Добавим суматохи в ряды автоматизаторов и поговорим в формате User Stories, где каждая будет начинаться с привычных и столь нелюбимых нами: «Как автоматизатор я хочу. ».

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

Вечный выбор между record-replay или языками программирования. Я адепт решений, связанных с языками программирования потому что:

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

Если говорить о выборе языка программирования, то холивары на этот счет никогда не прекращаются в связи с тем, что:

  • Бытует миф, что разработчики могут помогать писать автотесты автоматизаторам (на самом деле, нет);
  • Говорят, что хранить код продукта вместе с кодом автотестов − это правильно.

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

Далее мы рассмотрим примеры, приближенные к языку программирования Java. Тем не менее, информация ниже применима в любом из языков программирования.

Начнем. Подключаем любой build tool, например, Maven/Ant+Ivy/Gradle и наслаждаемся простотой добавления и широким выбором библиотек, которые можно использовать в проекте. Приятный бонус: запуск тестов из командной строки, что пригодится нам на этапе внедрения в CI, но об этом позже.

Как автоматизатор я хочу иметь возможность запускать тесты в параллели, использовать Asserts и группировку по тест-пакетам.

Для запуска тестов в параллели проще всего использовать xUnit-библиотеки. То есть хватаем JUnit или TestNG, и вуаля. Я фанат TestNG. Возможно, в паре с Hamcrest из-за большего количества функциональности и поддержки от создателей. Говорят, JUnit 5 просто космос. Пишите ваше мнение в комментариях.

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

Ниже разговор пойдет о фреймворках, так или иначе касающихся взаимодействия с UI-частью приложения через Selenium. И здесь не о библиотеке, а о стандартном подходе в Page Object Model, где в каждом из проектов описаны такие пакеты/модули:

  • Elements − взаимодействие с кнопками/инпутами и другими элементами, доступными в приложении. Здесь и ожидания, и логирование, и обработка исключения;
  • Pages − описание локаторов и действий с каждым из элементов, используя классы из elements;
  • Steps − объединение отдельных действий из разных страниц в так называемые business-scenarios. Ценные для конечного пользователя/описываемые в репортинге. Создатели Serenity прекрасно «переиспользовали» модель у себя во фреймворке, но, к сожалению, пожертвовали гибкостью;
  • Tests − из отдельных шагов собирается тестовый сценарий. Из тестов только пробрасывается ввод от конечного пользователя.

Окей, элементы расписаны. Что дальше?

Вакансии для QA Engineers - Гайд для QA Engineers: как создать фреймворк для автоматизации тестирования

Из них составляем страницы, из страниц готовим наборы бизнес-шагов, из них − тестовые сценарии.

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

Если тестовые сценарии написаны с помощью UI-библиотеки, например, Selenium, то со временем вы удивитесь почему один небольшой тест занимает 5-10 минут времени. Проблема в создании тестовых данных. Поэтому чем раньше в вашем фреймворке появится возможность использовать REST API или базу данных приложения, тем лучше. REST Assured и JDBC вам в помощь. Приятным бонусом будет то, что теперь внутри фреймворка можно делать тестовую пирамиду и автоматизировать часть проверок, используя только REST API.

Как автоматизатор я хочу забыть, где объявлен WebDriver, забыть о проблемах с параллелизацией и частыми StaleElementReferenceException.

Ребята из Selenide за последние несколько лет сделали многое для того, чтобы мы писали меньше кода.

  • Встроенные ожидания;
  • WebDriver не хранится как поле класса, а вызывается методом getWebDriver из любой точки кода. Не конфликтует друг с другом из коробки при параллельном запуске;
  • StaleElementReferenceException побежден раз и навсегда за счет «умного» обращения к DOM вашей страницы под капотом Selenide.

Как автоматизатор я хочу запускать тесты не у себя на компьютере.

Словосочетание Continuous Integration и так всем известно. Зачем? Чтобы не пить чай, пока запускаются тесты, а продолжать разрабатывать новые. В это время старые генерируют нам проблемы. Здесь подробный список доступных сейчас CI tools. Из приятных нововведений, появившихся не так давно: CircleCI для Selenium-like фреймворка заводится за несколько минут и предоставляет 240 бесплатных минут в неделю.

Как автоматизатор я хочу понимать, почему мои тесты упали.

JavaScript

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

Фреймворки модульного тестирования описывают тесты в человекочитаемом формате, чтобы те, кто не связан с технической сферой, смогли в них разобраться. Однако даже если вы являетесь представителем технической сферы, тесты в формате BDD могут значительно облегчить поиск проблемы.

Например, чтобы протестировать следующую функцию:

Нужно написать тест jasmine spec:

② Функция it(string, function) определяет отдельную спецификацию теста, которая содержит одно или несколько ожиданий теста.

Setup и teardown

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

Эти действия называются setup и teardown (для очистки). В Jasmine есть несколько функций для упрощения этого процесса:

beforeAll вызывается один раз перед запуском всех спецификаций в тестовом наборе.

afterAll вызывается один раз после завершения всех спецификаций в тестовом наборе.

beforeEach вызывается перед каждой спецификацией теста, если функция запущена.

afterEach вызывается после выполнения каждой спецификации теста.

Использование в Node

В проекте Node файлы модульного теста определяются в папке test в одной директории с папкой src :

Тест содержит файлы спецификации, которые являются модульными тестами для файлов в папке src. В package.json test находится в разделе script .

Если в командной строке запущен npm run test , тестовый фреймворк jest запустит все файлы спецификации в папке test и отобразит результат в командной строке.

Переходим к созданию собственного тестового фреймворка, который будет работать на Node.

Начнем с создания проекта Node:

Устанавливаем зависимость chalk, с помощью которой мы будем раскрашивать результаты тестов: npm i chalk .

Создаем папку lib, в которой будут находиться файлы:

Создаем папку bin, поскольку фреймворк будет использоваться в качестве инструмента CLI Node:

Начнем с создания файла CLI.

Создаем файл kwuo в папке bin и добавляем следующее:

Переходим к установке и заполнению lib/cli/cli.js.

Этот файл находит папку test, получает все файлы из нее и запускает их.

Однако ни одна из них не определена в этих файлах. Каким образом файлы и функции работают без ReferenceError? Причина в том, что тестовый фреймворк реализовывает функции и устанавливает их как global перед запуском тестовых файлов.

Создаем файл index.js в папке lib:

Здесь мы устанавливаем глобальные переменные и реализуем функции describe , it , expect , afterEach , beforeEach , afterAll и beforeAll :

Затем устанавливаем массивы beforeEachs, afterEachs, afterAlls и beforeAlls. beforeEachs содержит функции, которые вызываются в начале функции it , к которой он прикреплен. afterEachs вызывается в конце it . beforeEachs и afterEachs вызываются в начале и конце describe .

stats собирает статистику каждой функции describe, а curDesc обозначает текущую функцию describe, запущенную для сбора данных тестирования. currIt содержит запущенную в данный момент функцию it, выполняющую сбор тестовых данных.

Также у нас есть функция expect. Она выполняет тестирование:

Функция expect принимает аргумент для тестирования и возвращает объект, который содержит функции matcher. В данном случае она возвращает объект с функциями toBe и toEqual с аргументом, который они используют для сопоставления с аргументом значения, предоставляемым функцией expect. toBe использует === для сопоставления аргумента значения с ожидаемым аргументом. toEqual использует == для проверки фактического значения с ожидаемым. Функции увеличивают переменные passedTests и failedTests в зависимости от результатов теста, а также записывают статистику в переменную currIt. Мы используем только две функции matcher, однако их гораздо больше:

  • toThrow
  • toBeNull
  • toBeFalsy
  • и т. д.

Вы также можете реализовать их.

Функция describe выполняет те же действия, что и it , но вызывает beforeAlls и afterAlls в начале и в конце.

Функция showTestsResults анализирует массив stats и печатает пройденные и неудачные тесты на терминале.

Таким образом, мы реализовали и установили все функции в объект global , чтобы тестовые файлы могли вызывать их без ошибок.

runTestFiles принимает файлы в массиве, просматривает их с помощью метода forEach и использует метод require для запуска каждого файла.

Структура папки kwuo выглядит следующим образом:


Тестирование фреймворка

Попробуем протестировать наш фреймворк с реальным проектом Node.

Создаем папку src и добавляем add.js и sub.js:

Содержимое add.js и sub.js:

Создаем папку test и тестовые файлы:

Файлы спецификации будут проверять функции add и sub в add.js и sub.js:

Запускаем npm run test в командной строке:

Результат теста будет следующим:


Читайте также: