この記事では、BDDアプローチを使用して自動化をテストするための最も一般的なフレームワークの1つであるCucumberを取り上げます。 また、それがどのように機能し、どのツールが提供するのかも見ていきます。
CucumberはもともとRubyコミュニティによって開発されましたが、時間が経つにつれて他の一般的なプログラミング言語に適応してきました。 この記事では、JavaでのCucumberの動作について説明します。
ガーキン
BDDテストは、いくつかの動作を説明するストーリー(スクリプト)の形式で記述された、人間の言語による単純なテキストです。
CucumberはGherkin表記を使用してテストを記述し、テストの構造とキーワードのセットを定義します。 テストは、* .feature拡張子のファイルに書き込まれ、1つ以上のスクリプトを含む場合があります。
Gherkinを使用したロシア語のテスト例を考えてみましょう。
# language: ru @all : PIN- , PIN- , PIN- : PIN- @correct : PIN- @fail : PIN- , PIN-
例からわかるように、スクリプトは単純な非技術言語で記述されているため、プロジェクトの参加者は誰でも理解して記述できます。
スクリプトの構造に注意してください。
1.システムの初期状態を取得します。
2.何かすること。
3.システムの新しい状態を取得します。
この例では、キーワードは太字です。 以下は、ロシア語のキーワードの完全なリストです。
- Given 、 Suppose 、 Let-は、以前に知られていた予備的な状態を記述するために使用されます。
- When 、 If-キーアクションの説明に使用。
- そして 、 さらに、 -は追加の前提条件またはアクションを記述するためにも使用されます。
- その後 、 それは -実行されたアクションの期待される結果を記述するために使用されます。
- ただし 、 A-は、追加の予期される結果を記述するために使用されます。
- Function 、 Functional 、 Property-テストされた機能の名前と説明に使用されます。 説明は複数行の場合があります。
- シナリオ - シナリオを示すために使用されます。
- 背景 、 コンテキスト -ファイル内の各スクリプトの前に実行されるアクションを記述するために使用されます。
- スクリプト構造 、 例 -スクリプトテンプレートとそれに渡されるパラメーターのテーブルを作成するために使用されます。
パラグラフ1〜5にリストされているキーワードは、スクリプトのステップを説明するために使用され、Cucumberは技術的にそれらを区別しません。 代わりに*記号を使用できますが、これは推奨されません。 これらの言葉には明確な目的があり、そのために特別に選ばれました。
予約文字のリスト:
# -コメントを示します。
@ -スクリプトまたは機能にタグを付けます。
| -データを表形式で区切ります。
"" " -マルチラインデータをフレーム化します。
スクリプトは、#言語:ruで始まります。 この行は、スクリプトがロシア語を使用していることをCucumberに伝えます。 指定しない場合、スクリプトでロシア語のテキストに一致したフレームワークはLexingError例外をスローし、テストは実行されません。 デフォルトの言語は英語です。
シンプルなプロジェクト
キュウリプロジェクトは2つの部分で構成されます-これらは、スクリプト記述(* .feature)を含むテキストファイルと、プログラミング言語でのステップの実装を含むファイル(この場合、* .javaファイル)です。
プロジェクトを作成するには、Apache Mavenプロジェクト自動化システムを使用します。
まず、Mavenに応じてキュウリを追加します。
<dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-java</artifactId> <version>1.2.4</version> </dependency>
テストを実行するには、JUnitを使用します(TestNGから起動できます)。このために、さらに2つの依存関係を追加します。
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-junit</artifactId> <version>1.2.4</version> </dependency>
cucumber-junitライブラリにはcucumber.api.junit.Cucumberクラスが含まれており、JUnit RunWithアノテーションを使用してテストを実行できます。 このアノテーションで指定されたクラスは、テストの実行方法を決定します。
テストのエントリポイントとなるクラスを作成しましょう。
import cucumber.api.CucumberOptions; import cucumber.api.SnippetType; import cucumber.api.junit.Cucumber; import org.junit.runner.RunWith; @RunWith(Cucumber.class) @CucumberOptions( features = "src/test/features", glue = "ru.savkk.test", tags = "@all", dryRun = false, strict = false, snippets = SnippetType.UNDERSCORE, // name = "^|.*" ) public class RunnerTest { }
クラス名には終了テストが含まれている必要があります。そうでない場合、テストは実行されません。
キュウリのオプションを検討してください。
- features-.featureファイルがあるフォルダーへのパス。 フレームワークは、このフォルダーおよびすべての子フォルダー内のファイルを検索します。 いくつかのフォルダーを指定できます。例えば、features = {"src / test / features"、 "src / test / feat"};
- glue-ステップとフックの実装を持つクラスを含むパッケージ。 たとえば、次のように複数のパッケージを指定できます。glue = {"ru.savkk.test"、 "ru.savkk.hooks"};
- tags-タグによってテストを実行するためのフィルター。 タグのリストはコンマでリストできます。 〜記号は、実行中のテストのリストからテストを除外します。たとえば、〜@ fail;
- dryRun-trueの場合、テストを開始した直後に、フレームワークはテストのすべてのステップが開発されているかどうかを確認し、開発されていない場合は警告を表示します。 falseの場合、未開発のステップに到達すると警告が発行されます。 デフォルトはfalseです。
- strict-trueの場合、未開発のステップに遭遇すると、テストはエラーで停止します。 False-未開発のステップはスキップされます。 デフォルトはfalseです。
- スニペット-フレームワークが未実現のステップのテンプレートを提供する形式を示します。 利用可能な値は、SnippetType.CAMELCASE、SnippetType.UNDERSCOREです。
- name-正規表現に一致する名前で実行中のテストをフィルタリングします。
タグと名前オプションを同時に使用して、実行中のテストをフィルタリングすることはできません。
「機能」の作成
src / test / featuresフォルダーで、テストされた機能の説明を含むファイルを作成します。 アカウントからお金を引き出すための2つの簡単なシナリオ-成功と失敗を説明します。
# language: ru @withdrawal : @success : 120000 20000 100000 @fail : - 100 120 " "
打ち上げ
次の設定でRunnerTestを実行してみましょう。
@RunWith(Cucumber.class) @CucumberOptions( features = "src/test/features", glue = "ru.savkk.test", tags = "@withdrawal", snippets = SnippetType.CAMELCASE ) public class RunnerTest { }
テストに合格した結果がコンソールに表示されました:
Undefined scenarios: test.feature:6 # : test.feature:12 # : - 2 Scenarios (2 undefined) 6 Steps (6 undefined) 0m0,000s You can implement missing steps with the snippets below: @("^ (\\d+) $") public void (int arg1) throws Throwable { // Write code here that turns the phrase above into concrete actions throw new PendingException(); } @("^ (\\d+) $") public void (int arg1) throws Throwable { // Write code here that turns the phrase above into concrete actions throw new PendingException(); } @("^ \"([^\"]*)\"$") public void (String arg1) throws Throwable { // Write code here that turns the phrase above into concrete actions throw new PendingException(); }
Cucumberはステップの実装を見つけられず、独自の開発テンプレートを提案しました。
ru.savkk.testパッケージにMyStepdefsクラスを作成し、フレームワークによって提案されたメソッドをそこに転送しましょう。
import cucumber.api.PendingException; import cucumber.api.java.ru.*; public class MyStepdefs { @("^ (\\d+) $") public void (int arg1) throws Throwable { // Write code here that turns the phrase above into concrete actions throw new PendingException(); } @("^ (\\d+) $") public void (int arg1) throws Throwable { // Write code here that turns the phrase above into concrete actions throw new PendingException(); } @("^ \"([^\"]*)\"$") public void (String arg1) throws Throwable { // Write code here that turns the phrase above into concrete actions throw new PendingException(); } }
テストを実行すると、Cucumberはスクリプトを段階的に実行します。 ステップを踏むと、ステップの説明からキーワードを分離し、glueオプションで指定されたパッケージのJavaクラスで、説明と一致する正規表現を持つ注釈を見つけようとします。 一致するものを見つけると、フレームワークは、見つかった注釈でメソッドを呼び出します。 複数の正規表現がステップの説明を満たす場合、フレームワークはエラーをスローします。
前述のように、Cucumberの場合、ステップを説明するキーワードに技術的に違いはありません。これは、注釈についても同様です。たとえば、次のとおりです。
@("^ (\\d+) $")
そして
@("^ (\\d+) $")
フレームワークについては同じです。
正規表現の括弧内に記述されているものは、引数としてメソッドに渡されます。 フレームワークは、スクリプトからメソッドに引数として渡す必要があるものを個別に決定します。 これらの番号は(\\ d +)です。 引用符でエスケープされたテキストは\ "([^ \"] *)\ "です。これらは、渡される引数の中で最も一般的です。
次の表は、正規表現で使用される要素を示しています。
正規表現:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
コレクションを引数に渡す
多くの場合、同じ種類のデータ(コレクション)のセットをスクリプトからメソッドに転送する必要がある場合に状況が発生します。 Cucumberには、このタスクのためのいくつかのソリューションがあります。
- デフォルトのフレームワークは、ArrayListでコンマで区切られたデータをラップします。
, ,
@("^ (.*)$") public void (List<String> arg) { // - }
区切り文字を置き換えるには、Delimiter注釈を使用できます。
@("^ (.+)$") public void (@Delimiter(" ") List<String> arg) { // - }
- Cucumberは、ArrayListに単一列のテーブルとして書き込まれたデータをラップすることもできます。
| | | | | |
@("^ $") public void (List<String> arg) { // - }
- Cucumberは、連想配列の2つの列を持つテーブルに書き込まれたデータをラップできます。最初の列のデータがキーで、2番目の列のデータがキーです。
| | true | | | false | | | true |
public void (Map<String, Boolean> arg) { // - }
- 多数の列を持つテーブル形式のデータ転送は、次の2つの方法で可能です。
- DataTable
| | true | 5 | | | false | 8 | | | true | 2 |
@("^ $") public void (DataTable arg) { // - }
DataTableは、データの表形式表現をエミュレートするクラスです。 データにアクセスするための多数のメソッドがあります。 それらのいくつかを考えてみましょう:
public <K,V> List<Map<K,V>> asMaps(Class<K> keyType,Class<V> valueType)
テーブルを連想配列のリストに変換します。 テーブルの最初の行はキーに名前を付けるために使用され、残りは値として使用されます。
| | | | | | true | 5 | | | false | 8 | | | true | 2 |
@("^ $") public void (DataTable arg) { List<Map<String, String>> table = arg.asMaps(String.class, String.class); System.out.println(table.get(0).get("")); System.out.println(table.get(1).get("")); System.out.println(table.get(2).get("")); }
この例はコンソールに出力します:
public <T> List<List<T>> asLists(Class<T> itemType)
このメソッドは、テーブルをリストのリストに変換します。
| | true | 5 | | | false | 8 | | | true | 2 |
@("^ $") public void (DataTable arg) { List<List<String>> table = arg.asLists(String.class); System.out.print(table.get(0).get(0) + " "); System.out.print(table.get(0).get(1) + " "); System.out.println(table.get(0).get(2) + " "); System.out.print(table.get(1).get(0) + " "); System.out.print(table.get(1).get(1) + " "); System.out.println(table.get(1).get(2) + " "); }
コンソールには以下が表示されます。
true 5
false 8
public List<List<String>> cells(int firstRow)
このメソッドは、前のメソッドと同じことを行いますが、テーブル内のデータのタイプを判別できないことを除き、常に行のリスト-Listを返します。 引数として、メソッドは最初の行の番号を取ります:
| | true | 5 | | | false | 8 | | | true | 2 |
@("^ $") public void (DataTable arg) { List<List<String>> table = arg.cells(1); System.out.print(table.get(0).get(0) + " "); System.out.print(table.get(0).get(1) + " "); System.out.println(table.get(0).get(2) + " "); System.out.print(table.get(1).get(0) + " "); System.out.print(table.get(1).get(1) + " "); System.out.println(table.get(1).get(2) + " "); }
メソッドはコンソールに出力します:
false 8
true 2
- クラス
Cucumberは、スクリプトから渡されたテーブルデータからオブジェクトを作成できます。 これを行うには2つの方法があります。
たとえば、Menuクラスを作成します。
public class Menu { private String title; private boolean isAvailable; private int subMenuCount; public String getTitle() { return title; } public boolean getAvailable() { return isAvailable; } public int getSubMenuCount() { return subMenuCount; } }
最初の方法では、次のようにスクリプトにステップを記述します。
| title | isAvailable | subMenuCount | | | true | 5 | | | false | 8 | | | true | 2 |
実装:
@("^ $") public void (List<Menu> arg) { for (int i = 0; i < arg.size(); i++) { System.out.print(arg.get(i).getTitle() + " "); System.out.print(Boolean.toString(arg.get(i).getAvailable()) + " "); System.out.println(Integer.toString(arg.get(i).getSubMenuCount())); } }
コンソールへの出力:
true 5
false 8
true 2
フレームワークは、3つの列を持つテーブルからオブジェクトのリンクリストを作成します。 テーブルの最初の行には、オブジェクトによって作成されたクラスのフィールド名が含まれている必要があります。 フィールドが指定されていない場合、フィールドは初期化されません。
2番目の方法では、スクリプトステップを次の形式にします。
| title | | | | | isAvailable | true | false | true | | subMenuCount | 5 | 8 | 2 |
また、ステップの説明の引数では、@ Transposeアノテーションを使用します。
@("^ $") public void (@Transpose List<Menu> arg) { // - }
前の例と同様に、キュウリはオブジェクトのリンクリストを作成しますが、この場合、フィールド名はテーブルの最初の列に書き込まれます。
- DataTable
- 複数行の引数
メソッドの引数に複数行のデータを渡すには、3つの二重引用符でエスケープする必要があります。
""" . . """
メソッドのデータは、Stringクラスのオブジェクトとして提供されます。
@("^ $") public void (String expectedText) { // - }
日付
フレームワークは、スクリプトからのデータをメソッド引数で指定されたデータ型に独立して変換します。 これが不可能な場合、ConversionExceptionをスローします。 これは、DateクラスとCalendarクラスに当てはまります。 例を考えてみましょう:
04.05.2017
@("^ (.+)$") public void (Date arg) { // - }
Cucumberは、2017年4月5日を値「Thu May 04 00:00:00 EET 2017」のクラスDateのオブジェクトに変換しました。
別の例を考えてみましょう:
04-05-2017
@("^ (.+)$") public void (Date arg) { // - }
この手順に進むと、Cucumberは例外をスローしました。
cucumber.deps.com.thoughtworks.xstream.converters.ConversionException: Couldn't convert "04-05-2017" to an instance of: [class java.util.Date]
最初の例が機能し、2番目の例が機能しなかったのはなぜですか?
実際、Cucumberには現在のロケールに依存する日付形式のサポートが組み込まれています。 現在のロケールの形式とは異なる形式で日付を記述する必要がある場合は、Format注釈を使用する必要があります。
04-05-2017
@("^ (.+)$") public void (@Format("dd-MM-yyyy") Date arg) { // - }
シナリオ構造
異なるデータセットでテストを数回実行する必要がある場合があります。そのような場合、「スクリプト構造」の設計が役に立ちます。
# language: ru @withdrawal : @success : <> <> <> : | | | | | 10000 | 1 | 9999 | | 9999 | 9999 | 0 |
この構造の本質は、記号<>で示される場所に、例のテーブルのデータが挿入されることです。 このテーブルの行ごとにテストが交互に実行されます。 列名は、データ挿入場所の名前と一致する必要があります。
フックを使用する
Cucumberは、スクリプトの前または後に実行されるメソッドであるフックをサポートしています。 注釈BeforeおよびAfterは、それらを示すために使用されます。 フックを持つクラスは、フレームワークオプションで指定されたパッケージ内にある必要があります。 フックを持つクラスの例:
import cucumber.api.java.After; import cucumber.api.java.Before; public class Hooks { @Before public void prepareData() { // } @After public void clearData() { // } }
注釈Beforeを持つメソッドは、各スクリプトの前、後-後に起動されます。
実行順序
フックには、実行される順序を指定できます。 これを行うには、注釈に順序パラメーターを指定します。 orderのデフォルト値は10000です。
Beforeの場合、この値が低いほど、メソッドは早く実行されます。
@Before(order = 10) public void connectToServer() { // } @Before(order = 20) public void prepareData() { // }
この例では、まずconnectToServer()メソッドが実行され、次にprepareData()メソッドが実行されます。
逆順で履行した後。
タグ付け
valueパラメーターでは、フックが処理されるスクリプトタグを指定できます。 記号〜は「除く」を意味します。 例:
@Before(value = "@correct", order = 30) public void connectToServer() { // - } @Before(value = "~@fail", order = 20) public void prepareData() { // - }
connectToServerメソッドは、タグが正しいすべてのスクリプトに対して実行されます 。タグタグを持つスクリプトを除くすべてのスクリプトに対してprepareDataメソッドは失敗します。
シナリオクラス
フックメソッドの引数でScenarioクラスのオブジェクトを指定すると、このメソッドでは、実行中のスクリプトに関する多くの有用な情報を見つけることができます。たとえば:
@After public void getScenarioInfo(Scenario scenario) { System.out.println(scenario.getId()); System.out.println(scenario.getName()); System.out.println(scenario.getStatus()); System.out.println(scenario.isFailed()); System.out.println(scenario.getSourceTagNames()); }
シナリオの場合:
# language: ru @all : PIN- PIN- PIN- : PIN- @correct : PIN-
コンソールへの出力:
--;-
passed
false
[@correct, @all]
結論として
Cucumberは非常に強力で柔軟なフレームワークであり、他の多くの一般的なツールと組み合わせて使用できます。 たとえば、Webアプリケーションを自動化するためのフレームワークであるSeleniumや、便利なレポートを作成できるライブラリであるYandex.Allureを使用します。
自動化で頑張ってください。