Suppose an acceptance criterion says a traveler should see the next train departures. Outside-in development starts with that expected result, then works toward the code needed to produce it. Rather than designing every internal component first, developers use the acceptance criterion to decide what behavior to specify and implement next. section 2.3.7 section 3.5.4
A practical progression is:
- Start with a customer-facing acceptance criterion that states the expected outcome. section 2.3.7
- Write developer-facing specifications as unit tests for the behavior needed to meet it. Use those tests to discover what a component needs from its collaborators. section 2.3.7 section 3.5.4
- Implement enough behavior to pass the tests, adding tests for further cases and edge conditions. Avoid code that contributes nothing to the acceptance outcome. section 2.3.7 section 3.5.4
Discovering a timetable interface
To find the next departures, an itinerary service needs to know which train lines connect two stations and when trains leave on those lines. A unit test can first supply a dummy timetable with fixed times. That test exposes the two operations the service needs from the timetable; further tests check multiple departures and the case where no more trains are available. The timetable’s own behavior can then be developed through its tests. section 3.5.4
The same principle can shape acceptance-test automation. Name a task for the actor’s goal, not the particular clicks used to achieve it. A task that locates a registration form can navigate through the home page or go directly to the registration page without changing a test that depends only on that goal. section 15.1.4
Listing 15.4 Grouping alternative implementations of the same task
import { Task } from '@serenity-js/core';
import { Click, Navigate } from '@serenity-js/web';
export class LocateRegistrationForm {
static viaHomePage = () =>
Task.where(`#actor locates registration form via home page`,
Navigate.to('/'),
Click.on(Form.buttonCalled('Register')),
);
static viaDirectNavigation = () =>
Task.where(`#actor registration form via direct navigation`,
Navigate.to('/register'),
);
}
The two Serenity/JS task implementations make the substitution concrete: each locates the same form by a different route. section 15.1.4
