10

Sometimes, you need to define values dynamically, (like datetime now, random strings, random integers, file contents, etc.) and use them across different steps without being explicit or hard-coding the value.

So, my question is how could I define variables inside of steps (the correct way to do it) to use these variables in the following steps.

Some example

Given A random string of length "100" as "my_text"
And I log in to my platform
And I ask to add the following post:
 | title                    | description |
 | Some example of title    | {{my_text}} |
When I submit the post form
Then The posts table shows these posts:
 | title                    | description |
 | Some example of title    | {{my_text}} |
And I delete any post containing in the description "{{my_text}}"

This is a basic example trying to explain why I would like to define variables in steps and save them in the context to use it in the following steps.

My idea was to modify before_step and after_step methods... to set a variable in context to store my custom variables like this:

def before_step(context):
    if not hasattr(context, 'vars'):
       context.vars = {}

    if hasattr(context, table) and context.table:
       parse_table(context)

def parse_table(context):
    # Here use a regex to check each cell and look for `"{{<identifier>}}"` and if match, replace the cell value by context.vars[identifier] so the step "the posts table shows these posts will never know what is `{{my_text}}` it will be abstract seeing the random string.

Scenarios Outline, use something like this defining variables like "<some_identifier>" and then for each example replace the value in the step.

It's basically to reproduce the behaviour but for any kind of step, simple or using tables.

Is it the right way to do something like this?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Federico Castro
  • 119
  • 1
  • 1
  • 5

3 Answers3

4

From Behave docs on the context:

When behave launches into a new feature or scenario it adds a new layer to the context, allowing the new activity level to add new values, or overwrite ones previously defined, for the duration of that activity. These can be thought of as scopes:

@given('I request a new widget for an account via SOAP')
def step_impl(context):
    client = Client("http://127.0.0.1:8000/soap/")
    // method client.Allocate(...) returns a dict
    context.response = client.Allocate(customer_first='Firstname',
        customer_last='Lastname', colour='red')
    // context vars can be set more directly
    context.new_var = "My new variable!"

@then('I should receive an OK SOAP response')
def step_impl(context):
    eq_(context.response['ok'], 1)
    cnv = str(context.new_var)
    print (f"This is my new variable:'{cnv}'"

So, the value can be set using dot notation and retrieved the same.

ingyhere
  • 11,818
  • 3
  • 38
  • 52
0

To answer this question, one needs note:

  • Does the test data needs to be controlled externally? For example, test data can be inputed from command line so that the value can be chosen explicitly.

If the answer is no, then probably we should not bother hard coding anything in the feature file. And we can leave the data generated in one step, save it in context, and accessed again in any followed step.

The example I can think is exactly like what the question described. Do we care what the random text content we generated, posted and verified? Probably not. Then we should not expose such detail to user (i.e. feature file) since it is not important to the behaviour we are testing.

If the answer is yes, we do need a bit hack to make it happen. I am experiencing a case like this. What I want is to change the test data when I run the test so I don't have to hard code them in the feature files as in a table or scenario outline. How can I do this?

I can use -D option in command line to pass in as many user data as possible, which can then be accessed in context.config.userdata dictionary in any steps. If the number of test data is very limited. This approach is an easy way to go. But if the test data set contains many data that no one want type one by one in command line, it can be stored externally, for example, a ini file with section names like testdata_1...testdata_n, and thus a string can be passed in from command line to be used to address the section name in this config file. And the test data can be read out in either before_all, or before_scenario, etc., and get used in all steps.

Murphy Meng
  • 237
  • 3
  • 8
0

In my experience , you cannot create a dynamic value in feature file.
for example, this step :
Given A random string of length "100" as "my_text"
I dont see any way to change {my_text} each time you run the scenario. (not consider to use behave -D to parse the value to context.config.userdata,I think it is also a wrong approach)
Even Scenario Outline, it actually splits to many scenarios. each scenario will have different value but the value of {my_text} is already defined in Examples table for each scenario.

The way makes a step dynamic is using Step definition (Code layer).
You can generate a random number in step definition @given('A random string of length "100" as "{my_text}"')
And use context.my_text to store the created number and using it arround.

I also agree with Murphy Meng that you don't need to expose the generated random number explicitly in feature file. You know which step will use that number, simply use context.my_text in that step to get the value. That's it.