CucumberJSを使用したJavaScriptのBdd

0

Todd Anderson

私は以前、JavaScriptでテスト駆動開発(TDD)について書いていましたが、特にテスト駆動型食料品リストアプリケーションの構築に関するシリーズでbehavior driven development(BDD) その投稿シリーズでは、機能やシナリオのユーザーストーリーを実際の開発タスクとして考え、テスト駆動型コードを提供する方法を見つけている限り、それはす それには何も問題はありませんし、私はこれとその後の投稿を同じ方法で見る可能性が最も高いでしょう。 そうは言っても、TDDが簡潔で、テストされ、よく考え抜かれたコードを提供する最良の方法であることを私はまだ確信しています。

その時以来、私はJavaScriptベースのプロジェクトのための私のTDDワークフローに別のツールを組み込んで、私の開発にもっと密接に機能仕様の統合を提供し、真の行動駆動開発の私の現在の理想を包含しています:CucumberJS。 基本的に、機能をサポートするコードを記述するまで失敗する自動テストを実行している外部から開発しながら、本当にTDDを遵守することができます。

仮定と注意事項

この記事の例では、NodeJS、npm、ノードモジュールの開発、および一般的な単体テストの実践に精通していることを前提としています。

このトピックに関連するサポートファイルは、次の場所で利用できます:
https://github.com/bustardcelly/cucumberjs-examples

CucumberJS

CucumberJSは、人気のあるBDDツールCucumberのJavaScriptポートです(それ自体はRSpecの書き換えでした)。 これにより、Gherkinと呼ばれるドメイン固有言語(DSL)で機能仕様を定義し、シナリオの合格および/または失敗とそれらが構成されている手順を報告するコ

Cucumber自体はデフォルトのアサーションライブラリを提供していないことに注意することが重要です。 これは、定義された機能を使用し、JavaScriptで記述されたステップを実行することによってシナリオを検証するコマンドラインツールを提供するテス これらのステップを合格または不合格にするために使用される目的のアサーションライブラリを含めることは、開発者の選択です。 この記事では、複数のシナリオを持つ単一の機能を使用して、例によってプロセスを明確にすることを意図しています。

インストール

npmを使用してプロジェクトにCucumberJSをインストールできます:

$ npm install cucumber --save-dev

Gherkin

以前のTDDシリーズに従っていた場合、そのシリーズで定義されている仕様はGherkinと同様です。 実際には、私はあなたの最初のcuke(別名、機能仕様を渡す)を介して作業を実証するために、そのシリーズから機能仕様を再ハッシュされます。

Cucumberを使用してTdd/BDDの下で食料品リストアプリケーションをリメイクする場合は、まずGherkin構文を使用した機能から始めます:

/features/add-item。feature

Feature: Shopper can add an item to their Grocery List As a grocery shopper I want to add an item to my grocery list So that I can remember to buy that item at the grocery store Scenario: Item added to grocery list Given I have an empty grocery list When I add an item to the list Then The grocery list contains a single item Scenario: Item accessible from grocery list Given I have an empty grocery list When I add an item to the list Then I can access that item from the grocery list

この機能はビジネス価値を定義し、シナリオはその価値を提供するステップを定義します。 ほとんどの場合、ソフトウェア開発の世界では、開発タスクが取られ、QAテストが定義されるのはこれらのシナリオからです。

私は二つのシナリオで停止しましたが、私たちは非常に簡単にこの機能に多くのシナリオを追加することができます。 後知恵では、そのような詳細のために別々の機能仕様を作成する方が理にかなっています。 私たちは、しかし、そのようなトピックに全体のポストを費やすことができるので、すでに定義されている機能に戻

各シナリオ内には、Given、When、Thenの一連のステップのリストがあります。 CucumberJSがこの機能仕様を消費した後に実行するのは、これらの手順です。 それらのそれぞれの後に、必要に応じてAndとButを持つことができますが、時には必要かつ避けられませんが、私はそのような追加のステップ句から離

それを実行する

それを/featuresディレクトリのファイルに保存したら、Cucumberで実行できます:

$ node_modules/.bin/cucumber-js

デフォルトでは、CucumberJSは相対/featuresディレクトリにあるすべての機能仕様を消費します。

現在のコンソール出力は、基本的にすべてのステップが配置または定義されていないことを意味し、次のようになります:

UUUUUU2 scenarios (2 undefined)6 steps (6 undefined)You can implement step definitions for undefined steps with these snippets:this.Given(/^I have an empty grocery list$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});this.When(/^I add an item to the list$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});this.Then(/^The grocery list contains a single item$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});this.Then(/^I can access that item from the grocery list$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});

したがって、2つのシナリオを構成する6つの未定義のステップがあり、CucumberJS ciツールはそれらを定義する方法の例も提供しています!

理解すべきそのスニペットの重要な部分は、実装するステップが4つしかないということです。 私たちの機能では、2つのSceneriosそれぞれに3つのステップがあります。 合計6つのステップがありますが、定義する必要があるのは4つだけです。 これらは一度だけ定義する必要があり、各シナリオごとに個別に実行されます。 基本的に、同じコンテキストを使用して同様のステップを定義すると、各シナリオ内の単一のステップの”セットアップ”が再利用されます。

私は引用符で”setup”を使用しています。

CucumberJSのBefore/After support tasksとして知られている他の単体テストプラクティスのsetup/teardownメソッドと混同したくないし、テストが実行される環境を設定するためのコ

ステップ定義

前のセクションでは、項目の追加機能に対してCucumberJSを実行すると、この機能をサポートする未定義のシナリオがあることが警告されました(赤で印刷されていませんが、失敗します)。 デフォルトでは、CucumberJSは、コマンドが実行された場所に関連する/featuresディレクトリからすべての機能を読み取りますが、これらのメソッドが定義されてい

前述のように、CucumberJSはアサーションライブラリを提供しません。 この時点での唯一の前提は、CucumberjsツールがNodeJSの下で実行されるため、サポートされているステップは、実行されるエクスポートされた関数を持つノードモジュールとし この手順の実装を開始するときには、ロジックの検証に使用するアサーションライブラリを決定する必要があります。 私たちは、現時点では棚にその決定を入れて、ベアボーンのセットアップが失敗するように取得します。

まず、CucumberJS ciツールによって提供されるステップ定義をノードモジュール

/features/stepdefinitions/add-itemにドロップしましょう。ステップ。js_

'use strict';module.exports = function() { this.Given(/^I have an empty grocery list$/, function(callback) { callback.pending(); }); this.When(/^I add an item to the list$/, function(callback) { callback.pending(); }); this.Then(/^The grocery list contains a single item$/, function(callback) { callback.pending(); }); this.Then(/^I can access that item from the grocery list$/, function(callback) { callback.pending(); });};

デフォルトでは、CucumberJSは、コマンドを発行した場所に関連する/featuresディレクトリの下のstep_definitionsという名前のフォルダ内にロードされるステップを探します。 必要に応じて-rオプションを使用して、CucumberJSに別の場所からステップをロードさせることができます。 デフォルトを実行することは、次のステップ定義ディレクトリオプションを設定することと同じです:

./node_modules/.bin/cucumber-js -r features/step_definitions

コンソールの出力は次のようになります:

P--P--2 scenarios (2 pending)6 steps (2 pending, 4 skipped)

コールバックにpending状態を通知するのを見ても驚くことではありません。 CucumberJSは最初のステップ(与えられた)に入り、保留中の通知ですぐに返されます。 そのため、後続のステップを入力することは気にせず、スキップされたとしてマークします。

注:クライアント側のモジュールとAMD vs CommonJSについての議論に入るには多すぎます。 この例の目的のために、私は私の現在の興味はクライアント側の開発のためにBrowserifyを利用することにあるので、CommonJSを使用します。 長い間、私はRequireJSとAMDの支持者でした。 ここでも、それは全体の他の議論です。

与えられた

緑に近づくために、最初に指定されたステップに取り組みます:

/features/stepdefinitions/add-item。ステップ。js_

'use strict';var GroceryList = require(process.cwd() + '/script/model/grocery-list');module.exports = function() { var myList; this.Given(/^I have an empty grocery list$/, function(callback) { myList = GroceryList.create(); callback(); }); ...};

それをもう一度実行すると、すぐに例外が発生します:

$ ./node_modules/.bin/cucumber-jsmodule.js:340 throw err; ^Error: Cannot find module './script/model/grocery-list' at Function.Module._resolveFilename (module.js:338:15) at Function.Module._load (module.js:280:25) at Module.require (module.js:364:17) at require (module.js:380:17) at Object.<anonymous> (/Users/toddanderson/Documents/workspace/custardbelly/cucumberjs-example/features/step_definitions/add-item.steps.js:3:19)

これは、他のコードを定義していないが、このステップ定義モジュールを定義し、存在しないモジュールを要求しようとしているので理解できます。 TDDに固執することで、これは良いことです–なぜそれが失敗しているのかを知っていて、それを期待しています–例外をスローしなかった場合、私は髪を引き

これを渡すために、指定されたディレクトリにノードモジュールを作成し、createメソッド

/script/model/grocery-listでオブジェクトをエクスポートします。js

'use strict';module.exports = { create: function() { return Object.create(null); }};

与えられたステップを通過させるための最小限の要件を提供しました。 後者の手順に近づくにつれて、詳細について心配します。

それをもう一度実行すると、CucumberJSは各シナリオのWhenステップに入り、保留中の戻りのために中止します。

$ ./node_modules/.bin/cucumber-js.P-.P-2 scenarios (2 pending)6 steps (2 pending, 2 skipped, 2 passed)

とき

前のセクションでは、指定されたステップを各シナリオに渡すために、grocery-listモジュールのファクトリメソッドcreateから生成された食料品リストモデ 私はオブジェクトの作成、new演算子、クラス、プロトタイプについての議論に入りたくありません–少なくともこの記事ではありません–そして、あなたecmascript5のために定義された作成。シナリオのWhenステップの見直しで

:

When I add an item to the list

…与えられたもので作成された食料品リストインスタンスにアイテムを追加する方法を提供する必要があります–そしてステップを通過させるた

まず、ステップ定義で食料品リストのメイクアップとaddシグネチャの期待値を定義します:

/features/stepdefinitions/add-item。ステップ。js_

...module.exports = function() { var myList, listItem = 'apple'; this.Given(/^I have an empty grocery list$/, function(callback) { myList = GroceryList.create(); callback(); }); this.When(/^I add an item to the list$/, function(callback) { myList.add(listItem); callback(); }); ...};

それをもう一度実行すると:

$ ./node_modules/.bin/cucumber-js.F-.F-(::) failed steps (::)TypeError: Object object has no method 'add'

おおおおおおおおおおおおおおおおおおおおおお!!!!!! 今、私たちは話しています。 Big

それを渡すことに戻すために、できるだけ少ないコードでgrocery-listを変更します:

/script/model/grocery-list.js

'use strict';var groceryList = { add: function(item) { // }};module.exports = { create: function() { return Object.create(groceryList); }};

が再び実行され、CucumberJSはpending状態を報告しているthenステップに進みました。

$ ./node_modules/.bin/cucumber-js..P..P2 scenarios (2 pending)6 steps (2 pending, 4 passed)

次に

ステップの実装を進め、シナリオが意図した値を提供することを証明する操作とプロパティをアサートするステップに達しました。 前述のように、CucumberJSはアサーションライブラリを提供しません。 アサーションライブラリの私の好みは、Chai、Sinon、Sinon-Chaiの組み合わせですが、この記事の例では、NodeJSに付属のassertモジュールを使用します。 私はあなたが他のアサーションライブラリをチェックアウトし、お気に入りを持っている場合はメモを残すことをお勧めします。

注:このセクションは、コードの変更からすぐに切り替えて、specランナーを頻繁に実行するため、少し重い例になります。

最初のシナリオ

最初のシナリオの次のステップを確認する:

Then The grocery list contains a single item

…新しいアイテムが追加されるたびに、食料品リストインスタンスが1倍に増加することを証明する必要があります。

ステップを更新して、仕様の検証方法を定義します。

/feature/stepdefinitions/add-item。ステップ。js_

...var assert = require('assert');...module.exports = function() {... this.Then(/^The grocery list contains a single item$/, function(callback) { assert.equal(myList.getAll().length, 1, 'Grocery List should grow by one item.'); callback(); });...};...

assertモジュールをプルし、項目を追加する前のステップを実行した後、食料品リストの長さが1の値だけ増加したことを検証しようとしました。

それを実行すると、例外が発生します:

$ ./node_modules/.bin/cucumber-js..F..P(::) failed steps (::)TypeError: Object #<Object> has no method 'getAll'

このメソッドを食料品リストモデルに追加しましょう:

/script/model/grocery-list.js

'use strict';var groceryList = { add: function(item) { // }, getAll: function() { // }};module.exports = { create: function() { return Object.create(groceryList); }};

そして私たちの仕様の実行に戻ります:

$ ./node_modules/.bin/cucumber-js..F..P(::) failed steps (::)TypeError: Cannot read property 'length' of undefined

コードがgetAll()から何も返さないので、アサーションテストのlengthプロパティにアクセスすることはできません。配列を返すようにコードを変更した場合:

/feature/stepdefinitions/add-item。ステップ。js_

...getAll: function() { return ;}...

そして、再び仕様を実行すると、私たちが提供したアサーションエラーメッセージが表示されます:

$ ./node_modules/.bin/cucumber-js..F..P(::) failed steps (::)AssertionError: Grocery List should grow by one item.

これで、ステップが合格しない原因となるアサーションから適切な失敗が報告されました。 万歳!

一息

このステップを通過させるためにコードを追加する前に、ここで一秒間一時停止しましょう。 手元の問題は、実際に返される配列に項目を追加することではなく、addメソッドを介して項目が追加され、getAllの結果がその項目で拡張されたリストであ

このテストパスを作成する際に必要な実装の詳細は、チームがアーキテクチャの経験を使用する場所ですが、最も重要なコードのみを追加するように注意する必要があります。 食料品リスト収集モデルの内部について考える際に船外に出ることを避けるべきです。 それは簡単にウサギの穴を落ちることができる滑りやすいタイトロープです-ちょうどその不十分な言葉の比喩のように♥

仕事に戻って!

この例の目的のために、Object.createpropertiesObject引数を使用して、食料品リスト項目の可変配列として機能するlistゲッターを定義します。

/script/model/grocery-list。js

'use strict';var groceryList = { add: function(item) { this.list.push(item); }, getAll: function() { return this.list; }};module.exports = { create: function() { return Object.create(groceryList, { 'list': { value: , writable: false, enumerable: true } }); }};

それを実行すると、最初のシナリオが通過していることがわかります!

$ ./node_modules/.bin/cucumber-js.....P2 scenarios (1 pending, 1 passed)6 steps (1 pending, 5 passed)

Second Scenario

2番目のScenarioの最後のステップを確認すると、保留中の実装は追加されたアイテムにアクセスしています:

Then I can access that item from the grocery list

このステップに合格するには、アイテムでadd()を呼び出して、食料品リストに追加されたアイテムにアクセスできることを確認する必要があります。

食料品リストの長さにアクセスする実装と同様に、このテストをコード内で渡す方法がいくつかあります。 また、私はこれがソフトウェア開発の経験と味がアーキテクチャに関して遊びに来るところだと感じていますが、私は可能な限り最小限のコードを作

そうは言っても、アサーションパスの作成にどのように対処するかについては、言語と環境の仕様も考慮する必要があります。 それはわずかではありません、それは要件に対する期待を設定する上での単なる先見の明です。具体的には、食料品リストオブジェクトの’getAll()’から返されたコレクションのArray.indexOf()メソッドを使用してステップを検証できるとしますか? Polyfillがなければ、IE9以前のアサーションを渡すことに制限しています。 このような考慮事項は、テストに合格するためにコードベースに何を導入するかを決定する際の氷山の一角に過ぎず、製品を生産するために必要と考え

私は続けて行くことができますが、ブラウザ(IE6以上、身震い)に関しては、すべての基盤をカバーしたいと仮定しましょう。 私の意見では、この2番目のシナリオを緑色にするために、次のシグネチャを持つgetItemIndex()メソッドを追加します:

+ getItemIndex(itemValue):int

まず、失敗するようにステップを変更します:

/feature/stepdefinitions/add-item.ステップ。js_

this.Then(/^I can access that item from the grocery list$/, function(callback) { assert.notEqual(myList.getItemIndex(listItem), -1, 'Added item should be found at non-negative index.'); callback();});

このテストに合格するための受け入れは、追加されたアイテムがコレクション内に存在するインデックスが負ではないことです。 このシナリオでは、新しい項目がリスト内で追加される場所(例えば、追加または追加)に関する仕様を検証しようとしているのではなく、単にそれがア例外を生成する実行中の

:

$ ./node_modules/.bin/cucumber-js.....F(::) failed steps (::)TypeError: Object #<Object> has no method 'getItemIndex'

食料品リストオブジェクトを変更して、getItemIndexメソッド

/script/model/grocery-listをサポートしましょう。js

'use strict';var groceryList = { add: function(item) { this.list.push(item); }, getAll: function() { return this.list; }, getItemIndex: function(value) { var index = this.list.length; while(--index > -1) { if(this.list === value) { return index; } } return -1; }};module.exports = { create: function() { return Object.create(groceryList, { 'list': { value: , writable: false, enumerable: true } }); }};

getItemIndexの実装では、リストがトラバースされ、itemが見つかった場合はインデックスが返されます。 それ以外の場合は、-1の値が返されます。 基本的には、ECMAScript5のArray.indexOfメソッドがどのように機能するか。

注:Objectを使用するのは愚かに見えるかもしれません。ECMAScript5から作成しますが、配列は作成しません。インデクショフ… その理由は、主に、私は通常、常にオブジェクトのpolyfillを含めるということです。配列のためではなく、作成します。インデクショフ… 私は習慣を仮定します。

今、私たちはCucumberJSの下で再び仕様を実行した場合:

$ ./node_modules/.bin/cucumber-js......2 scenarios (2 passed)6 steps (6 passed)

私たちのキュークスは緑です! (これはあなたがあなたの眉と遅い拍手を拭くポイントです)。

結論

この記事では、JavaScriptでのテスト駆動開発を遵守するために、BDDツールCucumberJSを使用する方法を紹介しました。 私は、2つのシナリオを持つ単一の機能の例を使用し、失敗したステップを緑色のキューに変えました。 テストに合格するためのコードを生成するためだけにテストを最初に失敗させるプロセスに慣れていない場合は、あなたが従うことを願っていま; 私は言葉かもしれないし、プロセスは多くの時間がかかるように見えるかもしれませんが、そのような慣行の下での開発は、あなたが溝に入るとスムーズ さらに、開発者の健康とビジネスの両方で、リファクタリングとバグ修正に関しては、コードをテストハーネスの下に置くことには大きな報酬があると思

この記事は、もともとhttp://custardbelly.com/blog/blog-posts/2014/01/08/bdd-in-js-cucumberjs/index.html

に投稿されました画像提供:食べログのhttp://commons.wikimedia.org/wiki/File:Og%C3%B3rki…jpg

e6b2cff6a55928c8f62b9d602add3fed?s=100d=mmr=g

Todd Andersonは、アーキテクチャと開発ワークフローに情熱を持つアプリケーション開発者です。

アジャイルプラクティスとテスト駆動開発の強力な支持者として、Adobe、THQ、Condé Nast Publications、Motorolaを含む企業やエンターテイメント業界の多数の企業とweb、モバイル、デスクトップソリューションの提供を支援してきました。

彼は自分のブログにソフトウェアと技術について頻繁に書いており、オープンソースソフトウェアをサポートしており、GitHubを通じて開発コミュニティに恩返しするために最善を尽くしており、O’ReillyとWiley Wrox:Amazon profileのいくつかのタイトルの共著者になることが尊敬される喜びを持っています。

Twitterでトッドに従ってください

トッドのサイトを訪問

コメントを残す

メールアドレスが公開されることはありません。