【pytest-bdd】featureファイルでデータテーブルを定義したい
データテーブルを定義できないらしい
Cucumberではgivenで、テーブル構造で引数を定義することができる。
Cucumber Data Tables
Feature: データテーブルサンプル Scenario: データテーブルを使う Given XXデータが設定されている | 名前 | 年齢 | | 山田 | 22 | | 佐藤 | 23 | | 田中 | 33 |
しかし、pytest-bddではこの記法に対応していない。
(pull requestは2016年に作製されているようだが、まだ対応していない)
Add support for data tables (Github pytest-dev/pytest-bdd)
Parserを拡張して対応
テーブル構造でデータを定義できないと不便なため、なんとかしたい。
ドキュメントにカスタムparserの記述があったため、
Step arguments (Github pytest-dev/pytest-bdd)
標準のparserを拡張してテーブル部分のparse処理を追加する方式で対応してみることにした。
拡張parser
標準で用意されているparsers.parseを拡張して実装した。
from pytest_bdd import parsers class TableDataParser(parsers.parse): """テーブルデータ用parser""" def __init__(self, name: str, data_table_key: str, columns_name_alias={}, **kwargs): """Parser初期化処理 Args: name ([type]): parse定義 data_table_key (str): データテーブル部を割り当てる変数名 columns_def (dict): 列名の別名定義 """ # テーブル部を定義に追加して初期化する super().__init__(f"{name}\n{{{data_table_key}}}", **kwargs) # データテーブルの変数名 self.data_table_key = data_table_key # 列別名定義 self.columns_name_alias = columns_name_alias def parse_arguments(self, name): """引数をパースする :return: `dict` of step arguments """ # 標準のparse処理 arguments = super().parse_arguments(name) # テーブル部をparseする data_table_rows = arguments[self.data_table_key].split("\n") data_table = [ self._parse_table_row(row_str) for row_str in data_table_rows ] # 1行目は列定義 col_def = data_table.pop(0) # 列名に別名定義があれば差し替える col_def = [ col if col not in self.columns_name_alias else self.columns_name_alias[col] for col in col_def ] # 辞書の配列に組み替える data_table_dicts = [ { col: row[i] for i, col in enumerate(col_def) } for row in data_table ] # データテーブルをparse後の値に差し替える arguments[self.data_table_key] = data_table_dicts return arguments def _parse_table_row(self, row_str: str) -> list[str]: """データテーブルの行をparseする Args: row_str (str): データテーブル 1行分の文字列 Returns: list[str]: 行を列単位に分割した配列 """ # 列単位に分割 # 先頭と末尾は不要 cols = row_str.split("|")[1:-1] # 列ごとに空白を取り除く cols = [col.strip() for col in cols] return cols
使用部分
Scenario: データテーブルを使う Given XXデータが設定されている | 名前 | 年齢 | | 山田 | 22 | | 佐藤 | 23 | | 田中 | 33 |
@given(TableDataParser("XXデータが設定されている", "xx_data_table")) def settted_XX(xx_data_table): print(xx_data_table) # [ # {'名前': '山田', '年齢': '22'}, # {'名前': '佐藤', '年齢': '23'}, # {'名前': '田中', '年齢': '33'} # ]
標準のparserを拡張しているので標準の変数記法も利用できる。
@when(TableDataParser("{target}を設定する", "target_data_table") def set_data(target, target_data_table): print(target) print(target_data_table)
扱いやすいように、列別名を定義できるようにしてみた。
@when(TableDataParser("XXデータが設定されている", "target_data_table", columns_name_alias={ "名前": "name", "年齢": "age", })) def settted_XX(target_data_table): print(target_data_table) # [ # {'name': '山田', 'age': '22'}, # {'name': '佐藤', 'age': '23'}, # {'name': '田中', 'age': '33'} # ]
いまいちなところ
今の作りだと各列のデータ型を指定できないため、string以外で扱いたいときに面倒。
parserの引数で型を定義できるようにしたほうがいいかも。