Ch. 7 Writing Feature Files to Describe Behavior

We now have a complete model of an alarm clock. However, the alarm clock’s behavior is only defined at a high level. There isn’t enough detail to actually build such a component yet.

In this chapter, we’ll learn how to further specify the behavior of a component by refining this behavior into feature files. Feature files effectively form executable test cases for a component. The objective of a feature file is to provide an automated mechanism to ensure a product is compliant with its specification. Models and feature files are typically created in tandem, with modelers and test engineers working close together. In general, the modeler is concerned with the high level details of the normal cases and the test engineer ensures behavior for the off nominal cases is also defined.

Introduction To Gherkin

Features files are defined using the Gherkin language. This language was made popular by the behavior driven development product Cucumber. From the Gherkin wiki:

“Cucumber understands the language Gherkin. It is a Business Readable, Domain Specific Language that lets you describe software’s behavior without detailing how that behavior is implemented.”

Cucumber provides a way to execute feature files written in the Gherkin syntax. Feature files can be written by almost anyone, regardless of their background. Software developers then write code for each line in the feature file to perform the logic of the test. Cucumber then executes the feature file by invoking the appropriate code in sequence.

Why Cucumber?

You may ask, why do I need a tool like Cucumber, isn't this what unit test do? The answer
is no. Cucumber is intended to test a running application. This means that if you expect messages to be received in
order to perform some capability, your test code should send that message using the same mechanism your code expects to
receive it. Your test code should also assert that your code responds with the appropriate response message.

Gherkin is made up of a series of keywords. Each line in a feature file must begin with an keyword. Keywords can be followed by any text. This is what makes Gherkin so expressive. The main keywords are listed below:

  • Feature
  • Scenario
  • Given, When, Then, And, But (Steps)
  • Background
  • Scenario Outline
  • Examples

System Descriptor vs Gherkin

Gherkin scenarios are not to be confused with System Descriptor scenarios, as they are separate entities.

A feature file contains one or more scenarios. Each scenarios is made up of multiple steps. Like the System Descriptor scenario steps, these steps must start with either the given, when, or then keyword. Below is an example of a scenario pulled directly from the Cucumber documentation:

Gherkin Scenarios

Scenario: feeding a small suckler cow
 Given the cow weighs 450 kg
 When we calculate the feeding requirements
 Then the energy should be 26500 MJ
 And the protein should be 215 kg

It is also possible to create scenario outlines. These scenarios uses tables to describe variables and values:

Scenario Outlines

Scenario Outline: feeding a suckler cow
  Given the cow weighs <weight> kg
  When we calculate the feeding requirements
  Then the energy should be <energy> MJ
  And the protein should be <protein> kg
 
  Examples:
    | weight | energy | protein |
    |    450 |  26500 |     215 |
    |    500 |  29500 |     245 |
    |    575 |  31500 |     255 |
    |    600 |  37000 |     305 |

More information about Gherkin can be found at https://cucumber.io/docs/reference and https://github.com/cucumber/cucumber/wiki/Gherkin Be sure to review this material before preceding.

Writing a Feature File for the Alarm Component

Recall the alarm components looks like this:

Alarm.sd

model Alarm {
     
    input {
        ZonedTime currentTime
        ZonedTime alarmTime
        AlarmAcknowledgement alarmAcknowledgement
    }
     
    output {
        AlarmStatus alarmStatus
    }
     
    scenario triggerAlarm {
        given haveReceived alarmTime
        when receiving currentTime
        then willPublish alarmStatus
    }
     
    scenario deactivateAlarm {
        when receiving alarmAcknowledgement
        then willPublish alarmStatus
    }
}

We’ll start by refining the behavior of the deactivateAlarm scenario. We define a feature file for each scenario defined in a model. This feature file usually has multiple Gherkin scenarios for testing both normal cases and edge cases of that scenario.

As discussed earlier, model files reside in the src/main/sd directory of a System Descriptor (SD) project. Gherkin feature files reside in the src/test/gherkin directory of an SD project. The feature files are placed in a directory structure that mirrors the package structure of the feature file’s corresponding model. Since the Alarm component resides in a package named alarm, the feature files for this component will be placed in the alarm directory inside the src/test/gherkin directory. Feature files are named using the following format: modelName.scenarioName.feature

Since we are creating a feature file for the deactivateAlarm scenario of the Alarm component, the name of the feature file is Alarm.deactivateAlarm.feature. The full path of this file within with the project is src/test/gherkin/alarm/Alarm.deactivateAlarm.feature. To create this file, first create the package/directory. Within Eclipse, right click the src/test/gherkin directory and select New -> Package. Enter the name alarm and select OK. Right click the new package and select New -> File. Name the file Alarm.deactivateAlarm.feature.

Feature files always start with the Feature keyword. What follows is a description of the entire feature or SD scenario.

Alarm.deactivateAlarm.feature

Feature: Alarm deactivateAlarm
  The alarm should be deactivated when the alarm component receives an
  alarm acknowledgement message.

The file will contain multiple scenarios which begin with the scenario keyword. A descriptor follows each each work. The remaining lines are steps that form the scenario itself. Our first scenario will handle the normal case of deactivating an active alarm that has been acknowledged.

Alarm.deactivateAlarm.feature

Feature: Alarm deactivateAlarm
  The alarm should be deactivated when the alarm component receives an
  alarm acknowledgement message.
   
   
  Scenario: Deactivate an acknowledged an alarm.
    Given the alarm has published an AlarmStatus message
      And the alarmId field is 1
      And the active field is true
    When the alarm receives an AlarmAcknowledgement message
      And the alarmId field is 1
      And the alarmAcknowledged field is true
    Then the alarm will publish an AlarmStatus message
      And the alarmId field will be 1
      And the active field will be false

This scenario has a given steps, when steps, and then steps. The given steps set up the component to publish an AlarmStatus message with some fields set to the given values. This sets up the component, so we can acknowledge an active alarm. Note that we didn’t specify how the alarm got activated. We’ll leave that to the test implementer. The when steps result in the component receiving an AlarmAcknowledgement message to acknowledge the alarm. Finally, the then steps make sure the component publishes an AlarmStatus message for the alarm that was acknowledged and that the alarm is no longer active.

The scenarios should also cover the off nominal scenarios:

  • Do not deactivate an alarm is the alarmAcknowledge field of an acknowledgement is false.
  • Publish a deactivated alarm status when an acknowledgement is received even if the alarm is not currently active.
  • Do not reactivate a previously acknowledged alarm.

The complete feature file is given below:

Alarm.deactivateAlarm.feature

Feature: Alarm deactivateAlarm
  The alarm should be deactivated when the alarm component receives an
  alarm acknowledgement message.
   
   
  Scenario: Deactivate an acknowledged an alarm.
    Given the alarm has published an AlarmStatus message
      And the alarmId field is 1
      And the active field is true
    When the alarm receives an AlarmAcknowledgement message
      And the alarmId field is 1
      And the alarmAcknowledged field is true
    Then the alarm will publish an AlarmStatus message
      And the alarmId field will be 1
      And the active field will be false
   
   
  Scenario: Do not deactivate an alarm if the acknowledgement is false.
    Given the alarm has published an AlarmStatus message
      And the alarmId field is 2
      And the active field is true
    When the alarm receives an AlarmAcknowledgement message
      And the alarmId field is 2
      And the alarmAcknowledged field is false
    Then the alarm will publish an AlarmStatus message
      And the alarmId field will be 2
      And the active field will be true
   
   
  Scenario: Deactivate an alarm if an acknowledgement is received for an unknown
    alarm.
    When the alarm receives an AlarmAcknowledgement message
      And the alarmId field is 3
      And the alarmAcknowledged field is true
    Then the alarm will publish an AlarmStatus message
      And the alarmId field will be 3
      And the active field will be false
       
       
  Scenario: Do not reactivate a previously acknowledged alarm.
    Given the alarm has published an AlarmStatus message
      And the alarmId field is 4
      And the active field is true
      And the alarm has received an AlarmAcknowledgement message
      And the alarmId field is 4
      And the alarmAcknowledged field is true
      And the alarm has published an AlarmStatus message
    When the alarm receives an AlarmAcknowledgement message
      And the alarmId field is 4
      And the alarmAcknowledged field is false 
    Then the alarm will publish an AlarmStatus message
      And the alarmId field will be 4
      And the active field will be false

Cucumber Language Support

Cucumber supports many different language. However, we shouldn't care which language
the test are written. For example, we successfully tested a C++ application using a Java implementation of the test.
Using a micro-service architecture (MSA) is a great way to help facilitate this type of flexibility. See the next
chapter for an introduction to MSA.

Conclusion

Use feature files to help describe fine grained behavior of a component (nominal and off-nominal). Feature files should be refined from the scenarios defined in a component’s model. The scenarios defined in a feature file should support complete autonomy and should have all the detail necessary to ensure the test can pass repeatedly. The SD model and the feature files together form a specification for a component or system. Conflicts between model and feature files should be resolved before the publishing the specification.