In one of my previous posts, I wrote about the single and right level of abstraction. In that article, I focused on the code and unit tests level. However, the single and the right level of abstraction principle applies also at a much higher level – at the level of specification by example. The driving force behind it is different than in the case of unit tests though, so it’s worth a separate discussion.
1. Write specification to engage stakeholders
The main driving force behind the specification by example is discussion with stakeholders. The primary reason for formulating requirements as examples is that such a form facilitates dialog. We don’t treat specificaton as something closed that stakeholders are able to provide in a perfect, final form in the first shot, but rather as something that will be iteratively, and what’s most important cooperatively refined.
The examples are really good at it, because they describe concrete, narrow cases, not complex and abstract policies. They are straightforward questions, asking how the system should behave in a particular situation, and it’s much easier to answer such questions that to validate the whole complex algorithm all at once.
The examples provide many other benefits, for example they work nicely as acceptance criteria and lend themselves well to automation, but these benefits are byproducts. The focal point of the specification by example are stakeholders. This is important to remember, because all the other guidelines stem directly from this fact.
2. Hide implementation details
To really engage stakeholders, you must write the examples at the level of abstraction they understand and are interested in. Usually, this is the business rules and behaviors level, not the implementation details or interface elements level.
For example, such specification isn’t at the abstraction level appropriate for stakeholders:
Given I have "some item" in my shopping cart And I'm on the "delivery options" screen When I click a "priority delivery" radio button And I click the "proceed to the checkout" link Then I should be redirected to the "checkout" screen And the "total" input field should equal "some item" price plus the "priority delivery" cost
Stakeholders are not interested in radio buttons, input fields, links and screens. They think in business terms, like this:
Given I have "some item" in my shopping cart And I've chosen priority delivery When I proceed to the checkout Then my total price should equal "some item" price plus the "priority delivery" cost
This is often summarized as a “describe what not how rule” and it’s very similar to staying at the proper level of abstraction in unit tests. The difference lies in what determines which level of abstraction is correct. In the case of unit tests, this is determined by the layer of the system a tested class is the part of; in the case of specification by example, this is determined by the stakeholder the specification targets.
3. Give concrete examples (don’t generalize)
The goal of specification by example is, as the name implies, to specify how the system should work by giving concrete examples of its behavior instead of generalized descriptions. The key word here is concrete. If you make an example too general, it stops to be an example and starts to lean more towards the “traditional” requirements description again.
Therefore, the following examples are too vague:
Given I've bought only digital items Then I should not be charged for delivery --- Given I've bought some digital items And I've bought at least one physical item Then I should be charged for delivery
While the more concrete examples are better:
Given I've bought a PDF book and an mp3 song Then I should not be charged for delivery --- Given I've bought a PDF book and an mp3 song and a computer mouse Then I should be charged for delivery
This may seem contradictory to the “describe what not how” principle. However, making examples concrete by including business details is not the same as leaking implementation details. Implementation details make specification hard to understand and brittle. Concrete business examples enable discussion with stakeholders, which is the goal of the specification by example approach.
An example can be treated as a question for a stakeholder to answer: “Given such start conditions, when such an action happens, then we should expect such results – is it correct?”. It’s much easier to reason about questions like this when they are concrete. Vague, generalized algorithms or policies leave a lot of room for hidden edge cases that the stakeholder can miss.
But how detailed the examples should be? In the example above, when we discuss a customer buying a book, is it enough to specify it’s an ebook? Or do we have to be more precise and say it’s a PDF book? Or should we provide the book title, author and ISBN number?
This leads us to another important observation:
4. Use the abstraction level relevant to a given stakeholder
A project almost always has many stakeholders, operating on different levels of abstraction, looking at the system from different perspectives. In order to implement the system correctly, you need to have a dialog with all of these stakeholders.
In effect, you’ll be writing examples targeting different people. As discussed in the previous section, to get the most value out of the discussion, the examples need to be concrete – they need to contain relevant business details. But here’s a catch: for different people different details are relevant. There’s no single abstraction level suitable for everyone.
Consider a sales representative and a CEO, reading examples regarding “trusted” customers. The CEO will likely make decisions about high-level policies, like below, himself:
Given a Trusted Customer When he delays with payment Then he should still be able to make new purchases Given an Untrusted Customer When he delays with payment Then he should not be able to make any new purchases
On the other hand, a CEO will likely delegate decisions about low-level details, like below, to a domain expert from the sales department:
Given an order with a bank transfer form of payment When the current date is 7 days from the purchase date And there is still no payment received Then the payment should be considered delayed Given an order with a credit card form of payment When the current date is 2 days from the purchase date And the purchase week day is Mon, Tue, Wed or Thu And there is still no payment received Then the payment should be considered delayed Given an order with a credit card form of payment When the purchase week day is Fri, Sat or Sun And the wire transfer week day is Tue Then the payment should be considered on time
Both the CEO and a sales expert need concrete examples to be able to reason about them, but the “concreteness” for each of them means very different level of detail.
For each example you write, you must be aware who you write this example for, and consciously select the appropriate level of detail.
5. Target only one stakeholder per example
Because, as I’ve just discussed, different stakeholders need a different level of detail, it seems obvious that trying to involve more than one stakeholder in a single example can result in a messy description, mixing high and low level details, like this:
Given an Untrusted Customer And an order with a credit card form of payment When the current date is 2 days from the purchase date And the purchase week day is Mon, Tue, Wed or Thu And there is still no payment received Then the customer should not be able to make any new purchases
The above example is unnecessarily complicated and has poor readability. However, this isn’t the biggest problem. As I mentioned at the beginning of this post, the primary reason to write requirements in the example format is to facilitate discussion with stakeholders. With the above example, such discussion is hardly possible.
You can’t ask CEO if this example is correct, because he’ll get lost in the details of how bank transfers’ processing may be delayed on weekends etc. These are operational details, that should be well understood by specialized domain experts, but are irrelevant at the CEO level. You can’t ask the sales department domain expert if this example is correct either, because although he will understand all the low level details, he’s not in position to validate if the decision about the company strategy towards trusted and untrusted customers is correct.
To fully validate this example, you’d have to gather both the CEO and the sales domain expert in the same place and at the same time and have them work out the answer together, which makes discussion less effective and more difficult to schedule.
Although it may not be immediately obvious, the same rules that govern unit tests (or clean code in general) apply also at the specification by example level. The difference lies in what motivates these rules: the ease of dialog with stakeholders instead of code readability.
It’s important to remember this relation between clean code principles and examples. Examples are often written by developers. By a slight shift of focus – thinking in terms of stakeholders instead in terms of architecture – developers can leverage most of the knowledge they already posess, utilizing rules like the Single Level of Abstraction Principle to write better, more robust examples.
How do you write your examples? Do you notice any other patterns that are similar to the code-related ones? Please share your experiences in the comments section below!