Search code examples
javacucumbercucumber-jvm

Creating dependent resources that can be configured


Description

Currently I'm creating some Cucumber features for a service. Let's say I have a service that is used for configuring Tesla cars:

public class TeslaCar {
    Engine engine;
    Color color;
    // other things here...
}

where

public class Engine {
    boolean isAllWheelDrive;

    // for 60, 70, 90D, P100D
    KilowattEnum kWh;

    // other things here...
}

The simple create calls are easy to implement, I just use Given combined with Add to configure the resources then call the create.

So for CreateEngine, I'd do:

Feature: CreateEngine
    Scenario Outline: Create Engine
        Given an engine was initialized
          And engine has <all_wheel>
          And kwh is <kwh>
        When engine is created
        Then engine creation succeeds

    Examples:
    | kwh | all_wheel |
    | 60  | false     |
    | 60  | true      |
    | 70  | false     |

And now I need to write an integration test for DriveCar. I want to use Given to ensure there is a new car that I can drive. I want to create a new car because I have no idea what the state of the past cars could be. If the battery level is at 10%, it won't be a good test. Then, since the integration test depends on the type of Tesla I have, I want to configure this in my feature file.

So it'd look like:

Feature: Drive Car
  Scenario: Test drive Tesla
    Given a car is initialized
      And engine kwh is P90D
      And engine is all wheel drive
      And engine is created
      # Need to call CreateEngine with above line, but is this clunky?
      And car is created
      # Need to call CreateCar above because Engine is a parameter to CreateCar
    When car is driven
    Then max speed is 120mph

Problem

This seems pretty reasonable at first, but is there a better way to do this? If more things needed to be configured for DriveCar, then it would become unwieldy pretty fast.

Questions

  1. Is this bad practice for tests?
  2. Is there a better way to accomplish this?
  3. Am I not thinking the Cucumber way?

Possible Solution 1?

One thing I thought of is to use @tags. This would let me say, 'I need a 60kWh engine whenever I use @60kwh'. But this doesn't extend well.

Possible Solution 2?

Don't configure the car and instead just use a default one.


Solution

  • Cucumber is designed for acceptance tests, tests which exercise the entire system (so they are also integration tests) as the user would and can be understood by stakeholders as well as by developers.

    Your CreateEngine scenario isn't for the entire system, but for a software component that only has meaning to developers, so I'd test it not with Cucumber but with a unit-testing framework.

    Your "Drive Car" scenario, on the other hand, is an appropriate acceptance test. It has a lot of software detail, though. I'd write it something like this:

    Feature: Drive Car
    
      Scenario: Test drive Tesla
        Given there is a car with an all-wheel-drive engine and P90D engine kwh
        When the car is driven
        Then the max speed is 120 mph
    

    (I'm not sure that "P90D engine kwh" is correct grammar, so please correct as necessary.)

    Important points:

    • There is no reference to software detail.
    • Values that aren't important to the outcome aren't mentioned; they're just defaults. Note that if there are any other values that are important to the outcome, they should be mentioned too, so that the reader has all of the information in front of them needed to verify the correctness of the outcome, without having to read the step definitions. For example, if Teslas also had tail fins and tail fin size affected max speed, the step that defines the car should also mention "large tail fins" or whatever.

    I'm not sure whether you need more flexible car definition steps than I showed or not. I usually find that only a few simple steps are needed in most scenarios. Try to stay simple at first and build more complicated steps only if you need them.

    Also, resist the urge to write Cucumber scenarios (or scenario outlines) for all combinations of data. Acceptance tests are slow and and take work to maintain, so you want to have as few as you can while still exposing all of the important requirements to stakeholders. When you start writing combinatorial Cucumber scenarios, think whether you can instead write one Cucumber scenario as an example and test all the combinations in unit tests.