CypressでBDD
はじめに
参画しているサービスが Cypress でE2Eテストを書いていたため
よりユーザーストーリーに近い形で実装できるようBDDに拡張した。
振舞駆動開発(Behavior-Driven Development, BDD)とは
BDDは、2003年にDan Northによって提唱された開発手法です。
TDDから派生したもので、BDDもまた、先にテストプログラムで動く仕様を表現し、後で実装を作ります。
クラスやサブモジュールの使い方のレベルではなく、ユーザーが使う機能のレベルで行うのが特徴です。
TDDとBDDそれぞれの目的
これらについては、田中ひさてるさんが「ちょうぜつソフトウェア設計入門」でわかりやすくまとめてくれています。
BDDとTDD
BDDの目的は「仕様のとおり動く実装を得る」ことです。一方、TDDの可能性は、BDDの目指すものとは異なります。TDDは実装手法よりも、設計手法として使ったときに、その真価を発揮します。設計済みのフレームワークで動くプログラムを素早く作る目的なら、単体テストによる細粒度テストの積み上げはオーバースペックかもしれません。動作を作るだけの場合はBDDのほうが手短で適していると言えます。逆に言えば、TDDを使うのなら、その用法をBDDと同じで終わらせず、設計のために活用していくのが本当です。
田中 ひさてる. ちょうぜつソフトウェア設計入門――PHPで理解するオブジェクト指向の活用 (p.243). 株式会社技術評論社
今回導入した背景としては、まさに仕様の通り動く実装を得ることを目的としています。
cypress-cucumber-preprocessorを導入する
cypress-cucumber-preprocessor は Cypress と Cucmer を統合してくれるライブラリです。
※cypressとcucmerの統合では、おそらく最も広く使われている
Cucumberについて
そもそも Cucmber とは?ですが、BDDを実践するために生まれたツールです。
自然言語(Gherkin)で記述された仕様を実行可能なテストに変換してくれるため
テストを仕様書として落とし込むことが可能にしてくれます。
環境構築
cypress-cucumber-preprocessor を利用できる状態にするには、cypress.config.ts
に追加で設定を加える必要があります。
import createBundler from "@bahmutov/cypress-esbuild-preprocessor";
import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor";
import { createEsbuildPlugin } from "@badeball/cypress-cucumber-preprocessor/esbuild";
export default defineConfig({
e2e: {
specPattern: "**/*.feature",
async setupNodeEvents(
on: Cypress.PluginEvents,
config: Cypress.PluginConfigOptions
): Promise<Cypress.PluginConfigOptions> {
// プロプロセッサが各実行時にJSONレポートを生成できるようにするためなどに必要
await addCucumberPreprocessorPlugin(on, config);
on(
"file:preprocessor",
createBundler({
plugins: [createEsbuildPlugin(config)],
})
);
// プラグインによって変更されている可能性があるため、configオブジェクトを返すようにする。
return config;
},
},
});
Preprocessors API
プリプロセッサーは、Cypress が提供しているAPIで
ブラウザ用のサポートファイルやスペックファイルに関する準備を担当しています。
今回feature
ファイルに仕様を書き込んでいくため、それらをコンパイルする必要があり
file:preprocessor
上でEsbuild周りの拡張が必要と思われます。
実際にテストを書いてみる
基本的に書き方は Cucumber と同様 Gherkin で書いていきます。
cypress/e2e/duckduckgo.feature
Feature: duckduckgo.com Scenario: visiting the frontpage When I visit duckduckgo.com Then I should see a search ba
githubより参照
例で登場したキーワードについて
- Feature:ソフトウェア機能で、関連するシナリオを グループ化する
- Scenario:実際のユーザーストーリーを書く
- When:イベントやアクションを記述する
- Then:アサーションを記述する
WhenとThenに対応するStepを実装する
次に、仕様を実装していきます。
cypress/e2e/duckduckgo.ts
import { When, Then } from "@badeball/cypress-cucumber-preprocessor"; When("I visit duckduckgo.com", () => { cy.visit("https://www.duckduckgo.com"); }); Then("I should see a search bar", () => { cy.get("input").should( "have.attr", "placeholder", "Search the web without being tracked" ); });
githubより参照
When
とThen
を cypress-cucumber-preprocessor よりimport
し、その中で Cypress で必要な実装を加えていきます。
※このsampleではGiven
は出てこないですが、データのセットアップが必要な場合はGiven
で実装を行います。
他にも Cucumber をベースとした機能が数多く提供されています。
所感
Cypress のみでは、どうしても仕様と実装を切り離すことが難しく、見辛さを感じていました。
cypress-cucumber-preprocessor を導入し、ユーザーストーリーと実装を切り離すことができたことで、コミュニュケーションやTDDのステップも踏みやすくなりました。
同時に、cypress への理解も深まり、統合などを行うタイミングは新しく概念を深く理解する機会で楽しみながらできた気がします。
cypress の書き方で悩まれている、仕様と実装切り離したい方など、一度BDDに挑戦してみようと思うきっかけになればと思います。
Discussion