4. @drpicox
@drpicox
London School of TDD: Mockists
use spies to verify implementation
higher coupling
Chicago School of TDD: Statists
depend on results, not on implementation details
our objective
LONDON VS CHICAGO TDD
4
TDD TECHNIQUES — LONDON VS CHICAGO
8. @drpicox
@drpicox
Test that has no business value
Test that helps us to construct the code
Test that we
f
inally remove
STEP TEST
8
TDD TECHNIQUES — STEP TEST
import org.junit.jupiter.api.Test;
public class BowlingGameTest {
@Test
public void create_game() {
var g = new Game();
}
@Test
public void roll_a_ball() {
var g = new Game();
g.roll(0);
}
}
10. @drpicox
@drpicox
1. First test
2. Step tests
3. Remove UI/API references
4. Refactor
5. Business value
A. Bonus: Use tables
CRAFTSPEOPLE RECIPE
10
TDD TECHNIQUES — CRAFTSPEOPLE
11. @drpicox
@drpicox
Write
f
irst the test that gives you the excuse for writing the
f
irst code.
1. FIRST TESTS
11
TDD TECHNIQUES — CRAFTSPEOPLE
test("create a game", () => {
const game = new Game()
})
class Game {}
13. @drpicox
@drpicox
13
TDD TECHNIQUES — CRAFTSPEOPLE
// 1: I want to check that I can add the search box
test("There is a search box", () => {
const searchBox = getByPlaceholderText(container, "search")
expect(searchBox).toBeInTheDocument()
})
// 2: I want to check that I can write in the text box
test("The search box accepts text", () => {
const searchBox = getByPlaceholderText(container, "search")
userEvent.type(searchBox, "apollo x")
expect(searchBox).toHaveAttribute("value", "apollo x")
})
// 3: I want to check that I have a search button
test('There is a search button', () => {
const searchButton = getByText(container, 'search')
expect(searchButton).toBeInTheDocument()
})
...
14. @drpicox
@drpicox
// 6: I want to check that results are in the response area
test("The search result is shown in the response area", async () => {
const searchBox = getByPlaceholderText(container, "search")
})
14
TDD TECHNIQUES — CRAFTSPEOPLE
15. @drpicox
@drpicox
// 6: I want to check that results are in the response area
test("The search result is shown in the response area", async () => {
const searchBox = getByPlaceholderText(container, "search")
userEvent.type(searchBox, "apollo x")
})
15
TDD TECHNIQUES — CRAFTSPEOPLE
16. @drpicox
@drpicox
// 6: I want to check that results are in the response area
test("The search result is shown in the response area", async () => {
const searchBox = getByPlaceholderText(container, "search")
userEvent.type(searchBox, "apollo x")
const searchButton = getByText(container, "search")
})
16
TDD TECHNIQUES — CRAFTSPEOPLE
17. @drpicox
@drpicox
// 6: I want to check that results are in the response area
test("The search result is shown in the response area", async () => {
const searchBox = getByPlaceholderText(container, "search")
userEvent.type(searchBox, "apollo x")
const searchButton = getByText(container, "search")
searchButton.click()
await fetchMock
.whenGET("/api/v1/search?q=apollo%20x")
.respond(["snoopy and charlie brown"])
const resultArea = await findByTestId(
container,
"search-response"
)
expect(resultArea).toHaveTextContent(
"snoopy and charlie brown"
)
})
17
TDD TECHNIQUES — CRAFTSPEOPLE
18. @drpicox
@drpicox
Make test express your intention, not how to use the UI or API
Protect your tests from the UI/API changes
3. REMOVE UI/API REFERENCES
18
TDD TECHNIQUES — CRAFTSPEOPLE
import { search, findSearchResult } from "./__helpers__/ui.js"
test("The search result is shown in the response area", async () => {
search(container, "apollo x")
await fetchMock
.whenGET("/api/v1/search?q=apollo%20x")
.respond(["snoopy and charlie brown"])
const resultArea = await findSearchResult(container)
expect(resultArea).toHaveTextContent("snoopy and charlie brown")
})
19. @drpicox
@drpicox
Make test (and code) easy to read and understand
4. REFACTOR
19
TDD TECHNIQUES — CRAFTSPEOPLE
import { search } from "./__helpers__/ui.js"
import { theSearchServiceFor } from "./__helpers__/searchService.js"
test("The search result is shown in the response area",
async () => {
theSearchServiceFor("apollo%20x").respond([
"snoopy and charlie brown",
])
const resultArea = search(container, "apollo x")
expect(resultArea).toHaveTextContent(
"snoopy and charlie brown"
)
})
20. @drpicox
@drpicox
All tests should directly express requirements
5. BUSINESS VALUE
20
TDD TECHNIQUES — CRAFTSPEOPLE
import { search } from "./__helpers__/ui.js"
import { theSearchServiceFor } from "./__helpers__/searchService.js"
// removed test('There is a search box', () => {
// removed test('The search box accepts text', () => {
// removed test('There is a search button', () => {
// removed test('The search button makes a fetch from the service',...
test("Search for the LEM and Module name", async () => {
theSearchServiceFor("apollo%20x").respond([
"snoopy and charlie brown",
])
const resultArea = search(container, "apollo x")
expect(resultArea).toHaveTextContent("snoopy and charlie brown")
})
21. @drpicox
@drpicox
Refactor your test to accommodate tables
BONUS: USE TABLES
21
TDD TECHNIQUES — CRAFTSPEOPLE
test.each`
text | query | response | result
${"unknown"} | ${"unknown"} | ${[]} | ${"no results"}
${"apollo"} | ${"apollo"} | ${["snoopy", "eagle"]} | ${"snoopyeagle"}
${"apollo x"} | ${"apollo%20x"} | ${["snoopy"]} | ${"snoopy"}
${"apollo x module:command"} | ${"apollo%20x&module=command"} | ${["charlie brown"]} | ${"charlie brown"}
${"module:command"} | ${"&module=command"} | ${["charlie", "columbia"]} | ${"charliecolumbia"}
`(
'Search for the LEM and module name of "$text"',
async ({ text, query, response, result }) => {
theSearchServiceFor(query).respond(response)
const resultArea = search(container, text)
expect(resultArea).toHaveTextContent(result)
}
)
23. @drpicox
@drpicox
Find a path for small changes
Recipe for representation changes:
1. Add the structure representation → Test
2. Add a setter code → Test
3. Repeat 2. until no more setters
4. Modify a getter code → Test
5. Repeat 4. until no more getters
6. Clean a getter code → Test & Repeat
7. Clean a setter code → Test & Repeat
8. Remove old representation → Test
DO SMALL REFACTORS
23
TDD TECHNIQUES — REFACTOR
24. @drpicox
@drpicox
export default class Game {
_score = 0;
roll(pins) {
this._score += pins;
}
score() {
return this._score;
}
}
EXAMPLE (BGK)
24
TDD TECHNIQUES — REFACTOR
25. @drpicox
@drpicox
export default class Game {
_score = 0;
_rolls = [];
roll(pins) {
this._score += pins;
}
score() {
return this._score;
}
}
EXAMPLE (BGK)
25
TDD TECHNIQUES — REFACTOR
Step 1: new representation
32. @drpicox
@drpicox
Programmers have to read the production code to
work out what the system does, rather than the tests.
When a test fails programmers usually have to
f
ix the
test, not production code.
When a test fails programmers have to step through
code in the debugger to work out what failed.
It takes a lot of effort to write a test, and after a few
tests, writing another test isn’t any easier.
Programmers have to carefully study the code of a
test to work out what it is actually testing.
Programmers have to carefully study the code of a
test to work out what it is not testing.
Tests are named after issue identi
f
iers in the
company's issue tracker. Bonus points if the issue
tracker no longer exists.
Test failures are ignored because the team expect
them to be transient or meaningless.
The test veri
f
ies that the library works, not the actual
code.
Tests do not express the intention of their feature.
SYMPTOMS
32
TDD TECHNIQUES — FRAGILE TESTS