pytest-bdd icon indicating copy to clipboard operation
pytest-bdd copied to clipboard

Add support for data tables

Open PatrickMassot opened this issue 8 years ago • 54 comments

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 class Examples for clarity and debugging purposes, although it inherits absolutely everything from Table).
  • 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 and parse_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...

This change is Reviewable

PatrickMassot avatar Apr 19 '16 21:04 PatrickMassot

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

olegpidsadnyi avatar Apr 20 '16 08:04 olegpidsadnyi

@PatrickMassot Very nice, however I have a small question about how do you pass the datatable value. Please have a look

olegpidsadnyi avatar Apr 20 '16 08:04 olegpidsadnyi

@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.

PatrickMassot avatar Apr 24 '16 19:04 PatrickMassot

I'll have a look when I have more time

olegpidsadnyi avatar Apr 25 '16 22:04 olegpidsadnyi

Hi @olegpidsadnyi, what is the status of this PR? It would be great having data tables support!

davidemoro avatar Jul 13 '16 14:07 davidemoro

@PatrickMassot please rebase

bubenkoff avatar Jul 16 '16 17:07 bubenkoff

@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?

PatrickMassot avatar Jul 20 '16 07:07 PatrickMassot

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

bubenkoff avatar Jul 26 '16 20:07 bubenkoff

Coverage Status

Coverage increased (+0.08%) to 93.806% when pulling 264c4c23d063368b04dc2f4fc3d374caaa805ea6 on PatrickMassot:master into 544c81f59ef58a362eef65477997a10ab69eeb52 on pytest-dev:master.

coveralls avatar Jul 26 '16 22:07 coveralls

Coverage Status

Coverage increased (+0.08%) to 93.806% when pulling 264c4c23d063368b04dc2f4fc3d374caaa805ea6 on PatrickMassot:master into 544c81f59ef58a362eef65477997a10ab69eeb52 on pytest-dev:master.

coveralls avatar Jul 26 '16 22:07 coveralls

@bubenkoff I think I've done what you want.

PatrickMassot avatar Jul 26 '16 22:07 PatrickMassot

Coverage Status

Coverage decreased (-1.05%) to 92.676% when pulling 929f7f17e7c96b50bd7bf32261a5fe5ecffcc83a on PatrickMassot:master into 544c81f59ef58a362eef65477997a10ab69eeb52 on pytest-dev:master.

coveralls avatar Jul 26 '16 22:07 coveralls

it's not your fault, but could you please also fix the failing tests for py27-pytest-latest?

bubenkoff avatar Jul 28 '16 11:07 bubenkoff

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

bubenkoff avatar Jul 28 '16 11:07 bubenkoff

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.

PatrickMassot avatar Aug 04 '16 11:08 PatrickMassot

ok, i'll try to manage some time to look at both issues, thanks for your contribution!

bubenkoff avatar Aug 04 '16 14:08 bubenkoff

@bubenkoff any updates?

neg3ntropy avatar Nov 22 '16 11:11 neg3ntropy

@soulrebel not yet, sorry

bubenkoff avatar Nov 22 '16 12:11 bubenkoff

@bubenkoff did you find some time to look at this? :)

patrick91 avatar Mar 07 '17 14:03 patrick91

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

davidemoro avatar Mar 07 '17 21:03 davidemoro

@patrick91 nope, sorry @davidemoro - good stuff!

bubenkoff avatar Mar 08 '17 11:03 bubenkoff

@bubenkoff Is this something that needs additional review or additional testing?

franco-reyes avatar May 31 '17 21:05 franco-reyes

@francostrike yes it had comments on the changes, they are still not resolved

bubenkoff avatar May 31 '17 21:05 bubenkoff

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

incazteca avatar Jun 01 '17 04:06 incazteca

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 avatar Jan 01 '18 18:01 thefotios-enigma

@thefotios-enigma I don't think anyone is working on this.

incazteca avatar Jan 01 '18 19:01 incazteca

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.

PatrickMassot avatar Jan 01 '18 20:01 PatrickMassot

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 avatar Jan 02 '18 21:01 thefotios-enigma

@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

incazteca avatar Jan 02 '18 22:01 incazteca

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

rlvrs avatar Jun 28 '19 16:06 rlvrs