This post continues directly from Using Sequences in Lucid. In this post I want to show a bit about how sequences can be parameterized, from simple variable elements in statements all the way to the use of data tables.
You probably already know that steps in a TDL can have arguments or parameters. Well, in similar fashion, a sequence phrase can have one or more arguments or parameters as well.
To test this out, add the following in your sequences.spec file:
Ability: Sequences @sequence Scenario: Test Sequence Creation 2 Given the step "* [checking a triangle with <side1>, <side2>, <side3> as sides]" is defined to mean: """ Given the triangle test app When the data condition is "<side1>", "<side2>", "<side3>" """
A sequence phrase can contain zero or more placeholders and here you see I have more than one. You can probably see that this is just a slight variation on the sequence definition we did before, the difference here being that when you invoke the sequence phrase, you will do so with parameters. Those parameters have to then be transferred to the actual sequence steps.
As you know from the last post, there are two modes in which a sequence is handled by Lucid. In definition mode, which is what you see in the sequence phrase above, a placeholder is delimited by angle brackets <...> whereas in invocation mode, any value that is bound to a placeholder is delimited by double quotes. You can see that in the sequence steps.
Now let’s place the invocation in your triangle.spec file:
Scenario: Test Sequence Execution 2 When [checking a triangle with "3", "4", "5" as sides]
The idea is that the actual values that are bound to the arguments are passed to the sequence steps at execution time.
If you run the lucid command, you should find that this works.
Note that the distinction here between the angle brackets in the sequence phrase and the quotes in the sequence step are necessary in order for Lucid to attempt to correlate the two.
An alternative way to pass data values in a Gherkin-structured TDL is via a table. To enable this for a given sequence definition, you have to ensure that in the definition of the sequence phrase, the quoted sentence ends with a terminating colon (:) character. To put this into practice, addd this sequence definition to your sequences.spec file:
@sequence Scenario: Test Sequence Creation 3 Given the step "* [checking a triangle with sides]:" is defined to mean: """ Given the triangle test app When side1 is set to "<side1>" And side2 is set to "<side2>" And side3 is set to "<side3>" """
Notice how there is a colon inside the quoted string. That’s critical to indicate that the sequence phrase relies on a table. With the definition in place, let’s add an invocation by putting the following in triangle.spec:
Scenario: Test Sequence Execution 3 When [checking a triangle with sides:] | side1 | 3 | | side2 | 4 | | side3 | 5 |
When you try to execute the above scenario, you’re going to have to put a matcher in your triangle_steps.rb file. Notice that Lucid tries to help you by providing this matcher:
When (/^side(\d+) is set to "(.*?)"$/) do |arg1, arg2, table| # table is a Lucid::AST::Table pending end
Notice how a table is expected? The problem here is that Lucid is actually trying to match one of the steps from your sequence definition, specifically:
When side1 is set to "<side1>"
That statement does not require a table. However, Lucid is executing that sequence step in the context of a sequence phrase that does require a table. So what you’ll want to do is add the following matcher, and note how it differs from that which Lucid provided:
When (/^side(\d+) is set to "(.*?)"$/) do |arg1, arg2|
Now let’s modify that a bit with some code:
When (/^side(\d+) is set to "(.*?)"$/) do |side, value|
Notice here that I’ve changed the arguments from the defaults of ‘arg1’ and ‘arg2’ to ‘side’ and ‘value’. I’ve also included a call to an action within a page context. To have this work, you’ll need to add the following action to the triangle.rb page definition:
def enter_side(side, value)
when "1" then self.side1 = value
when "2" then self.side2 = value
when "3" then self.side3 = value
If you run the lucid command now, you should see that everything passes.
Maybe this next thing doesn’t fit strictly into “providing data” for sequences but you can have sequences call other sequences. Let’s add the following sequence definition to sequences.spec:
@sequence Scenario: Test Sequence Creation 4 Given the step "Given [a scalene]" is defined to mean: """ When [entering a scalene triangle] """
Here notice that my When sequence step actually calls the step defined in the “Test Sequence Creation 1” scenario. So basically my sequence phrase is indicating that the sole step in that sequence will invoke yet another, previously defined, sequence phrase.
Now put the following in triangle.spec:
Scenario: Test Sequence Execution 4 Given [a scalene]
If you run the lucid command, you should find that everything works.
This post hopefully gives you a fairly good idea of how you can send data information as part of parameterized statements when those statements are either part of the sequence phrase or part of the sequence steps.
There are a few other things I’ll note about sequences. One of those is that you are not able to define sequences in Backgrounds or within Scenario Outlines. The reason for the former is that since the background is executed for each scenario, any definition would be executed twice which would effectively be seen by Lucid as trying to define a sequence phrase with an existing key. In terms of the scenario outline restriction, the same logic applies: the general steps of a scenario outline are repeated. If a definition is used, it will attempt to create the same sequence key twice.
Note that all of this is speaking to defining sequences; you can however most certainly invoke a sequence from either a background or a scenario outline.
Also note that if you are using sequences, using options to run specific scenarios at the command line, like below can result in problems:
lucid specs/triangle.spec --name "Execution 3"
The reason for this is probably obvious: the above command causes that specific spec file to execute. What will not get parsed is the sequence phrases that the spec file relies on. This could be solved, of course, by putting the sequence phrases in the same spec file as the invocations of those phrases. That, however, would seem to remove the benefit of having a sequence phrase available for many spec files to use.
So what you might note here is a concern that sequences do introduce a dependency between spec files. That’s generally not a good thing. However, this is not a dependency in execution per se — as in “Test 1 relies on Test 2” — but rather equivalent to what you do in most any programming language when you require or import header or library files. There are various ways this could be made to work and one area I’m looking at is that Lucid could follow a convention wherein all spec files with the name “_sequence.spec” will be parsed for any run through. This would allow you to have different such files, like “triangle_sequence.spec”, “square_sequence.spec”, and so on.
As with the previous post, my goal here was to show you the functionality in terms of the benefits and in terms of the costs. Right now it is an open question as to whether the costs outweigh the benefits or vice versa. Reports from the field will likely make that decision.