BDD in Action, 2nd Edition 第7章 を読んだ感想メモ
この本はまだMEAPで全部が公開されていないわけだけど、 6章の公開が去年の6月末で、7章は去年の大晦日でした。
まぁ自分の感想は、そこから更に5ヶ月近くかかっていますが。
6章までを軽くおさらいすると、このような流れでシナリオをfeatureファイルに書き出しました。
ステークホルダーやQAエンジニアと協力して、
要件を深掘りして詳細化し、
実例を出して話し合いながら、
(テスト)実行可能なシナリオを作る。
シナリオはfeatureファイルに、Gherkinフォーマット(Given, When, Then) で書く。
そしてこの7章から少しずつ テストコード (受け入れテストコードと言った方がいいかも。)を書き始めるので、これまでよりもCucumberの使うためのテクニックの説明に入ります。
ですが7章と8章は、テストコードを書き方というよりは、準備やテストコード設計のための原則を説明しています。
中でも7章は、6章までで見てきたシナリオ と ステップ定義を紐づけるときのテクニック、ステップ定義の文法、Gherkinの主なキーワードの説明のような、Cucumberの基本的な使い方の説明が主な内容となっている。
尚、本文ではJava と JavaScript の2種類のパターンで説明していますが、本ブログは基本的にJavaで書くことを前提とした感想になっている。
ステップ定義 (グルーコード)
ステップ定義 (StepDefinition)は、featureファイルに書いたシナリオから、ステップ別にメソッドを定義する。
ステップとは Gherkinの Given, When, Then を指し、ステップをAnd や But で繋げている場合は、それらも1つのステップ定義としてメソッドにする。
ステップ定義メソッドには、シナリオのどのステップに対応しているかを明示するために、 アノテーション(@Given, @When, @Then) を付け、値にシナリオのステップ文を入力する。
ステップ定義は、Givenメソッドがテストの前提条件、Whenメソッドがテストの入力条件、そして Thenメソッドが結果の出力と検証(assertion) を書く。
Cucumberが シナリオのテストを実行する時は、書かれているステップの順番でメソッドを実行するみたい。
ステップ定義は、Cucumberに対して、(featureファイルの)シナリオと対応するテストコードを結びつける役割を持つので、グルーコード (glue code: glue は接着剤)と呼ばれる。
Step Definitions - Cucumber Documentation
Cucumber Test Runner クラス
Cucumber は専用のランナークラスを作成して、テストを実行する。 JavaでJUnitを利用する場合は、JUnitの @RunWithアノテーションを利用して、ランナークラスを定義する。
またこのランナークラスの内部では、featureファイルのルートディレクトリ、ステップ定義のルートパッケージなどの設定も行える。
Cucumber Reference - Cucumber Documentation
ステップ定義パラメータ
シナリオのステップ文に書かれた値は、ステップ定義メソッドのパラメータに渡すことができる。
パラメータとして渡す値は、アノテーションの値に記載したステップ文で {} で囲むことでメソッドが参照することができる。{} の中は定数だけでなく、Cucumber Expression(Cucumber独自の正規表現)と 一般的な正規表現が使えるのでテストデータの値を変えたい場合はステップの文が変わらない限り、featureファイル側のシナリオの値を変えるだけで対応できる。
そのため、featureファイルのシナリオ側は、値をリストやテーブル形式で定義することができ、ステップ定義のパラメータはList型オブジェクトで受け取ることも可能。
ステップ定義のパラメータは、@ParameterTypeアノテーションを付けたカスタムクラスを利用することもできる。
シナリオの値を内包した、アクター(ペルソナ)や ビジネスの関心事(ドメイン)として、テスト用のオブジェクト用意することもできます。
Cucumberのテストに、この ペルソナやドメイン などの概念を取り入れて、テストコードをレイヤー化する話は次の8章で詳しく説明することになる。
Cucumber Expressions - Cucumber Documentation
Cucumber Reference - Cucumber Documentation
Background と Hook
Background と Hook(@Befor, @After, など) は、複数のシナリオ、あるいは フィーチャーを横断して、前述のオブジェクトを共有したり、テスト実行前の前処理あるいは後処理の工程を定義することができる。
Backgroundは、主にFeatureファイル内の複数のシナリオで共用するデータテーブルのことを指す。 このデータテーブルをステップ定義で、@DataTableTypeアノテーションを付与したメソッド内でデータオブジェクト生成し、ステップ定義のメソッドで参照できるようになる。
Backgroudは featureファイルで宣言して記載することから、このデータテーブル自体もビジネス側の関心事の1つであることを意識する。
Gherkin Reference - Cucumber Documentation
Hookは、開発/テストチームがテスト実行のために必要な前処理及び後処理を定義するときに使う。
使用事例としては、「テスト開始前にブラウザを開き、終了後にブラウザを閉じる。」といった操作、「テストの整合性のためにテスト実行時にデータ初期化する。あるいはテスト終了後に破棄する」など。
要するに「適切にテストするためには必要な工程だが、ビジネス側の関心事ではない。」ような、シナリオには現れない工程に対して宣言するのが主な使い方になる。
尚、どのシナリオでどのHookを使うかは、シナリオに任意のタグを設定し、ステップ定義メソッドのHook側のアノテーション内にタグを指定すれば、タグに紐づくシナリオを実行する時だけHook処理を行う。といった条件が設定できる。
Cucumber Reference - Cucumber Documentation
テスト環境の準備
章の最後は、テスト環境の構築についての話。
BDDが対象としているテストは、主に統合テスト、受け入れテスト、およびエンドツーエンドテストとなる。このためテストを行う際はテスト対象アプリと連携する他のシステムの構築が必要になることが多い。
データベースであれば、H2などのインメモリータイプを利用すれば実現しやすいが、本番で使うデータベースと異なったり、データベース以外にも連携するコンポーネントがあると、本番と同等環境でテストを行うことが大事になってくる。
そういった点で、テスト環境としてできる限り仮想化環境、特に最近ならDockerでコンテナ化したコンポーネントでテストを実行しようというお話でした。
この章の最後のセクションでは、テスト用のDockerコンテナを管理しやすくする、TestConteinersというオープンソースライブラリが紹介されています。
TestContainersは私自身も初めて聞いた気がしますが、PostgreSQLなどのRDBMS以外にも、NoSQL、Kafka、Elasticsearch といったコンポーネントを Dockerコンテナで起動し、更にそれらをJavaオブジェクトとしてテストコードで扱うことができるようです。
TestContainersを使えば、テストコードで連携するコンポーネントやテストデータを管理できるので、便利な気がします。
所感
ステップ定義メソッドはできるだけシンプルにしたいが、冗長化を避けて汎用的にしようとすると Cucumber Expressionなり、正規表現を多用してしまいそうな気がした。
逆にステップ定義のglue code が複雑になりそうなら、シナリオを見直すことも考えておいた方が良さそう。
TestContainersは、連携サービスをJavaオブジェクトで一括管理できそうな気がしていて、個別にdockerfileを用意しなくても良さそうなのが便利な気がするので、今度使ってみたい。
次の8章も公開されているので、続けて読んでいきます。