Reputation: 10548
I've been trying to learn about Cucumber in Ruby and I thought that the best way to do that would be to make my own project. However, I'm wondering what constitutes a good "Given" clause.
As far as I understand, "Given" is basically a set up, "When" is the function under test, and "Then" is the expected result.
For example, let's assume I am making a Minecraft scenario based on an entity stepping in lava. My current G-W-T looks like this:
Scenario: Take damage when I stand in lava.
Given an entity is standing next to a block of lava with 10 health
When the entity steps in the block of lava
Then the entity should take 2 damage
However, this "Given" step seems fairly 'off'. It doesn't make sense that I should have to be standing next to a block of lava for this scenario to work. Similarly - how would I write (and test) a GWT for a scenario that should always happen - for example, how could I ensure that as long as my entity remains in lava, that it will keep taking damage? I find it hard to write code that will test how long an entity has been standing in lava. How is the system to know how long the entity has been sat in lava? it seems to me that testing that sort of thing would require me almost writing the rest of the world in order to be able to say "this entity has been in the lava for x seconds, advance the simulation, how much hp have I lost"
Thoughts?
Upvotes: 5
Views: 1990
Reputation: 3312
I've tried to improve readability of Fresh' answer. I'm currently learning Gherkin (using the excellent The Cucumber book). Code can be downloaded from minecraft_gherkin_example
Feature: Take damage when I stand in lava.
In minecraft, lava is bad for your health.
Every unit of time, damage reduces your health.
Scenario Outline: Step into lava
Given my health level is <Health>
When I step into the lava
And I wait <LavaTime> unit of time
Then my new health level is <NewHealth>
And the outcome is <Outcome>
Examples:
| Health | LavaTime | NewHealth | Outcome |
| 10 | 1 | 8 | Alive |
| 10 | 4 | 2 | Alive |
| 4 | 1 | 2 | Alive |
| 2 | 1 | 0 | Game over |
Upvotes: 0
Reputation: 37647
You don't have to rewrite the world. You just have to be able to fool your tests about the state of the world (in this case, time). The usual way to control time in tests is to stub.
I would write that scenario like this
Scenario: Take damage when I stand in lava.
Given I have 10 health
And there is a block of lava next to me
When I note the time
And I step in to the block of lava
And I wait 5 seconds
Then I should have 8 health
and implement the time steps like this:
When /^I note the time$/ do
@start = Time.now
end
When /^I wait (\d+) seconds$/ do
Time.stub(:now) { @start + 5.seconds }
end
When I note the time
is kind of artificial, so you might fold that in to another step if it made sense. (I don't see an appropriate step in this case, but you might in a longer scenario.) When I wait 5 seconds
is perfectly appropriate to the domain, though.
Other niceties:
Given
is for conditions that are true before the scenario starts. One way to think about it is that time might have elapsed between when the Given
becomes true and when the actual scenario starts, during which other things might have happened that aren't relevant to the scenario.Given
that initializes your health. Minimizing such dependencies makes Cucumber steps more reusable. So as long as it doesn't hurt understandability (which I don't think it does in this case) just assert the final state at the end.Upvotes: 2
Reputation: 810
Like said above, a Give clause indicates some sort of setup. Most of my Given clauses can be found in my Background. For example:
Feature: A New User
Background:
Given I am a new user
Scenario: Writing a new Feature
And I add "text" to my new feature
Then I should have a new feature named "feature.feature"
In this case, the background validates that "I am a new user". In feature files, the background step(s) get ran before each subsequent Scenario.
Upvotes: 0
Reputation: 20230
Interesting question!
"It doesn't make sense that I should have to be standing next to a block of lava for this scenario to work."
If an entity is NOT standing next to lava, then it won't be able to step into lava. What exactly don't you like about your scenario?
Now regarding testing how much damage gets inflicted on an entity, if you were writing this scenario to test the actual Minecraft game, then you'd have to enable some kind of browser based timer to count the monitor the amount of time that passes (if it were being played in a browser). This would indeed be awkward.
However if you were writing your own version of Minecraft, then you could write you scenario so that it would test the code itself (i.e. not test the code running in a browser). For instance:
Scenario: Take damage when I stand in lava.
Given an entity is standing next to a block of lava with 10 health
When the entity steps in the block of lava
And remains there for a unit of time
Then the entity should take 2 damage
If this test was exercising the code you'd written, you would be able to accurately control the amount of time that the entity would be spending in the lava region (hence the use of "unit of time")
Similarly:
Scenario: Take fatal damage when I remain standing in lava.
Given an entity is standing next to a block of lava with 10 health
When the entity steps in the block of lava
And remains there for 5 units of time
Then the entity should lose all health
You are right when you say:
"it seems to me that testing that sort of thing would require me almost writing the rest of the world"
You hit the nail on the head when you say "almost". The key to this BDD approach is to take an evolutionary approach and mock as much as possible initially to satisfy the test. Once the test goes green, then implement the mocked areas using TDD.
Upvotes: 1