How would I best express this time-dependent scenario in Cucumber?

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?

Answers


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:

  • It's concise and appropriate to the domain to use first person.
  • 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.
  • Health doesn't have much to do with standing next to lava, so those are better set up in separate steps.
  • Testing that you've taken two damage (as opposed to that your health is 8) would require a dependency between the assertion step and the 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.

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.


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.


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 |

Need Your Help

Why are there local variables in stack-based IL bytecode

c# java bytecode local-variables

In a stack-based intermediate language, such as CIL or Java bytecode, why are there local variables? One could just use only the stack. May not be so easy for hand-crafted IL, but a compiler can su...

Can't embed File Form to another Form In Symfony 2.8

php forms symfony

The idea here is to add a file (photo ) to a user or other entity. I decided that this file could have its own entity Media.