erikedin / Behavior.jl

Tool for Behavior Driven Development in Julia
Other
25 stars 3 forks source link

Support the use of variables in step definition #51

Closed tk3369 closed 3 years ago

tk3369 commented 3 years ago

As mentioned in the README.md file:

One would like to be able to define a variable in a step definition like @given "some value {foo}" begin. Without this, scenario outlines may become difficult to use, since you have to define a separate step definition for each value used in the outline.

Here's an example from Grakn's python client:

@step("entity({type_label}) create new instance; throws exception")
def step_impl(context: Context, type_label: str):
    assert_that(calling(context.tx().concepts().get_entity_type(type_label).as_remote(context.tx()).create), raises(GraknClientException))

This enhancement is required to support Grakn client. See master issue #44.

tk3369 commented 3 years ago

@erikedin, here's one possible syntax:

@when "foo is set to value {x::Int}" begin
    context[:foo] = args[:x]  # already parsed using parse(Int, str)
end

Alternatively, having a do-block seems less magical and allow the user to name their own variables:

@when "foo is set to value {Int}" do context, x
    context[:foo] = x
end

Thoughts?

erikedin commented 3 years ago

I think the {x::Int} syntax looks good. Making it look like Julias typing is a good idea I think.

I was thinking that perhaps the variables can be automatically added in the macro that calls the step. So, they would be magically available, like

@when "foo is set to value {x::Int}" begin
    context[:foo] = x
end

I'm not sure if I actually can do that though. It ought to be possible? I'll have to look into the Julia macros more closely.

Also, I think types should be optional and default to strings, so that {x} is short for {x::String}.

tk3369 commented 3 years ago

Based upon my understanding of macros, anything is possible! :-) It may be a little tricky to get it right but this syntax looks awesome to me.

I agree about the default for String.

erikedin commented 3 years ago

@tk3369 I think I'm going to implement the args syntax you first suggested, to begin with.

@when "foo is set to value {x::Int}" begin
    context[:foo] = args[:x]  # already parsed using parse(Int, str)
end

I'm doing this for these reasons:

  1. It's simpler to implement.
  2. It will still work even if/when we get the variables to automatically be in scope
  3. What if a variable has a name that is not a valid Julia variable name?

The more I think about it, case 3 will be a problem. For instance, any variable name involving a hyphen will invalid. Any variable name that is a reserved keyword, like else or try will also be invalid. To get variables to automatically be in scope, we'll have to detect this scenario, and either rewrite the variable name in some way (which will be confusing for the user) or simply skip that variable, and only have it be accessible using args.

Note: I may or may not choose some other name than args, like vars or something. I'll see if the Cucumber documentation has some pointers about the name.

erikedin commented 3 years ago

It's now implemented using the args[:foo] syntax, but no type information support yet.

tk3369 commented 3 years ago

Yeah, that's why I thought the alternative syntax would be less magical and the user must pick a valid name:

@when "foo is set to value {Int}" do context, x
    context[:foo] = x
end

The only drawback is that the user would have to align the variable names with the positions when there are multiple ones.

erikedin commented 3 years ago

@tk3369 I've been thinking it over and getting used to the idea, and I think the do-notation you suggested is better. It won't be a major thing to change, but it'll break all existing step implementations and I'll have to update whole bunch of tests. I think I could make it backwards compatible, so that the begin/end notation is still valid, but I don't think it's worth the effort or extra complications.

Tracked separately in issue #68

erikedin commented 3 years ago

Now parameters can be other built-in types like Int and Float64, so I'm going to close this. Any further work on parameters will have to be in another issue, so this doesn't live on forever.

Then this is seventeen: 17

will match

@then("this is seventeen: {Int}") do context, v
    @expect v == 17
    @expect typeof(v) == Int
end

(I may have typos above, but that's the idea)