Through my recent User Experience course through Algonquin College I was exposed to the five elements of User Experience (UX) described in 1. This note proposes how to apply these planes to a Behavior Driven Development (BDD) feature system implemented using Gherkin syntax.
While [^garret] is addressing how to work through the necessary specification of User Experience for a design, from a software designer and architect perspective, I realised that the five elements are really just five layers of design specification to work through for any kind of design, including more abstract software systems that may or may not contain a recognizable (Graphical) User Interface.
In addition, when you combine the five planes with BDD feature definition using Gherkin syntax, you now have a nice in-repository method for defining project requirements. Since the requirements are in-repository, the change of the requirements over time also perfectly aligns with the evolution of the code itself in the same repository.
Complete traceability of requirements against implementation. Nirvana? Maybe.
The five Elements
The five elements of User Experience from 1 are defined here in order from abstract to concrete:
- Strategy: User needs and product objectives.
- Scope: Functional specifications or content requirements.
- Structure: Interaction design.
- Skeleton: Interface or information design.
- Surface: Sensory design.
Note that this is the reverse order from that used in the book in order to facilitate understanding of what should probably be worked on first; before you start working on the details of you fancy new REST API (requirements probably residing in the Skeleton plane), you want to have a good grasp of the strategy and goals of the project (the Strategy plane).
BDD features in Gherkin syntax
Gherkin is a common syntax for defining BDD features in plain text files. You could argue that BDD features are only intended to be used for functional specification or content requirements, but I’m suggesting here that the syntax is general enough that you can apply it to all five layers.
You can see here a very brief BDD feature in Gherkin syntax.
Feature: utility exit strategy
When someone runs the utility they want the exit state of the utility to
indicate the success or failure of the execution so that the state can be
used by other processes to decide how to proceed if the utility succeeded or
failed.
Scenario: an error occurs in the utility
Given the user initiates execution of the utility
When the utility halts execution due to an error
Then the utility exits with "dirty" state (exit non-zero)
Scenario: the utility completes without errors
Given the user initiates execution of the utility
When the utility completes execution without errors
Then the utility exits with "clean" state (exit 0)
Note also that I’m using Job Story2 syntax for the feature description here instead of User Story syntax. I am beginning to align with the idea that the Job Story better focuses the expression of a requirement around the important why and not just the what.
Rules of thumb
These “rules” may not be always accurate for your project, or even any particular requirement but they should be useful as a kind of “requirement smell” to help guide effective organization of features.
- Only one feature per feature file.
- A feature in the Strategy plane that has many scenarios might be a smell that it has too much detail and at least some of it belongs in a lower layer.
- Corollary: A feature in lower planes (more concrete specification) that persists as “not implemented” over time (or gets converted to “not testable”) might be a smell that the feature belongs in a higher plane such as the Strategy plane.
Gherkin tagging
Gherkin tagging is a syntax to add labels to some parts of a feature file, including the feature itself. The tag enables filtering of features tags for functions such as running tests.
eg. Using the Python behave package to not run anything applying the
@notimplemented
tag.
behave features --tags ~@notimplemented
Unimplemented features
By default a feature should be tagged as “not implemented” - at least initially it is almost certainly going to be the case that there are no step definitions for the feature. As step definitions for a feature are implemented then the tag must be removed in order for it to be executed under the presumed rules of the CI pipeline that excluded the not implemented tag from execution.
Tagging the feature as not implemented enables the BDD run to
ignore missing step files and such for feature files that contain the
@notimplemented
tag. This is important while features and code are in active
development simultaneously; requirement specifiers need to be able to freely
create new requirements while at the same time not impeding the ability of
developers to implement against the requirements that already exist.
@notimplemented
Feature: utility exit strategy
Note also that you can tag scenarios and some other Gherkin elements. This means that you can implement a single scenario in a feature with multiple scenarios and tag the remainder as not implemented, even though the feature must have the “not implemented” tag removed to enable execution of the newly implemented scenario.
Features applied to UX planes
Features that are implemented against one of the UX planes simply tag the feature with the name of the UX plane it applies to.
@notimplemented @structure
Feature: utility exit strategy
In the above example, without a lot more context it’s not easy to identify which plane that feature should be applied to. In this case I’ve just chosen the Structure plane for the example.
Using the UX plane tag, now development could focus on a specific UX plane using the tag as a filter for test execution.
Feature file Structure
In addition to tagging the feature definition, the file structure can also be used to facilitate browsing of the feature definitions within the repository.
The feature definition files (plain text files with a .feature
suffix) are
typically placed in the features
directory in the root of the repository.
Feature file parsing utilities such as Python behave also look for .feature
files in the feature folder recursively. This means that sub-folder can be used
to facilitate the organization of features into the planes.
. <project root>
└── features
├── 1_strategy
│ └── <my strategy>.feature
├── 2_scope
│ └── <my scope requirements>.feature
├── 3_structure
│ └── <my structure requirements>.feature
├── 4_skeleton
│ └── <my skeleton requirements>.feature
└── 5_surface
└── <my surface requirements>.feature
I’ve prefixed each directory with a number to further facilitate the understanding of the planes as an ordered sequence. Otherwise the file system is going to just order them alphabetically which could be confusing to newcomers or occasional requirement browsers such as some project stakeholders.
Feature files in the directory of a plane must all tag the feature with that UX plane as described above.
eg. All features in the Strategy folder would apply the @strategy
tag, and
so on.
Conclusions
I’ve outlined a process for applying the five User Experience specification planes to BDD features defined using Gherkin syntax. Multiple useful side effects arise from using BDD feature files, including:
- Using the Gherkin syntax helps organize requirements in a way that aligns with User Experience design.
- In-repository source control of requirements maintained in lock-step with
code implementation.
- Even when tests cannot be implemented in code, the fact that the requirement is defined continues to highlight the need for that requirement and helps inform the code reader/consumer about the intention for the code (not forgetting that that code consumer could be your future self).
- The in-repo requirements facilitate code discovery and comprehension; you can start with the feature file, trace testing through step definitions, and then through to integration and unit tests.
- A machine and human readable syntax for defining requirements that can be used to facilitate automation of various stages of requirements implementation and code testing.
- When it comes time to release, part of the review can now include why any
features are still tagged as not implemented; is it because the requirement
was overlooked, or because the nature of the requirement is not testable
using automation?
- For that matter, CI automation could fail a release pipeline, or perhaps a
pull request if any features persist with the
@notimplemented
tag. In this case it might be useful to make a distinction between@notimplemented
and@nottestable
where some stakeholder review process converts the tag of features that cannot be tested.
- For that matter, CI automation could fail a release pipeline, or perhaps a
pull request if any features persist with the