Chapter 1 argued that robust software engineering is about controlling risk with process and with automation. This chapter takes a step back and asks the question that comes before any test: what exactly is the software supposed to do, who decides that, and how do we write it down so that a machine can check it. The course answers with a very compact definition.
A requirement is a stakeholder's expression of a need, wish, or desire with regard to the system being built. Note what the definition puts first: not the system, but a person. A requirement is not a technical statement that happens to be written in prose, it is somebody's want, recorded.
That immediately raises the question of whose wants count, and the deck defines the second term just as tightly. A stakeholder is someone, or something, that derives value, benefits from, or influences our system. The word "something" is deliberate: a stakeholder need not be a human being. Another piece of software that consumes our API, or a regulator whose rules constrain us, influences the system just as much as a person does.
Stakeholders come in two flavours, and the distinction matters when you decide whom to interview and whom to demo to:
The deck draws an explicit and slightly provocative conclusion: system developers are not stakeholders, unless we are building a library. Your preference for a given framework, your taste in architecture, your wish for a cleaner build are not requirements. The exception is real and important: when the product is an API or a library, the developer using it becomes an acting stakeholder, and developer ergonomics becomes a legitimate requirement.
This is more than terminology. If developers were stakeholders, the requirement set would quietly absorb implementation preferences, and acceptance tests would end up testing the code we wrote rather than the value we promised. Keeping the boundary sharp is what makes the rest of the chapter, and acceptance testing in particular, possible at all.
Requirements are classified by the role they play in the project, and the deck lists five roles. They form a rough ladder from business intent down to technical constraint:
| Kind | What it captures |
|---|---|
| Business requirements | The value the organisation wants out of the system, usually stated by non-acting stakeholders. |
| Functional requirements | What the system must do: the visible behaviour, the inputs it accepts and the results it produces. |
| System requirements | Properties of the system as a whole, including the environment it must live in. |
| Non-functional requirements | Qualities rather than functions: performance, availability, security, usability. |
| Implementation requirements | Constraints imposed on how the thing is built, for example a mandated platform or language. |
The second axis is representation: how a requirement is written down and kept. The deck lists formal statements, business rules, examples, user stories, screen mock-ups, narratives and diagrams. These are not competing schools, they are different registers of the same content, and real projects mix them. A screen mock-up conveys a layout requirement in one second and would take three paragraphs of prose; a business rule is precise where a narrative would be vague. The important observation for this chapter is that examples are on the list: an example is a concrete instance of desired behaviour, and an example is exactly the kind of requirement that a machine can execute. That is the crack through which Gherkin and Cucumber will enter.
Whatever the kind and whatever the notation, the deck gives seven quality properties. A good requirement is:
Two of these are in permanent tension with each other, and noticing that is worth an exam point. Unambiguous pushes towards formality, while understandable pushes towards the stakeholder's own language. Gherkin, later in this chapter, is essentially an engineering compromise between the two: enough structure for a tool to parse, enough prose for a business analyst to sign off on.
Use the widget below to practise the judgement. Each candidate is a plausible-looking requirement; click it and see which of the seven properties it breaks.
Be able to recite the seven properties (unambiguous, understandable, testable, feasible, implementation-free, consistent, complete) and to diagnose a bad requirement against them. "The system must be fast" is the canonical failure: ambiguous and untestable at once. "Results shall be stored in a hash map" is the other canonical failure: it is not implementation-free, it is a design decision wearing a requirement's clothes.
A requirement on its own is not very useful. The deck is blunt about it: requirements are little useful per se, they should rather be given context, scope, and precision, and this is achieved by turning them into specifications. Context tells you when the requirement applies, scope tells you how far it reaches, and precision tells you what counts as satisfying it.
A specification is a description of system behaviour required to fulfil or realise a requirement. The requirement is the want; the specification is the behaviour that answers the want. One requirement typically expands into several specifications, because a single wish usually has several observable consequences.
The step from requirement to specification is where behaviour-driven development enters. The course states its aim in one line: BDD aims at turning requirements into executable specifications. "Executable" is the operative word. A specification written in prose has to be read by a human before anything can be checked; a specification written so that a tool can run it becomes a regression test that never goes stale, because the moment the code drifts away from it the build turns red.
The chain does not stop at specification. Conceptually, specifications can be turned into design, and design can be turned into implementation, with high guarantees that the implementation conforms to the requirements. The word "conceptually" is doing real work there: in ordinary practice these transformations are performed by hand, by people, and the guarantee is only as good as their discipline. The course notes that this chain becomes practical in so-called Model-Driven Engineering (MDE), where the transformations are mechanised and the models, rather than the code, become the primary artifacts. MDE returns later in the course as a topic in its own right; here it is enough to see where it sits on the chain.
Requirements are not written once and then obeyed. They have a life cycle, and the deck presents it as a diagram rather than as a bulleted list. The loop it depicts is the standard one of requirements engineering, and it is worth spelling out because every stage of it will reappear when we look at the ATDD cycle.
Requirements are first elicited from stakeholders, by interviews, observation or examples. They are then analysed, which is where contradictions and gaps surface and where the seven quality properties get applied. They are specified, that is, turned into descriptions of required behaviour with context, scope and precision. They are validated with the stakeholders, who confirm that what was written is what they meant. And then they are managed: the world changes, the business changes, and requirements are added, retired and revised, feeding the loop again.
The slide carries a figure with no accompanying text, so the stage names above are the ordinary requirements-engineering vocabulary rather than a quotation from the deck. What the deck unambiguously conveys with the figure is the shape: this is a cycle, not a phase you leave behind, which is exactly why executable specifications pay off. A requirement that lives as a runnable test is revalidated on every build instead of once, at kick-off.
With requirements and specifications in place we can define the kind of testing that judges them. The deck's definition:
Acceptance testing is a quality assurance (QA) process that determines to what degree an application meets end users' approval. Depending on the organisation, acceptance testing might take the form of beta testing, application testing, field testing or end-user testing.
Read the definition carefully, because two of its choices are unusual. It says "to what degree", not "whether": acceptance is a matter of degree, and a system can be accepted with reservations. And it says "end users' approval", not "correctness": the verdict belongs to people, not to an oracle. That is the difference in kind between acceptance testing and the unit testing of Chapter 1.
The deck attaches four characteristics, and they define the shape of everything that follows:
Do not confuse the position of acceptance testing in the pipeline with its perspective. Its position is late: after integration and system testing, before production. Its perspective is black box and business-facing. And the fourth characteristic (contractual) explains why the second matters so much: a contractual test that depended on internal APIs would make refactoring a breach of contract.
Recall TDD from the previous chapter: in the TDD technique, a single unit test is written before writing the (smallest) production code that passes the test. The rhythm is red, green, refactor, and the unit of progress is one small test.
ATDD keeps the rhythm and lifts the altitude:
In the ATDD technique, a single acceptance test is considered (from the user's perspective, mainly focusing on satisfying the system's functional behaviour) before writing, with TDD, the smallest production code that passes the test.
The crucial words are "with TDD". ATDD does not replace TDD, it wraps it. The outer loop picks one acceptance test and holds it red; the inner loop is ordinary TDD, one unit test at a time, until the outer test turns green. You end up with two nested feedback loops running at two different speeds: seconds for the unit tests, minutes or a demo cycle for the acceptance test.
The deck lists three differences between the two techniques, and they line up neatly with the definitions from section 1:
| TDD | ATDD | |
|---|---|---|
| Who takes part | Developer-centric | Includes stakeholders |
| What the tests face | Not business-facing | Acceptance tests are business-facing |
| What the tests touch | Exposes and exercises the API | Focuses on the requirement model |
The third difference is the deepest one and it is what motivates Gherkin. A TDD test is written in the language of the API, which is fine because its author owns the API. An acceptance test written in the language of the API silently violates the black box characteristic of section 5, and locks a business promise to a method signature. Section 9 shows exactly that failure mode in code.
Behaviour-driven development is defined by the deck as follows: BDD is a testing approach where tests are mainly based on systems' behaviour, typically expressed by the Given-When-Then approach. The triple is the whole grammar of the idea:
This is the classical arrange, act, assert of unit testing, restated in words a non-programmer can use. That restatement is the entire contribution: it moves the test from the developer's vocabulary into the stakeholder's.
How do BDD and ATDD relate? The deck is explicit that they are very similar, and essentially we shall use them as synonyms, while noting a difference of emphasis:
In other words, ATDD comes at the idea from the testing side (start from TDD, raise the level) and BDD from the specification side (start from behaviour, then build). They meet in the middle, which is why the same tool, Cucumber, serves both.
The ATDD cycle itself is shown in the deck as a diagram. Explore its stages below, and notice how the inner TDD loop nests inside it.
The cycle slide is a figure without a caption, so the stage names in the explorer are a reconstruction from the definitions given elsewhere in the two decks (in particular the lab's one-line summary: "ATDD: determine or pick an acceptance test, then TDD to make it pass"). In the wider literature the same loop is often taught as four Ds: discuss, distill, develop, demo. Learn the mechanism, and treat the names as scaffolding.
Before writing a single scenario, it helps to know at which altitude a scenario is supposed to fly. The deck gives three levels, each with a one-word question attached:
| Level | Question | Definition |
|---|---|---|
| Goal | why | Motivation or need of the stakeholder |
| Capability | how | Ability to do something with our system, in order to achieve the goal |
| Feature | what | System functionality or behaviour required to support the capability |
The three levels form a chain of justification. A goal explains why anyone should care; a capability says what the user will be able to do about it; a feature says what the system must actually provide so that the capability exists. Take the calculator example that runs through the deck: the goal is "I do not want to learn maths", the capability is "I want to be able to add numbers using a Calculator", and the features are the individual behaviours (adding two positive numbers, adding a positive and a negative, and so on).
The deck ends the slide with an arrow and an exclamation mark: tests in ATDD/BDD express features. Not goals, which are motivations and cannot be executed, and not capabilities, which are still too coarse. Features, the "what", are the level at which a behaviour is both meaningful to a stakeholder and checkable by a machine. Get the altitude wrong and your scenarios become either untestable manifestos or unreadable API calls.
Suppose we simply write the acceptance test with the tools we already have. The deck's motivating example is a calculator, tested with plain JUnit:
public class CalculatorTest {
@Test
void acceptanceTest() {
Calculator calculator = new Calculator();
calculator.enter(5);
assertEquals(5, calculator.getResult());
calculator.enter(6);
calculator.add();
assertEquals(11, calculator.getResult());
calculator.enter(3);
calculator.add();
assertEquals(14, calculator.getResult());
assertEquals(14, calculator.getResult());
calculator.enter(3);
assertThrows(IllegalStateException.class, () -> calculator.enter(5));
}
}
There is nothing wrong with this test as a test. It is readable, it covers a realistic session, and it even pins down an error case (entering a third operand without an operator throws). What is wrong with it is its role. The deck states the point directly: BDD/ATDD is not about APIs. Acceptance testing starts from requirements defined according to business analysts and domain experts, and without specific technologies those requirements tend to resemble system tests and are API-dependent.
Look at what a stakeholder would have to accept in order to sign off on this file. They would have to accept the names enter, add and getResult, the fact that operands are pushed one at a time before the operator (a stack-machine convention that nobody asked for), and the choice of IllegalStateException as the failure signal. None of that is a requirement. All of it is design. The requirement underneath is much smaller and much more stable: adding 5 and 6 gives 11.
An API-dependent acceptance test breaks twice over. It breaks the black box characteristic, because it asserts on the shape of the interface rather than on results. And it makes refactoring expensive: rename enter to push and a test that was supposed to encode a business promise goes red, even though the promise is intact. Recall the fourth characteristic from section 5: when this file is contractual, that rename is a contractual event.
Gherkin is the answer to the previous section. The deck introduces it as a simple specification language for features, part of Cucumber, and characterises its flavour precisely: it is not programming, but structuring user stories. The reference lives at cucumber.io/docs/gherkin/reference/. Crucially, it can be used to generate template executable tests, called "steps". That single sentence is the bridge between prose and code: you write the story, the tool writes the empty test methods, and you fill them in.
The main syntactic elements are few:
| Keyword | Role |
|---|---|
Feature | The typical entry point of a file. |
Scenario | Namely, an example. |
Given, When, Then, And, But | The steps, possibly carrying arguments. |
Background | A template example shared by the various scenarios. |
Scenario Outline | A scenario that addresses many examples at once. |
Notice that Scenario is glossed as "an example". This is the deck's way of saying that Gherkin is an example-based notation: you do not state a universal law, you exhibit concrete cases, exactly the "examples" listed among the ways of tracking requirements back in section 2. Here is the deck's full calculator specification:
Feature: Adding numbers with a Calculator
In order to not learn math
As someone who is bad at math
I want to be able to add numbers using a Calculator
Scenario: Add two positive numbers
Given I have a Calculator
When I add 1 and 1
Then the sum should be 2
Scenario: Add a positive and negative number
Given I have a Calculator
When I add 1 and -1
Then the sum should be 0
Scenario: Add two negative numbers
Given I have a Calculator
When I add -1 and -1
Then the sum should be -2
The three lines under Feature are free prose, ignored by the parser, and they map exactly onto the three levels of section 8: "in order to not learn math" is the goal, "I want to be able to add numbers using a Calculator" is the capability, and each Scenario below is a feature. Compare this file with the JUnit test of section 9: no class name, no method name, no exception type, and yet the same behaviour is pinned down. That is what "not about APIs" buys you.
Pick one Given, one When and one Then below. The widget assembles the scenario and shows the step definitions Cucumber would generate from it, including how literal values in the prose become typed parameters in the signature.
Gherkin describes; Cucumber executes. As the lab deck puts it in one line, Cucumber binds Gherkin to code execution, of the "steps". The deck lists the ingredients needed to get this running in IntelliJ with SBT and Java:
"io.cucumber" % "cucumber-java" % "6.1.1" % Test.test/resources/features/*.feature.Two details are worth pausing on. The dependency is scoped % Test, so Cucumber never ships with the production artifact: the executable specification is test infrastructure, not a runtime library. And the feature files live under resources, not under sources, because they are data to be parsed, not code to be compiled. That is also why a stakeholder can edit one without touching a compiler.
Asking the IDE to generate steps for the calculator feature produces a skeleton in which each distinct step text has become an annotated method:
package calculator;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
public class CalculatorSteps {
@Given("I have a Calculator")
public void iHaveACalculator() {
}
@When("I add {int} and {int}")
public void iAddAnd(int arg0, int arg1) {
}
@Then("the sum should be {int}")
public void theSumShouldBe(int arg0) {
}
}
Three scenarios produced three methods, not nine, because the three scenarios reuse the same three step texts with different numbers. Where the prose said 1, -1 or 2, the generated annotation says {int}: Cucumber recognised the literal as a typed parameter and turned it into a capture group, delivered to the method as an int. This is the whole binding mechanism, and it is why one step definition can serve an unbounded family of examples.
The step definitions are the only place where API knowledge is allowed to live. The feature file talks about calculators and sums; the Java class talks about enter, add and getResult. Rename a method and you fix one line in one class, while the executable specification, the part the stakeholder signed, is untouched. This indirection is precisely what the JUnit version of section 9 lacked.
Click any line of the deck's filled-in version to read what it does.
Notice the two decisions the deck makes explicit in this class. The When step only enters the operands, while the Then step performs the add() and compares: the arithmetic is triggered where the outcome is checked, which keeps each step small but does mean the mapping between Gherkin steps and API calls is a design choice, not a mechanical translation. And the failure is signalled by throwing IllegalStateException, with the deck's own comment noting that one could use JUnit's asserts instead. Cucumber declares a step failed when it throws, so any assertion library works.
The three-scenario calculator file repeats Given I have a Calculator three times, and it spells out three near-identical scenarios that differ only in two numbers. Gherkin has constructs for both problems, plus two ways of carrying richer data into a step. Each tab below shows the deck's own example.
The baseline: a Feature header with free prose (goal, role, capability) and one Scenario per example, each a Given-When-Then triple.
Feature: Adding numbers with a Calculator
In order to not learn math
As someone who is bad at math
I want to be able to add numbers using a Calculator
Scenario: Add two positive numbers
Given I have a Calculator
When I add 1 and 1
Then the sum should be 2
Background factors out the steps common to all the scenarios of the feature: it runs before each one, so the repeated Given disappears from the individual scenarios and every scenario still starts from a fresh, well-defined context.
Feature: Adding numbers with a Calculator
In order to not learn math
As someone who is bad at math
I want to be able to add numbers using a Calculator
Background: Start with a Calculator
Given I have a Calculator
Scenario: Add two positive numbers
When I add 1 and 1
Then the sum should be 2
Scenario: Add a positive and negative number
When I add 1 and -1
Then the sum should be 0
Scenario: Add two negative numbers
When I add -1 and -1
Then the sum should be -2
Scenario Outline parameterises the steps with placeholders in angle brackets, and an Examples table supplies one row per case. One outline plus three rows replaces three scenarios, and adding a fourth case is one line of table.
Feature: Adding numbers with a Calculator
In order to not learn math
As someone who is bad at math
I want to be able to add numbers using a Calculator
Scenario Outline: Add two numbers
Given I have a Calculator
When I add <arg0> and <arg1>
Then the sum should be <res>
Examples:
| arg0 | arg1 | res |
| 1 | 1 | 2 |
| 1 | -1 | 0 |
| -5 | -6 | -11 |
A doc string attaches a multi-line block of text to a single step, delimited by triple quotes. It is the way to carry a paragraph, a JSON payload or an expected page fragment into a step without mangling it into one line.
Feature: Google Searching
As a web surfer, I want to search Google, so that I can learn new things.
Scenario: Simple Google search
Given a web browser is on the Google page
When the search phrase "panda" is entered
Then results for "panda" are shown
And the result page displays the text
"""
Scientific name: Ailuropoda melanoleuca
Conservation status: Endangered (Population decreasing)
"""
A step table attaches tabular data to one step. Do not confuse it with the Examples table of a scenario outline: an Examples table multiplies the scenario into several runs, while a step table is a single argument, a value, passed to one step of one run.
Feature: Google Searching
As a web surfer, I want to search Google, so that I can learn new things.
Scenario: Simple Google search
Given a web browser is on the Google page
When the search phrase "panda" is entered
Then results for "panda" are shown
And the following related results are shown
| related |
| Panda Express |
| giant panda |
| panda videos |
Background versus Scenario Outline versus step table is a classic discrimination question. Background factors out shared setup and runs once per scenario. Scenario Outline with Examples factors out repeated structure and produces one run per row. A step table is neither: it is data for a single step. Doc strings are the same idea as step tables for free text instead of tabular data.
The deck closes its Gherkin tour with an example that shows how far a step table can be pushed. A whole chess position is passed to a When step as a table, and the expected king moves are checked against another table:
Feature: King movement
Scenario: A king in its beginning position
When there is a chess board set up as
| x | a | b | c | d | e | f | g | h |
| 1 | | | | BK | | | | |
| 2 | | | | | | | | |
| 3 | | | | | | | | |
| 4 | | | | | | | | |
| 5 | | | | | | | | |
| 6 | | | | | | | | |
| 7 | WP | WP | WP | WP | WP | WP | WP | WP |
| 8 | WR | WN | WB | WQ | WK | WB | WN | WR |
Then the WK at E8 should have the following moves
| Start | End |
Scenario: The pawn was moved up
When there is a chess board set up as
| x | a | b | c | d | e | f | g | h |
| 1 | | | | BK | | | | |
| 2 | | | | | | | | |
| 3 | | | | | | | | |
| 4 | | | | | | | | |
| 5 | | | | | | | | |
| 6 | | | | | WP | | | |
| 7 | WP | WP | WP | WP | | WP | WP | WP |
| 8 | WR | WN | WB | WQ | WK | WB | WN | WR |
Then the WK at E8 should have the following moves
| Start | End |
| E8 | E7 |
The first scenario expects an empty result table: with every square around it occupied by its own pieces, the king cannot move at all. In the second, the e-pawn has advanced, one square opens up, and the expected moves table has exactly one row. The example is instructive because it shows Gherkin used pictorially: the table is not decoration, it is the argument, and a domain expert who knows chess can check the specification without knowing anything about the board representation chosen in code.
Section 5 warned that acceptance testing very often is not automated. Now that we have Cucumber, how much of that gap can we actually close? The deck is careful, and the answer has two halves:
The two obstacles are of different natures, which is worth stressing. Usability is hard to automate because the property itself is subjective: no assertion decides whether a screen is confusing, only a user does. Integration with external resources is hard for a different reason: the property is objective, but the resource is outside our control, slow, stateful or simply absent from the build server.
For the second category the deck proposes concrete machinery: dependencies with external frameworks can be "managed" by suitable drivers, which create sorts of proxies facilitating the tracking of events for testing purposes. The examples given are AssertJ Swing for desktop GUIs and Selenium for the web. A driver of this kind sits between the test and the real component, exposes a programmable interface over something that was designed for a human, and lets the test observe events it could otherwise not see. The deck adds that mocking will be used as well, which is the subject of the next chapter: where a driver wraps the real thing, a mock replaces it.
Be ready to distinguish the two automation obstacles and their remedies. UI usability stays manual because the criterion is human judgement, and no driver fixes that; UI interaction can be automated with drivers or proxies such as AssertJ Swing and Selenium. External resources such as databases and servers are handled with drivers, or replaced with test doubles. And keep the honest conclusion of the deck in mind: automation extends acceptance testing, it does not abolish the physical interaction with stakeholders.
There is a design lesson hiding in this slide, and the lab's REENGINEER task makes it explicit: how easily a system can be acceptance-tested is a property of its architecture. An application whose logic is reachable only through button handlers forces you into Selenium or AssertJ Swing; the same application with the logic behind a plain interface can be driven directly. Testability is designed in, not added later.
Each module of ASMD has a lab activity, and each lab proposes two classes of exercises:
The R&D tasks are not meant to be all completed: pick one, or two, or more, depending on your interest and the time you have, start in the lab and continue at home. Students work alone or in small groups, typically in pairs. The teachers provide assistance as needed, during the lab or later offline, and ask that you share interesting results or completed tasks as soon as you have them: send an email with the subject [ASMD24-LABXX-TASKYY] and put authors and repository in the body.
On the exam: assume around 90 to 100 hours of work in addition to the labs. The exam is a discussion of the tasks completed by the student, for example one big task producing a software artifact (which can later become a scientific paper) or various smaller tasks, plus a discussion of the links to other parts of the course.
The lab deck compresses the whole module into three groups of lines, which are worth memorising as they stand:
The lab points at cucumber.io for Cucumber, at the Gherkin reference for the language, and at the starting repository github.com/mviroli/asmd23-public-01-atdd. It also lists LLM front-ends to experiment with (ChatGPT, Perplexity, Claude, Gemini) and notes that slide 01b, that is, the material of sections 10 to 12 above, provides rather complete examples of Gherkin syntax. The general goals are: be operative with Cucumber, JUnit, Java and IntelliJ; exercise writing requirements with Gherkin; exercise ATDD; and pre-check the ability of LLMs to deal with Gherkin, which the deck flags as still very preliminary and something the course will deepen later.
Step 1, get ready. Clone the repository 01-repo-atdd, open it in IntelliJ, install the Cucumber plugin (both the Cucumber for Scala and the Cucumber for Java plugins), and run the tests: both the JUnit ones and the Cucumber ones. For Cucumber, go to src/test/resources and run the feature files in the features directory.
Step 2, play with Cucumber and ATDD. Add operations to the calculator, for example multiplication; write Gherkin specifications for the new operations; implement them; and repeat the process for other operations. The deck insists on the order in capitals: follow the ATDD process, that is, specification first, implementation after. Then ask yourself the deck's closing question: does everything work as expected?
| Task | What it asks |
|---|---|
| TOOLING | Experiment with installing and using Cucumber with Scala and/or in VSCode. Is VSCode better at all here? Does Cucumber play well with Scala 3? |
| REENGINEER | Take an existing small application with a GUI, for example an OOP exam from bitbucket.org/mviroli/oop2023-esami (2023, 2022, and so on). Write Gherkin specifications explaining what the system is expected to do, and make the acceptance tests pass. Does the system need a refactor of its implementation? What does this tell us about how an application has to be designed in order to be easily acceptance tested? |
| REQUIRE | Write Gherkin specifications that completely capture the requirements (functional and non-functional) of a real application. You may use a previous project of yours with requirements already written down, or any existing or hypothetical small application. What are the good and bad aspects of Gherkin? |
| ATDD-LLM | LLMs can arguably help write, improve, complete, implement or reverse-engineer a Gherkin specification. Experiment with this, based on the tasks above or on other cases. Are LLMs useful here? Among the LLMs, which one is better? |
The four tasks are not independent: REQUIRE produces the specifications that REENGINEER makes pass, TOOLING decides the environment both of them run in, and ATDD-LLM can be layered on top of any of the others. If you want one project rather than four exercises, chain them.
A requirement is a stakeholder's expression of a need, wish or desire with regard to the system being built. A stakeholder is someone, or something, that derives value from, benefits from, or influences our system: non-acting ones include directors, business owners, enterprise architects and clients, while acting ones include UI and API users and the actors the system needs to be assisted by. Developers are not stakeholders because their preferences about frameworks, architecture or tooling are design decisions rather than expressions of need. The exception stated by the deck is building a library, where the developer using the API is genuinely an acting stakeholder.
A good requirement is unambiguous, understandable, testable, feasible, implementation-free, consistent and complete. "The system must be fast" fails at least two of them: it is ambiguous, because "fast" is not defined, and it is untestable, because no procedure can decide whether it holds. It is arguably also incomplete, since it does not say which operation must be fast or under which load. Making it good means fixing a measurable threshold and a scope, for example a response time bound for a named operation under a stated load.
A specification is a description of system behaviour required to fulfil or realise a requirement. The difference is one of role and precision: the requirement is the stakeholder's want, while the specification adds context, scope and precision by saying what the system must do about it. Requirements are little useful per se, which is why they are turned into specifications, and one requirement usually yields several. BDD is defined precisely as the practice of turning requirements into executable specifications. Beyond that, conceptually, specifications can be turned into design and design into implementation, with high guarantees of conformance to the requirements; since in ordinary practice those steps are manual, the guarantee depends on human discipline, and the chain becomes practical only in Model-Driven Engineering (MDE), where models are the primary artifacts and the transformations are mechanised.
Acceptance testing is a QA process that determines to what degree an application meets end users' approval, and it may take the form of beta testing, application testing, field testing or end-user testing. Its four characteristics are: very often it is not automated but involves physical interaction with stakeholders; it is black box testing, concerned with results rather than with the API; it is typically performed after integration and system testing and before production; and it is sometimes an essential element of software agreements and contracts.
TDD writes a single unit test before the smallest production code that passes it. ATDD picks a single acceptance test first, from the user's perspective and focused on functional behaviour, and then uses TDD to make it pass, so the two loops nest rather than compete. The three differences: TDD is developer-centric while ATDD includes stakeholders; ATDD acceptance tests are business-facing while TDD tests are not; TDD exposes and exercises the API while ATDD focuses on the requirement model. BDD is a testing approach in which tests are mainly based on the system's behaviour, typically expressed through Given-When-Then; the course states that BDD and ATDD are very similar and uses them essentially as synonyms, the difference being one of emphasis (ATDD as an extension of TDD towards acceptance tests written from actual requirements, BDD as a black-box starting point for system development, not necessarily for acceptance).
A goal is the why: the motivation or need of a stakeholder. A capability is the how: the ability to do something with our system in order to achieve the goal. A feature is the what: the system functionality or behaviour required to support the capability. Tests in ATDD/BDD express features, because that is the only level which is both meaningful to a stakeholder and executable by a machine. In the deck's calculator file, the prose under Feature states the goal and the capability, while each Scenario is a feature.
Because BDD and ATDD are not about APIs. That test hard-codes the method names enter, add and getResult, the convention of pushing operands before the operator, and IllegalStateException as the failure signal, none of which is a requirement. Without specific technologies, acceptance tests written this way tend to resemble system tests and are API-dependent, which contradicts the black box characteristic of acceptance testing and makes any refactoring break a test that was supposed to encode a business promise.
Gherkin is a simple specification language for features, part of Cucumber; its flavour is not programming but structuring user stories, and its reference is at cucumber.io/docs/gherkin/reference. It can be used to generate template executable tests, called steps. Its main elements are Feature (the typical entry point), Scenario (namely, an example), the steps Given, When, Then, And and But (possibly with arguments), Background (a template example for the various scenarios), and Scenario Outline (a scenario addressing many examples).
Background factors out the steps common to all the scenarios of a feature and runs before each one, removing repeated setup such as "Given I have a Calculator". A Scenario Outline parameterises its steps with placeholders in angle brackets and is instantiated once per row of its Examples table, so one outline replaces several near-identical scenarios. A step table is neither of those: it is tabular data passed as an argument to a single step, as in the chess example where a whole board is handed to a When step. Doc strings play the same role as step tables for multi-line free text.
You need the Gherkin and Cucumber for Java plugins in the IDE, the build dependency "io.cucumber" % "cucumber-java" % "6.1.1" % Test, and the feature files placed in a resource folder of the module, under test/resources/features/*.feature. Step code is generated by asking the IDE from the contextual menu of a step, and tests can be played directly on the feature files. The generated skeleton contains one annotated method per distinct step text, with literal values in the prose turned into typed parameters such as {int}.
Tests concerning functional aspects can be fully automated. Tests dealing with input/output aspects can hardly be automated, in particular the usability aspects of the UI and the integration with external resources such as databases or servers. Dependencies on external frameworks can be managed with suitable drivers, which act as proxies that make it easier to track events for testing purposes: AssertJ Swing and Selenium are the examples given. Mocking is used as well, and is the subject of the next chapter.
Every module has a lab with operational steps (quick exercises for the session) and R&D tasks (deeper research and development activities, similar to exam projects, requiring work outside the lab). The R&D tasks are not all meant to be completed: you pick one or more; students work alone or in small groups, typically pairs, and share results by email with the subject [ASMD24-LABXX-TASKYY], authors and repository in the body. The four tasks of Lab 01 are TOOLING (Cucumber with Scala and/or VSCode), REENGINEER (write Gherkin for an existing GUI app and make the acceptance tests pass), REQUIRE (capture the full functional and non-functional requirements of a real application in Gherkin, and judge Gherkin's strengths and weaknesses) and ATDD-LLM (use LLMs to write, improve, complete, implement or reverse-engineer Gherkin specifications, and compare them).