Статьи

BDD в JavaScript: начало работы с огурцом и огурцом

BDD в JavaScript: начало работы с огурцом и огурцом

К настоящему времени все слышали о разработке через тестирование (TDD) и о преимуществах, которые это может иметь для вашего продукта и вашего жизненного цикла разработки. Это действительно просто. Каждый раз, когда вы пишете тест для части кода, вы знаете, что код работает. Более того, вы узнаете в будущем, если этот код сломается.

Behavior Driven Development (BDD) является расширением этой концепции, но вместо того, чтобы тестировать свой код, вы тестируете свой продукт и, в частности, ваш продукт ведет себя так, как вы этого хотите.

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

Готов? Тогда давайте погрузимся.

BDD против TDD — так в чем же разница?

Первоначально в том, как тесты структурированы и написаны.

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

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

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

Это разница между:

const assert = require('assert'); const webdriver = require('selenium-webdriver'); const browser = new webdriver.Builder() .usingServer() .withCapabilities({'browserName': 'chrome' }) .build(); browser.get('http://en.wikipedia.org/wiki/Wiki'); browser.findElements(webdriver.By.css('[href^="/wiki/"]')) .then(function(links){ assert.equal(19, links.length); // Made up number browser.quit(); }); 

И:

 Given I have opened a Web Browser When I load the Wikipedia article on "Wiki" Then I have "19" Wiki Links 

Два теста делают одно и то же, но один на самом деле читается человеком, а другой — только тем, кто знает и JavaScript, и Selenium.

Эта статья покажет вам, как вы можете внедрить BDD-тесты в свой проект JavaScript с помощью фреймворка Cucumber.js, что позволит вам использовать этот уровень тестирования для вашего продукта.

Что такое огурец / корнишон?

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

Огурец — это доменоспецифический язык (DSL), который используется для написания тестов на огурец. Это позволяет писать сценарии тестирования в удобочитаемом формате, который затем может быть использован всеми заинтересованными сторонами в разработке продукта.

Файлы корнишона — это файлы, содержащие тесты, написанные на языке корнишона. Эти файлы обычно имеют расширение .feature . Содержимое этих файлов корнишонов часто называют просто «корнишонами».

Огурцы

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

Сценарий — это буквально один тест. Это должно проверить ровно одну вещь в вашем приложении.

Функция представляет собой группу связанных сценариев. Таким образом, он будет тестировать много связанных вещей в вашем приложении. В идеале функции в файлах Gherkin будут тесно связаны с компонентами в приложении — отсюда и название.

Каждый файл Gherkin содержит ровно одну функцию, и каждая функция содержит один или несколько сценариев.

Сценарии затем состоят из шагов, которые упорядочены определенным образом:

  • Дано — Эти шаги используются для установки начального состояния перед выполнением теста.
  • Когда — Эти шаги являются фактическим тестом, который должен быть выполнен
  • Затем — Эти шаги используются для подтверждения результатов теста.

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

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

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

Пример теста с огурцом

Ниже приведен пример Gherkin для поиска в Google для Cucumber.js.

 Given I have loaded Google When I search for "cucumber.js" Then the first result is "GitHub - cucumber/cucumber-js: Cucumber for JavaScript" 

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

Вы можете прочитать больше о « Given When Then» на вики «Огурец».

Cucumber.js

После того, как вы написали свои тестовые примеры в форме Gherkin, вам нужен какой-то способ их выполнения. В мире JavaScript есть модуль Cucumber.js, который позволяет вам это делать. Он работает, позволяя вам определять код JavaScript, который он может подключать к различным шагам, определенным в ваших файлах Gherkin. Затем он запускает тесты, загружая файлы Gherkin и выполняя код JavaScript, связанный с каждым шагом, в правильном порядке.

Например, в приведенном выше примере вы должны выполнить следующие шаги:

 Given('I have loaded Google', function() {}); When('I search for {stringInDoubleQuotes}', function() {}); Then('the first result is {stringInDoubleQuotes}', function() {}); 

Не беспокойтесь о том, что все это значит — это будет подробно объяснено позже. По сути, он определяет некоторые способы, которыми фреймворк Cucumber.js может связать ваш код с шагами в ваших файлах Gherkin.

Включение Cucumber.js в вашу сборку

Включить Cucumber.js в свою сборку так же просто, как добавить модуль cucumber в свою сборку, а затем настроить его для запуска. Первая часть этого так же просто, как:

 $ npm install --save-dev cucumber 

Второй из них зависит от того, как вы выполняете сборку.

Работает вручную

Выполнить Cucumber вручную довольно просто, и неплохо сначала убедиться, что вы можете сделать это, потому что следующие решения — это просто автоматизированные способы сделать то же самое.

После установки исполняемый файл будет ./node_modules/.bin/cucumber.js . Когда вы запускаете его, ему нужно знать, где в файловой системе он может найти все необходимые файлы. Это и файлы Gherkin, и код JavaScript, который нужно выполнить.

По соглашению, все ваши файлы Gherkin будут храниться в каталоге features , и если вы не укажете его в противном случае, то Cucumber будет искать в том же каталоге код JavaScript для выполнения. Указывать, где искать эти файлы, — разумная практика, чтобы вы лучше контролировали процесс сборки.

Например, если вы храните все свои файлы myFeatures в каталоге myFeatures и весь свой код JavaScript в mySteps вы можете выполнить следующее:

 $ ./node_modules/.bin/cucumber.js ./myFeatures -r ./mySteps 

Флаг -r — это каталог, содержащий файлы JavaScript, которые автоматически требуются для тестов. Также могут быть интересны другие флаги — просто прочитайте текст справки, чтобы увидеть, как они все работают: $ ./node_modules/.bin/cucumber.js --help .

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

сценарии npm

После того как Cucumber запущен вручную, добавление его в сборку в виде сценария npm является тривиальным случаем. Вам просто нужно добавить следующую команду — без полного пути, поскольку npm обрабатывает его для вас — в ваш package.json следующим образом:

 "scripts": { "cucumber": "cucumber.js ./myFeatures -r ./mySteps" } 

Как только это будет сделано, вы можете выполнить:

 $ npm run cucumber 

И он будет выполнять ваши тесты Cucumber точно так же, как вы делали раньше.

хрюкать

Существует плагин Grunt для выполнения тестов Cucumber.js. К сожалению, он очень устарел и не работает с более свежими версиями Cucumber.js, что означает, что вы пропустите множество улучшений, если будете его использовать.

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

После установки это просто добавление следующей конфигурации плагина в ваш Gruntfile.js :

 shell: { cucumber: { command: 'cucumber.js ./myFeatures -r ./mySteps' } } 

И теперь, как и раньше, вы можете выполнить свои тесты, запустив grunt shell:cucumber .

Глоток

Gulp находится в точно такой же ситуации, что и Grunt, в том смысле, что существующие плагины очень устарели и будут использовать старую версию инструмента Cucumber. Опять же, здесь вы можете использовать модуль gulp-shell для выполнения команды Cucumber.js, как и в других сценариях.

Настроить это так же просто, как:

 gulp.task('cucumber', shell.task([ 'cucumber.js ./myFeatures -r ./mySteps' ])); 

И теперь, как и раньше, вы можете выполнить свои тесты, запустив gulp cucumber .

Ваш первый тест на огурец

Обратите внимание, что все примеры кода в этой статье доступны на GitHub .

Теперь, когда мы знаем, как выполнить Cucumber, давайте на самом деле напишем тест. В этом примере мы собираемся сделать что-то довольно изобретательное, чтобы показать систему в действии. На самом деле вы будете делать что-то гораздо более сложное, например, напрямую вызывать код, который вы тестируете, делать HTTP-вызовы API для работающей службы или управлять Selenium для запуска веб-браузера для тестирования вашего приложения.

Наш простой пример докажет, что математика все еще работает. У нас будет две функции — сложение и умножение.

Во-первых, давайте настроимся.

 $ npm init $ npm install --save-dev cucumber $ mkdir features steps 

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

 $ ./node_modules/.bin/cucumber.js features/ -r steps/ 0 scenarios 0 steps 0m00.000s $ 

Теперь давайте напишем нашу первую актуальную функцию. Это пойдет в features/addition.feature :

 Feature: Addition Scenario: 1 + 0 Given I start with 1 When I add 0 Then I end up with 1 Scenario: 1 + 1 Given I start with 1 When I add 1 Then I end up with 2 

Очень просто, очень легко читать. Говорит нам точно, что мы делаем, и ничего о том, как мы это делаем. Давайте попробуем это:

 $ ./node_modules/.bin/cucumber.js features/ -r steps/ Feature: Addition Scenario: 1 + 0 ? Given I start with 1 ? When I add 0 ? Then I end up with 1 Scenario: 1 + 1 ? Given I start with 1 ? When I add 1 ? Then I end up with 2 Warnings: 1) Scenario: 1 + 0 - features/addition.feature:3 Step: Given I start with 1 - features/addition.feature:4 Message: Undefined. Implement with the following snippet: Given('I start with {int}', function (int, callback) { // Write code here that turns the phrase above into concrete actions callback(null, 'pending'); }); 2) Scenario: 1 + 0 - features/addition.feature:3 Step: When I add 0 - features/addition.feature:5 Message: Undefined. Implement with the following snippet: When('I add {int}', function (int, callback) { // Write code here that turns the phrase above into concrete actions callback(null, 'pending'); }); 3) Scenario: 1 + 0 - features/addition.feature:3 Step: Then I end up with 1 - features/addition.feature:6 Message: Undefined. Implement with the following snippet: Then('I end up with {int}', function (int, callback) { // Write code here that turns the phrase above into concrete actions callback(null, 'pending'); }); 4) Scenario: 1 + 1 - features/addition.feature:8 Step: Given I start with 1 - features/addition.feature:9 Message: Undefined. Implement with the following snippet: Given('I start with {int}', function (int, callback) { // Write code here that turns the phrase above into concrete actions callback(null, 'pending'); }); 5) Scenario: 1 + 1 - features/addition.feature:8 Step: When I add 1 - features/addition.feature:10 Message: Undefined. Implement with the following snippet: When('I add {int}', function (int, callback) { // Write code here that turns the phrase above into concrete actions callback(null, 'pending'); }); 6) Scenario: 1 + 1 - features/addition.feature:8 Step: Then I end up with 2 - features/addition.feature:11 Message: Undefined. Implement with the following snippet: Then('I end up with {int}', function (int, callback) { // Write code here that turns the phrase above into concrete actions callback(null, 'pending'); }); 2 scenarios (2 undefined) 6 steps (6 undefined) 0m00.000s $ 

Вау. Мы только что написали нашего корнишона, и все выполняется. Это не работает, потому что мы еще не знаем, что делать с этими шагами, но Cucumber говорит нам об этом очень четко.

Давайте напишем наш первый файл шага тогда. Это просто реализует шаги так, как нам подсказывает вывод Cucumber, что не делает ничего полезного, но приводит в порядок вывод.

Это идет в steps/maths.js :

 const defineSupportCode = require('cucumber').defineSupportCode; defineSupportCode(function({ Given, Then, When }) { Given('I start with {int}', function (int, callback) { // Write code here that turns the phrase above into concrete actions callback(null, 'pending'); }); When('I add {int}', function (int, callback) { // Write code here that turns the phrase above into concrete actions callback(null, 'pending'); }); Then('I end up with {int}', function (int, callback) { // Write code here that turns the phrase above into concrete actions callback(null, 'pending'); }); }); 

defineSupportCode — это способ Cucumber.js, позволяющий вам предоставлять код, который он будет использовать для различных ситуаций. Все они будут рассмотрены, но, по сути, каждый раз, когда вы захотите написать код, который Cucumber будет вызывать напрямую, он должен находиться внутри одного из этих блоков.

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

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

 $ ./node_modules/.bin/cucumber.js features/ -r steps/ Feature: Addition Scenario: 1 + 0 ? Given I start with 1 - When I add 0 - Then I end up with 1 Scenario: 1 + 1 ? Given I start with 1 - When I add 1 - Then I end up with 2 Warnings: 1) Scenario: 1 + 0 - features/addition.feature:3 Step: Given I start with 1 - features/addition.feature:4 Step Definition: steps/maths.js:4 Message: Pending 2) Scenario: 1 + 1 - features/addition.feature:8 Step: Given I start with 1 - features/addition.feature:9 Step Definition: steps/maths.js:4 Message: Pending 2 scenarios (2 pending) 6 steps (2 pending, 4 skipped) 0m00.002s В $ ./node_modules/.bin/cucumber.js features/ -r steps/ Feature: Addition Scenario: 1 + 0 ? Given I start with 1 - When I add 0 - Then I end up with 1 Scenario: 1 + 1 ? Given I start with 1 - When I add 1 - Then I end up with 2 Warnings: 1) Scenario: 1 + 0 - features/addition.feature:3 Step: Given I start with 1 - features/addition.feature:4 Step Definition: steps/maths.js:4 Message: Pending 2) Scenario: 1 + 1 - features/addition.feature:8 Step: Given I start with 1 - features/addition.feature:9 Step Definition: steps/maths.js:4 Message: Pending 2 scenarios (2 pending) 6 steps (2 pending, 4 skipped) 0m00.002s В $ ./node_modules/.bin/cucumber.js features/ -r steps/ Feature: Addition Scenario: 1 + 0 ? Given I start with 1 - When I add 0 - Then I end up with 1 Scenario: 1 + 1 ? Given I start with 1 - When I add 1 - Then I end up with 2 Warnings: 1) Scenario: 1 + 0 - features/addition.feature:3 Step: Given I start with 1 - features/addition.feature:4 Step Definition: steps/maths.js:4 Message: Pending 2) Scenario: 1 + 1 - features/addition.feature:8 Step: Given I start with 1 - features/addition.feature:9 Step Definition: steps/maths.js:4 Message: Pending 2 scenarios (2 pending) 6 steps (2 pending, 4 skipped) 0m00.002s 

Теперь, чтобы все это заработало. Все, что нам нужно сделать, это реализовать код в наших определениях шагов. Мы также собираемся немного навести порядок, чтобы было легче читать. Это по существу устраняет необходимость в параметре callback поскольку мы не делаем ничего асинхронного.

После этого наши «steps / maths.js» будут выглядеть так:

 const defineSupportCode = require('cucumber').defineSupportCode; const assert = require('assert'); defineSupportCode(function({ Given, Then, When }) { let answer = 0; Given('I start with {int}', function (input) { answer = input; }); When('I add {int}', function (input) { answer = answer + input; }); Then('I end up with {int}', function (input) { assert.equal(answer, input); }); }); 

И выполнение выглядит так:

 $ ./node_modules/.bin/cucumber.js features/ -r steps/ Feature: Addition Scenario: 1 + 0  Given I start with 1  When I add 0  Then I end up with 1 Scenario: 1 + 1  Given I start with 1  When I add 1  Then I end up with 2 2 scenarios (2 passed) 6 steps (6 passed) 0m00.001s 

Все проходит. Теперь мы знаем, что сложение работает правильно.

Обратите внимание, что нам нужно было написать совсем немного кода, и система Cucumber склеивает все это вместе.
Мы получили автоматические параметризованные тесты, просто указав, как код шага выполняется из файлов Gherkin. Это означает, что добавить еще много сценариев действительно просто.

Далее, давайте докажем, что умножение работает также. Для этого мы напишем следующий огурец в файле features/multiplication.feature :

 Feature: Multiplication Scenario: 1 * 0 Given I start with 1 When I multiply by 0 Then I end up with 0 Scenario: 1 * 1 Given I start with 1 When I multiply by 1 Then I end up with 1 Scenario: 2 + 2 Given I start with 2 When I multiply by 2 Then I end up with 4 

А затем давайте реализуем новый шаг в наших steps/maths.js . Для этого нам просто нужно добавить следующий блок внутри метода defineSupportCode :

 When('I multiply by {int}', function (input) { answer = answer * input; }); 

Вот и все. Выполнение этого даст следующие результаты:

 $ ./node_modules/.bin/cucumber.js features/ -r steps/ Feature: Addition Scenario: 1 + 0  Given I start with 1  When I add 0  Then I end up with 1 Scenario: 1 + 1  Given I start with 1  When I add 1  Then I end up with 2 Feature: Multiplication Scenario: 1 * 0  Given I start with 1  When I multiply by 0  Then I end up with 0 Scenario: 1 * 1  Given I start with 1  When I multiply by 1  Then I end up with 1 Scenario: 2 + 2  Given I start with 2  When I multiply by 2  Then I end up with 4 5 scenarios (5 passed) 15 steps (15 passed) 0m00.003s $ 

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

Более продвинутые хитрости Cucumber.js

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

Определения асинхронных шагов

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

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

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

Например, определение шага для выполнения вызова API HTTP с использованием обратных вызовов может выглядеть следующим образом. Это написано с использованием запроса, поскольку при ответе используются обратные вызовы.

 When('I make an API call using callbacks', function(callbacks) { request('http://localhost:3000/api/endpoint', (err, response, body) => { if (err) { callback(err); } else { doSomethingWithResponse(body); callback(); } }); }); 

Альтернативным и предпочтительным способом является тип возвращаемого значения. Если вы вернете Обещание со своего шага, то этот шаг будет считаться завершенным только после выполнения Обещания. Если обещание будет отклонено, то шаг не будет выполнен, а если обещание выполнено, то шаг будет выполнен успешно.

В противном случае, если вы вернете что-то, что не является Обещанием, то Шаг сразу будет считаться успешным. Это включает в себя возврат undefined или null . Это означает, что вы можете выбрать во время выполнения шага, нужно ли вам возвращать обещание или нет, и среда будет адаптироваться по мере необходимости.

Например, определение шага для выполнения вызова HTTP API с помощью Promises может выглядеть следующим образом. Это написано с использованием Fetch API, поскольку оно возвращает Promise при ответе.

 When('I make an API call using promises', function() { return fetch('http://localhost:3000/api/endpoint') .then(res => res.json()) .then(body => doSomethingWithResponse(body)); }); 

Особенность фона

Фоновый объект — это фрагмент корнишона, который добавляется к началу каждого сценария в файле. Это позволяет легко распределять общие шаги настройки между сценариями без необходимости их повторения.

Фоны пишутся с использованием ключевого слова Background вместо ключевого слова Scenario . В идеале должны быть включены только заданные шаги, поскольку нет смысла включать шаги «когда» или «потом», которые разделяются между каждым тестом. Тем не менее, среда не будет ограничивать вас в этом, поэтому будьте осторожны в том, как вы структурируете свои тесты.

Используя это, мы можем переписать нашу функцию сложения следующим образом:

 Feature: Addition Background: Given I start with 1 Scenario: 1 + 0 When I add 0 Then I end up with 1 Scenario: 1 + 1 When I add 1 Then I end up with 2 

На самом деле это точно так же, как и раньше, но это немного короче, так как мы учли общий шаг настройки.

Контуры сценария

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

Схемы сценариев пишутся с использованием ключевого слова Scenario Outline вместо ключевого слова Scenario , а затем с использованием одной или нескольких таблиц Examples . Параметры из таблиц Examples затем подставляются в Scenario Outline для создания сценариев, которые выполняются.

Используя это, мы можем переписать нашу функцию умножения следующим образом:

 Feature: Multiplication Scenario Outline: <a> * <b> Given I start with <a> When I multiply by <b> Then I end up with <answer> Examples: | a | b | answer | | 1 | 0 | 0 | | 1 | 1 | 1 | | 2 | 2 | 4 | 

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

 Feature: Multiplication Scenario: 1 * 0  Given I start with 1  When I multiply by 0  Then I end up with 0 Scenario: 1 * 1  Given I start with 1  When I multiply by 1  Then I end up with 1 Scenario: 2 * 2  Given I start with 2  When I multiply by 2  Then I end up with 4 

Таблицы данных

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

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

 Scenario: Add numbers Given I start with 0 When I add the following numbers: | 1 | | 2 | | 3 | | 4 | Then I end up with 10 

В этом простом примере шаг будет выглядеть примерно так:

 When('I add the following numbers:', function (table) { answer = table.raw() .map(row => row[0]) .map(v => parseInt(v)) .reduce((current, next) => current + next, answer); }); 

Предоставляемый нами параметр table — это объект DataTable , в котором есть raw метод, который вы можете вызвать. Этот метод возвращает двумерный массив всех значений в таблице данных, так что каждая запись во внешнем массиве является строкой в ​​таблице, а каждая запись во внутреннем массиве представляет собой ячейку из этой строки — в виде строки.

Более сложным примером может быть использование таблицы данных для заполнения формы. Затем можно использовать таблицу для предоставления всех входных данных, вместо того, чтобы иметь очень сложное для чтения определение шага. Это может читать что-то вроде:

 Scenario: Create a new user When I create a new user with details: | Username | graham | | Email | [email protected] | | Password | mySecretPassword | Then the user is created successfully 

В этом случае класс таблицы данных может упростить нам доступ к таблице с помощью метода rowsHash .

Наш шаг для этого может выглядеть так:

 When('I create a new user with details:', function (table) { const data = table.rowsHash(); createUser(data); }); 

В этом случае объект data будет проанализирован из таблицы данных и будет выглядеть следующим образом:

 { "Username": "graham", "Email": "[email protected]", "Password": "mySecretPassword" } 

Сделать доступ к полям очень простым с помощью клавиш в первом столбце.

Крючки

Как и большинство тестовых сред, Cucumber.js поддерживает хуки , которые выполняются до и после запуска сценария.

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

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

 defineSupportCode(function({ Before, Given, Then, When }) { let answer; Before(function() { answer = 0; }); }); 

Расширение нашего файла шагов по математике, как указано выше, гарантирует, что переменная answer будет сброшена до 0 перед каждым сценарием, что означает, что нам не нужен данный шаг, если мы начинаем с 0.

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

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

События

Если по каким-либо причинам вам не хватает простых хуков «до» и «после», то есть еще много событий для изучения . Это дает нам возможность обрабатывать:

  • BeforeFeatures — вызывается один раз, прежде чем что-либо запускается, со списком функций.
  • BeforeFeature — вызывается перед запуском каждого файла компонента, который предоставляется вместе с компонентом.
  • BeforeScenario — вызывается перед запуском каждого сценария, предоставленного в сценарии. Это примерно аналогично крючку «До».
  • BeforeStep — вызывается перед каждым шагом, предоставляется вместе с шагом.
  • StepResult — вызывается после выполнения каждого шага, предоставляется результат шага.
  • AfterStep — вызывается после выполнения каждого шага, предоставляется вместе с шагом.
  • ScenarioResult — вызывается после запуска каждого сценария, предоставляется результат сценария.
  • AfterScenario — вызывается после запуска каждого сценария, предоставленного в сценарии. Это примерно аналогично крюку «После».
  • AfterFeature — вызывается после запуска каждой функции, предоставляемой функцией.
  • FeaturesResult — вызывается один раз после того, как все выполнено, предоставляется результат запуска всего.
  • AfterFeatures — вызывается один раз после запуска, предоставляется список функций.

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

Обработка этих событий осуществляется с помощью метода registerHandler из метода defineSupportCode . Это может выглядеть примерно так:

 defineSupportCode(function({ registerHandler }) { registerHandler('BeforeStep', function(step) { console.log('About to execute step:' + util.inspect(step)); }); registerHandler('ScenarioResult', function(scenario) { console.log('Result of Scenario:' + util.inspect(scenario)); }); }); 

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

Мир — Обмен кодом и данными

До сих пор у нас не было возможности делиться кодом между шагами. Мы можем довольно легко иметь столько файлов JavaScript, которые содержат определения шагов, ловушки, события и т. Д., Но все они независимы друг от друга (не считая трюков с системой Node Module для сохранения состояния).

Как это бывает, это не так. Cucumber.js имеет концепцию «мира», то есть состояния, с которым работает сценарий. Все определения шагов, перехватчики и обработчики событий имеют доступ к этому, получая доступ к параметру this , независимо от файла, в котором определено определение шага. Именно поэтому все примеры написаны с использованием традиционного ключевого слова function вместо стрелочных функций , Функции стрелок в JavaScript перепривязывают переменную this для вас, что означает, что вы теряете доступ к состоянию World, которое может вам понадобиться в ваших тестах.

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

Резюме

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

Эта статья лишь поверхностно рассказывает о том, на что способен Cucumber, поэтому я призываю вас попробовать его самостоятельно, чтобы почувствовать его силу. Cucumber также имеет очень активное сообщество, и их список рассылки и канал Gitter — отличные способы обратиться за помощью, если она вам понадобится.

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

Эта статья была рецензирована Яни Хартикайнен . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!