Dan Pantry Dan Pantry - 3 months ago 16
Ruby Question

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?

Answer

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.