pytest-bdd
pytest-bdd copied to clipboard
Add support for data tables
Fixes #150 and helps mitigating the effects of #157
The goal is to allow data tables attached to steps as described in https://cucumber.io/docs/reference#data-tables. This has nothing to do with example tables, it does not trigger multiple run of the scenario. An artificial example would be:
Feature: Datatables
Scenario Outline: I use datatables
Given the following users exist:
| name | email | twitter |
| Aslak | [email protected] | @aslak_hellesoy |
| Julien | [email protected] | @jbpros |
| Matt | [email protected] | @mattwynne |
Then I should see the following names:
| name |
| Aslak |
| Julien |
| Matt |
For each step which has a data table, the implementation can use a datatable argument which is a list of two lists: the list of headers and the list of rows.
@given('the following users exist:')
def the_following_users_exist(datatable):
"""the following users exist:."""
return datatable[1]
@then('I should see the following names:')
def i_should_see(datatable, the_following_users_exist):
names = [row[0] for row in the_following_users_exist]
assert names == sum(datatable[1], [])
Implementation
Commits should be reviewed one by one, otherwise things will be cluttered. Note that all commits are fully toxed.
- 9f744fd performs clean up that have nothing to do with the issues. It should probably be merged in any case.
- 8461566 is preparatory. Since a data table very much looks like an example table I factored out a generic class
Table
. Not much happens here, only renaming and trivial subclassing (I chose to keep the classExamples
for clarity and debugging purposes, although it inherits absolutely everything fromTable
). - 0b14878 implements the feature parsing part. Datatables are sort of multi-line steps so they are parsed at the same point in the file. I also introduced two helper functions
is_table_line
andparse_table_line
. - 454a01a is where I could easily be totally wrong. Fixture injection is deep into python dark magic and totally out of my comfort zone. Still I tried to figure out how to pass the data tables to steps implementations
- 67fffa1 adds some minimal test for this new feature (in case of merge we should probably add more)
- 7e7e9a7 is because I missed one
git commit --amend
before pushing...
Review status: 0 of 6 files reviewed at latest revision, 7 unresolved discussions.
pytest_bdd/feature.py, line 162 [r1] (raw file): Imperative e.g. "detect"
pytest_bdd/feature.py, line 173 [r1] (raw file): Parse instead of returns. There's a return section of the docstring.
pytest_bdd/feature.py, line 175 [r1] (raw file): :param str line:
pytest_bdd/feature.py, line 178 [r1] (raw file): Should there be newline according to docstring pep?
pytest_bdd/feature.py, line 261 [r1] (raw file): Please add a docstring instead of pass. This way we know what the purpose of the class is.
pytest_bdd/scenario.py, line 195 [r1] (raw file): I have a little doubt about the name of the fixture. User doesn't specify it during the step declaration in Python code. It might also clash with some 'datatable' fixture of some other guy. Can we think of the way to specify that on the @then decorator (or where it applies)? Or a bit more magical name (which I doubt even more).
tests/feature/test_datatable.py, line 21 [r1] (raw file): You see - here is it pretty magical. It is too implicit how you get the datatable value.
Comments from Reviewable
@PatrickMassot Very nice, however I have a small question about how do you pass the datatable value. Please have a look
@olegpidsadnyi I tried today to think about how to define the fixture name in a argument to the step decorator but failed. I doesn't seem to be doable with my level of understanding of all the magic going on in pytest-bdd and pytest in the first place (I'm not at all a professional programmer). Probably there are too many levels of introspection for me.
We don't have access to the Step
object when decorating the step function. It seems we would need the step decorator to create something like a temporary pytest fixture which would be overwritten by _execute_scenario
. I have no idea how to to this. And I'm afraid I don't have much time either (this pull request exists thanks to a five hour train ride but I would clearly need a much longer journey to go further).
If you don't have time for this you can either close this pull request or live with the fixed magic variable name for a while.
I'll have a look when I have more time
Hi @olegpidsadnyi, what is the status of this PR? It would be great having data tables support!
@PatrickMassot please rebase
@bubenkoff I'm not sure I understand your request (I'm not a git/github expert). Do you want me to do do something like: git rebase upstream/master && git push
? Wouldn't git complain that I'd be about to mess up things, and indeed wouldn't this mess up the above discussion? Or should I create a new branch based on upstream/master and a new PR?
yes i meant to squash commits into a single one better to do an interactive rebase: git rebase -i upstream/master and git push -f
Coverage increased (+0.08%) to 93.806% when pulling 264c4c23d063368b04dc2f4fc3d374caaa805ea6 on PatrickMassot:master into 544c81f59ef58a362eef65477997a10ab69eeb52 on pytest-dev:master.
Coverage increased (+0.08%) to 93.806% when pulling 264c4c23d063368b04dc2f4fc3d374caaa805ea6 on PatrickMassot:master into 544c81f59ef58a362eef65477997a10ab69eeb52 on pytest-dev:master.
@bubenkoff I think I've done what you want.
Coverage decreased (-1.05%) to 92.676% when pulling 929f7f17e7c96b50bd7bf32261a5fe5ecffcc83a on PatrickMassot:master into 544c81f59ef58a362eef65477997a10ab69eeb52 on pytest-dev:master.
it's not your fault, but could you please also fix the failing tests for py27-pytest-latest?
and the implementation comment: i think the name of the datatable fixture should be explicitly set in the step decorator call (just like we have a target_fixture argument already, may be something like data_table_fixture) How it is now, it's not very clear for the user what will be the name of the fixture
I'm afraid I cannot do anything to help here.
The py27-pytest-latest issue is completely beyond me (my branch does include 544c81f59ef58a362eef65477997a10ab69eeb52 which is meant to fix this).
The fixture name thing is the same issue that @olegpidsadnyi raised. I spend a couple of evenings trying to understand enough pytest/pytest-bdd magic to do that but couldn't do anything. There is so much magic happening at run time that I find it impossible to follow.
ok, i'll try to manage some time to look at both issues, thanks for your contribution!
@bubenkoff any updates?
@soulrebel not yet, sorry
@bubenkoff did you find some time to look at this? :)
Instead of data tables I am using now plain json and... this workaround sounds even better than data tables thanks to the pytest super powers! It's also very easy to implement parametrization for dynamic values.
Anyway I think I'll still be using json configuration steps even if pytest-bdd will introduce data tables support.
Hope it might help.
For additional information see:
How to use #json instead of #BDD data tables with #pytest #python (see "I set the encoded..." step). Full example https://t.co/d3CT6nRstl https://t.co/3J8nVvG8bW
@patrick91 nope, sorry @davidemoro - good stuff!
@bubenkoff Is this something that needs additional review or additional testing?
@francostrike yes it had comments on the changes, they are still not resolved
Review status: 0 of 6 files reviewed at latest revision, 7 unresolved discussions.
pytest_bdd/feature.py, line 162 at r1 (raw file):
Previously, olegpidsadnyi (Oleg Pidsadnyi) wrote…
Imperative e.g. "detect"
I'm going to try continuing this PR. Could you please elaborate on this comment. Do you mean that detect
is not the right word to use here?
pytest_bdd/feature.py, line 175 at r1 (raw file):
Previously, olegpidsadnyi (Oleg Pidsadnyi) wrote…
:param str line:
Just to be sure are these comments referring to an older revision?
Comments from Reviewable
Is this commit still being worked on? If not, I'd love this feature as well and can try to pick up work on the PR
@thefotios-enigma I don't think anyone is working on this.
It's becoming a running joke. Every few months, someone comes and asks about the status, and then nothing happens. So @thefotios-enigma, feel free to be the one who makes it become true.
I'll give it the old college try, need to get my pyenv/virtualenv/tox mess working properly first. In the meantime, I have something that works well enough that other people might want to use it in the interim (showing the same scenario as the original PR):
from pytest_bdd import given, then, scenarios, parsers
import functools
def parse_data_table(text, orient='dict'):
parsed_text = [
# Split each line on | and trim whitespace
[x.strip() for x in line.split('|')]
# Split the text into an array of lines with leading and trailing | removed
for line in [x.strip('|') for x in text.splitlines()]
]
# The first line is the header, the rest is the data
header, *data = parsed_text
if orient == 'dict':
# Combine the header with each line to create a list of dicts
return [
dict(zip(header, line))
for line in data
]
# Otherwise we want a list
else:
# Columnar
if orient == 'columns':
data = [
[line[i] for line in data]
for i in range(len(header))
]
return header, data
def data_table(name, fixture='data', orient='dict'):
formatted_str = '{name}\n{{{fixture}:DataTable}}'.format(
name=name,
fixture=fixture,
)
data_table_parser = functools.partial(parse_data_table, orient=orient)
return parsers.cfparse(formatted_str, extra_types=dict(DataTable=data_table_parser))
# Example of reading in a dict
@given(data_table('the following users exist:', fixture='users'))
def the_following_users_exist(users):
"""the following users exist:."""
return users
# Example of reading in a tuple of [header, columns]
@then(data_table('I should see the following names:', fixture='name_data', orient='columns'))
def i_should_see(name_data, the_following_users_exist):
expected = [x['name'] for x in the_following_users_exist]
_, columns = name_data
assert columns[0] == expected
@thefotios-enigma BTW I had a branch that built on this a while ago that added tests for fixture collisions if you want to use them or not, https://github.com/pytest-dev/pytest-bdd/pull/218
Hi !
Is there anyone working on this? Will this be released or forgotten? I saw some unanswered questions, if someone tries to finish this work will it be reviewed? If not, I'll need to fallback to behave...
Thanks