Do not write any code unless you know why the user cares.
We have two ways of knowing whether a user cares: the design, and the acceptance specification.
The Design
When starting a feature, we start with a design. What we mean by this can be vaguely high level, or it can be exact. Here are two example stories:
- As a moderator I want to mark a message as spam so I can improve the messaging experience.
- As a paying member I want the latest news articles 15 minutes before unpaying members.
We “design” both of these first. The first one gets a visual: either a drawing on a piece of paper, or a HTML wireframe, or a full template using high_voltage, or something else.
The second example is harder to represent visually, so it gets a more detailed user experience discussion, coupled with diagrams: which news articles, how do they get these articles, how do they know it’s 15 minutes faster–and all of that leads into a visual to work off, too.
The Acceptance Spec
Much has been written about acceptance specs through the ages, but a topic that continues to arise is that of varying level of detail. Here are two examples:
Scenario: Moderator marks messages as spam
Given a user exists with a username of "joey user"
And the following message exists:
| user:username | subject |
| joey user | Subjects are lame |
And I am signed in as a moderator
When I go to the spams page
And I follow "Subjects are lame"
And I press "Spam"
Then I should see "the message has been marked as spam"
Given I am signed in as "joey user"
When I go to the messages page
Then I should not see "Subjects are lame"
Scenario: Moderator marks messages as spam
Given a user has a message
And I am signed in as a moderator
When I mark that message as spam
Then the user should not see the message
The first scenario spells out exactly what to do, step-by-step. It is written as if I were using a super lame programming language that didn’t have abstractions. The second scenario describes the user’s experience; it describes the reason to care about the whole concept.
The second scenario is very closely related to the design discussion: all the key points of the discussion are there, with none of the boring details (like the fact that messages can have subjects). Moreover, it’s readable.
“OK, great,” you say. “Woo hoo, cucumber. So what?”
Cut the sass; you can do this in Rspec too:
describe 'moderation (like smoking cigars)' do
it 'works as expected when the moderator marks a message as spam' do
harness.make_user_with_message
harness.sign_in_as_moderator
harness.mark_the_message_as_spam
harness.user_should_not see_the_message
end
let(:harness) { ModerationTestHarness.new }
end
The Reasoning
There are at least four reasons why you absolutely need to start doing both of these things right now:
- Do not build what you do not need. If it never shows up in the UI, and especially if it never shows up in the UX, drop it. Why did you even think you needed it at all?
- Confine changes to one place. If the existing story does not change, why should the test for it? For example, say you finally convince the client that messages should not have subjects (“‘cause it’s, like, 2012; c'mon,” is your winning argument); this would cause you to re-write the first scenario, even though that story is fine and intact. You would have to change the implementation of the second scenario, but that’s OK and boring; the high-level concept still exists.
- Keep it readable. The second scenario is easier to read, plain and simple. It hides more–when debugging, you will need to open another file–but that is only relevant when it breaks.
- Provide consistency. Going through the app from the end user’s perspective means the names of models will match what the user sees, the algorithms and especially data will make sense. Compare “I need to show all weekly reminders across the events” to “I have a reminders table and an events table and need to select the reminders that have the weekly flag set and join with events on …”.