cucumberjsを使ってみる


- Gherkin syntaxを使いたい
- AWS Lambdaで使いたい
- 知ってる言語ベースだと更に助かる

上記の理由から、nodeで動くcucumber-jsを使ってみました。

## 参考にした記事
http://qiita.com/p-baleine@github/items/5e6cd1ed12e31bf7edc2
https://github.com/cucumber/cucumber-js/blob/master/docs/nodejs_example.md

##ライブラリインストール

```bash
$ npm install -S express multiline
$ npm install --save-dev cucumber@latest selenium-webdriver@3.0.1 chromedriver@2.25.1
```

とりあえずこんな感じに。

```package.json 
{
  "name": "cucumnerjs-example",
  "version": "1.0.0",
  "description": "example",
  "main": "index.js",
  "scripts": {
    "test": "./node_modules/.bin/cucumber.js",
    "start": "node index.js"
  },
  "author": "hideokamoto",
  "license": "MIT",
  "dependencies": {
    "express": "^4.14.0",
    "multiline": "^1.0.2"
  },
  "devDependencies": {
    "chromedriver": "^2.25.1",
    "cucumber": "^2.0.0-rc.6",
    "selenium-webdriver": "^3.0.1"
  }
}
```

##テスト用アプリの作成
テストするにはテスト対象を作らないとです。
ということで参考記事よろしくnodeで動くwebページを１つ用意します。

```src/index.js
const express = require('express')
const multiline = require('multiline')
const app = express()

app.get('/', (req, res) => {
    res.send(multiline( () => {/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <p>Cucumber-js example</p>
  </body>
</html>
  */}))
})

module.exports = app

```

```index.js
const app = require('./src/index')

app.listen(3000, () => {
    console.log('port 3000')
})
```

アプリは`npm start`で起動します。

```
$ npm start

> cucumnerjs-example@1.0.0 start /develop/node/cucumberjs
> node index.js

port 3000
```
テスト時に使うので、そのままにしておきましょう。

##cucumberjsでのテスト準備

cucumberjs自体は`feature`ディレクトリさえあれば実行可能です。

```bash
$ mkdir features
$ npm test

> cucumnerjs-example@1.0.0 test /develop/node/cucumberjs
> cucumber.js 

0 scenarios
0 steps
0m00.000s
```

もっとも、テストファイルが存在しないのでただ実行しただけですが。

###Gherkinでテストを書く
とりあえずテストを書いておきましょう。

```features/example.feature
Feature: Example feature
  As a user of Cucumber.js
  I want to have example application
  So that I can concentrate on building awesome applications

  Scenario: I am on the TopPage
    Given I am on the TopPage
    Then I should see "Cucumber-js example"
```

`Feature`の下３行はテスト概要などです。
`Scenario`以下が実際に実行するテスト内容となります。

今回は

- Given I am on the TopPage
- Then I should see "Cucumber-js example"

の２つをテストするということですね。

この状態でテストを実行すると、以下のように「対応するコードがない」というエラーが出てきます。

```
Feature: Example feature

    As a user of Cucumber.js
    I want to have documentation on Cucumber
    So that I can concentrate on building awesome applications

  Scenario: I am on the TopPage
  ? Given I am on the TopPage
  ? Then I should see "Cucumber-js example"

Warnings:

1) Scenario: I am on the TopPage - features/documentation.feature:6
   Step: Given I am on the TopPage - features/documentation.feature:7
   Message:
     Undefined. Implement with the following snippet:

       Given('I am on the TopPage', function (callback) {
         // Write code here that turns the phrase above into concrete actions
         callback(null, 'pending');
       });

2) Scenario: I am on the TopPage - features/documentation.feature:6
   Step: Then I should see "Cucumber-js example" - features/documentation.feature:8
   Message:
     Undefined. Implement with the following snippet:

       Then('I should see {arg1:stringInDoubleQuotes}', function (arg1, callback) {
         // Write code here that turns the phrase above into concrete actions
         callback(null, 'pending');
       });

1 scenario (1 undefined)
2 steps (2 undefined)
0m00.000s
npm ERR! Test failed.  See above for more details.
```
「こういう風にコード書いてくれ」という指示も出してくれるので、このあたりを参考にコードを書いておきましょう。

###ドライバーの設定

先にテストを実行するためのwebドライバを設定します。

```features/support/world.js
require('chromedriver')
var seleniumWebdriver = require('selenium-webdriver');
var {defineSupportCode} = require('cucumber');

function CustomWorld() {
  this.driver = new seleniumWebdriver.Builder()
    .forBrowser('chrome')
    .build();
}

defineSupportCode(function({setWorldConstructor}) {
  setWorldConstructor(CustomWorld)
})
```

###step_definitionsの設定
続いてGherkinに対応するstep_definitionsを記述します。

```features/step_definitions/browser_steps.js
var seleniumWebdriver = require('selenium-webdriver');
var {defineSupportCode} = require('cucumber');

defineSupportCode(function({Given, Then}) {
    Given('I am on the TopPage', function () {
      return this.driver.get('http://localhost:3000');
    });
    Then('I should see {stringInDoubleQuotes}', function (text) {
        const xpath = "//*[contains(text(),'" + text + "')]"
        const condition = seleniumWebdriver.until.elementLocated({xpath: xpath})

        return this.driver.wait(condition, 10000)
    });
});
```

###hookの設定
features/support/hooks.jsへ、scenarioの前後に実行したいコードを書くことができます。

```
var {defineSupportCode} = require('cucumber');

defineSupportCode(function({After}) {
  After(function() {
    return this.driver.quit();
  });
});
```

##テストを実行

お待たせしました。いよいよテスト実行の瞬間です。
事前に`npm start`で`localhost:3000`にアクセスできることを確認しておきましょう。

```bash
$ npm test

> cucumnerjs-example@1.0.0 test /develop/node/cucumberjs
> cucumber.js

Feature: Example feature

    As a user of Cucumber.js
    I want to have documentation on Cucumber
    So that I can concentrate on building awesome applications

  Scenario: I am on the TopPage
  ✔ Given I am on the TopPage
  ✔ Then I should see "Cucumber-js example"

1 scenario (1 passed)
2 steps (2 passed)
0m01.406s
```

成功しました。

###テストを失敗させる

せっかくなので、テスト失敗のケースも見ます。
`src/index.js`内のコンテンツを書き換えて、`npm start`を再実行します。

```src/index.js
const express = require('express')
const multiline = require('multiline')
const app = express()

app.get('/', (req, res) => {
    res.send(multiline( () => {/*
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <p>Fail test pattern</p>
  </body>
</html>
  */}))
})

module.exports = app

```

テストを実行してみましょう。

```bash
$ npm test

> cucumnerjs-example@1.0.0 test /develop/node/cucumberjs
> cucumber.js

Feature: Example feature

    As a user of Cucumber.js
    I want to have documentation on Cucumber
    So that I can concentrate on building awesome applications

  Scenario: I am on the TopPage
  ✔ Given I am on the TopPage
  ✖ Then I should see "Cucumber-js example"

Failures:

1) Scenario: I am on the TopPage - features/example.feature:6
   Step: Then I should see "Cucumber-js example" - features/example.feature:8
   Step Definition: features/step_definitions/browser_steps.js:8
   Message:
     Error: function timed out after 5000 milliseconds
         at Timeout.<anonymous> (/develop/node/cucumberjs/node_modules/cucumber/lib/user_code_runner.js:91:22)
         at ontimeout (timers.js:365:14)
         at tryOnTimeout (timers.js:237:5)
         at Timer.listOnTimeout (timers.js:207:5)

1 scenario (1 failed)
2 steps (1 failed, 1 passed)
0m06.392s
npm ERR! Test failed.  See above for more details.
```

失敗しました。

##使ってみて

step_definitionsを自前で書かないといけないのがちょっとツラいなという感じがあります。
BehatであればMinkExtensionを入れるだけでそこそこ使える感じになっていたという印象がありますので。

・・・なんてことを脳内でボヤいていたら、ありました。
https://www.npmjs.com/package/cucumber-mink

次回はこれを使ってみようと思います。