SlideShare a Scribd company logo
06 Testing - TDD
Enginyeria del Software 3
1
@drpicox — 2020
Accounting
• Accounting is a discipline that is in charge of studying,
measuring and analyzing the assets and financial and
economic situation of a company or organization, in order
to facilitate decision-making within it and external control,
presenting the information , previously registered, in a
systematic and useful way for the different interested
parties.



—Wikipedia
2
Double-entry bookkeping
• At least two accounting entries are required to record
each financial transaction: debit and credit.
3
–Merriam-Webster dictionary
“Discipline: training that corrects, molds, or
perfects the mental faculties or moral character”
4
Test Development Driven
• The Three rules of TDD:

1. You are not allowed to write any production code unless it is
to make a failing unit test pass.

2. You are not allowed to write any more of a unit test than is
sufficient to fail; and compilation failures are failures.

3. You are not allowed to write any more production code than
is sufficient to pass the one failing unit test.
5
http://www.butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd
Stages of TDD
6
RED
GREEN
REFACTOR
TEST CODE
CLEAN
Write production code
until tests pass.
Write tests
until a test fails.
Clean code while tests passes.
Clean tests while tests passes.
Clean ...
...
YOU
START
HERE
30s
loop
Common Pitfalls (1/2)
• Typical individual mistakes include:

• forgetting to run tests frequently,

• writing too many tests at once,

• writing tests that are too large or coarse-grained,

• writing overly trivial tests, for instance omitting assertions,

• writing tests for trivial code, for instance accessors.
7
Common Pitfalls (2/2)
• Typical team mistakes include:

• partial adoption: only a few developers on the team use TDD,

• poor maintenance of the test suite: most commonly leading to a
test suite with a prohibitively long running time,

• abandoned test suite (i.e. seldom or never run): sometimes as a
result of poor maintenance, sometimes as a result of team
turnover.
8
London vs Chicago TDD
• London School of TDD: Mockists

• use spies to verify implementation

• higher coupling

• Chicago School of TDD: Statists

• depend on results, not on imlpementation details

• our objective
9
London Example
import { add } from "math"
import multiply from "./multiply"
let log
function addMock(a, b) {
log.push(`${a}+${b}`)
return add(a, b)
}
test("multiplies two numbers", () => {
log = []
const result = multiply(3, 4, { add: addMock })
expect(log).toEqual("0+3", "3+3", "6+3", "9+3")
expect(result).toEqual(12)
})
10
Chicago Example
import multiply from "./multiply"
test("multiplies two numbers", () => {
const result = multiply(3, 4)
expect(result).toEqual(12)
})
11
Craftsman Recipe
• You already know how to create tests and do TDD

• Probably you already do some kind of TDD manually

• You just need to write it first

• There are 5 steps to help you
12
Step 1: First tests
• Write first the test that gives you the excuse for writing
the first code.
13
test("create a game", () => {
const game = new Game()
})
class Game {}
Step 2: Progressing
• You already test manually things before and after code

• Write the test code that test that for you
14
Step 2: Progressing
// 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()
})
15
Step 2: Progressing
// 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")
})
16
Step 2: Progressing
// 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()
})
17
Step 2: Progressing
// 4: I want to know that clicking in search triggers fetch
test("The search button makes a fetch from the service",
async () => {
const searchBox = getByPlaceholderText(
container,
"search"
)
userEvent.type(searchBox, "apollo x")
const searchButton = getByText(container, "search")
searchButton.click()
await fetchMock.expectGET("/api/v1/search?q=apollo%20x")
})
18
Step 2: Progressing
// 5: I want to know if there is the response area
test("There is a response area after doing a search", 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).toBeInTheDocument()
})
19
Step 2: Progressing
// 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"
)
})
20
Step 3: Remove UI use
• Make test express your intention, not how to use the UI

• Protect your tests from the UI changes
21
Step 3: Remove UI use
• Make test express your intention, not how to use the UI

• Protect your tests from the UI changes
22
Step 3: Remove UI use
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"
)
})
23
Step 3: Remove UI use
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"
)
})
24
Step 3: Remove UI use
// __helpers__/ui.js
export function search(container, text) {
const searchBox = getByPlaceholderText(
container,
"search"
)
userEvent.type(searchBox, text)
const searchButton = getByText(container, "search")
searchButton.click()
}
export async function findSearchResult(container) {
const results = await findByTestId(
container,
"search-result"
)
return results
}
25
Step 4: Refactor
• Make test and code easy to read and understand
26
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"
)
})
Step 5: Business value
• All tests should directly express requirements
27
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"
)
})
Bonus Step: Use tables
• Refactor your test to accommodate tables
28
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)
}
)
Refactors
• Big Refactors

• Small Refactors
29
Big Refactors
• Programmers always want to throw
away the code and start over

• They think the old code is a mess

• They are probably wrong.
30
https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/
by Joel Spolsky
It’s harder to read code than to write it.
Small Refactors
• Leverage on Tests: small change, test, small change, test, ...

• Clean the code and adjust the architecture, slowly and firmly
31
REFACTOR
CLEAN
Small Refactors
• 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. Moddify 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
32
Example (BGK)
export default class Game {
_score = 0;
roll(pins) {
this._score += pins;
}
score() {
return this._score;
}
}
33
Example (BGK)
export default class Game {
_score = 0;
_rolls = [];
roll(pins) {
this._score += pins;
}
score() {
return this._score;
}
}
34
Step 1: new representation
Example (BGK)
export default class Game {
_score = 0;
_rolls = [];
roll(pins) {
this._score += pins;
this._rolls.push(pins);
}
score() {
return this._score;
}
}
35
Step 2: setters
Example (BGK)
export default class Game {
_score = 0;
_rolls = [];
roll(pins) {
this._score += pins;
this._rolls.push(pins);
}
score() {
let score = 0;
for (let i = 0; i < this._rolls.length; i++) {
score += this._rolls[i];
}
return score;
}
}
36
Step 4: getters
Example (BGK)
export default class Game {
_score = 0;
_rolls = [];
roll(pins) {
this._score += pins;
this._rolls.push(pins);
}
score() {
let score = 0;
for (let i = 0; i < this._rolls.length; i++) {
score += this._rolls[i];
}
return score;
}
}
37
Step 7: clean setters
Example (BGK)
export default class Game {
_score = 0;
_rolls = [];
roll(pins) {
this._rolls.push(pins);
}
score() {
let score = 0;
for (let i = 0; i < this._rolls.length; i++) {
score += this._rolls[i];
}
return score;
}
}
38
Step 8: clean old represent.
Example (BGK)
export default class Game {
_score = 0;
roll(pins) {
this._rolls.push(pins);
}
score() {
let score = 0;
for (let i = 0; i < this._rolls.length; i++) {
score += this._rolls[i];
}
return score;
}
}
39
Done
Example (Pattern)
// Painter should draw a square
class Painter {
draw() {
drawSquare()
}
}
40
Example (Pattern)
// The square size may change
class Painter {
draw(size: number) {
drawSquare(size)
}
}
41
Example (Pattern)
// The painter can draw also circles with size
class Painter {
draw(shape: String, size: number) {
if (shape === "Circle") drawCircle(size)
else drawSquare(size)
}
}
42
Example (Pattern)
// The painter can draw also stars
class Painter {
draw(shape: String, size: number) {
if (shape === "Star") drawStar(size)
else if (shape === "Circle") drawCircle(size)
else drawSquare(size)
}
}
43
This is not right, too complex if/switch
Example (Pattern)
// The painter can draw also circles with size
class Painter {
draw(shape: String, size: number) {
if (shape === "circle") drawCircle(size)
else drawSquare(size)
}
}
44
Example (Pattern)
interface Shape {
draw(size: number);
}
class Painter {
draw(shape: String, size: number) {
if (shape === "circle") drawCircle(size)
else drawSquare(size)
}
}
45
Example (Pattern)
interface Shape { ... }
class Circle implements Shape {
draw(size: number) {
drawCircle(size)
}
}
class Painter {
draw(shape: String, size: number) {
if (shape === "circle") drawCircle(size)
else drawSquare(size)
}
}
46
Example (Pattern)
interface Shape { ... }
class Circle implements Shape {
draw(size: number) {
drawCircle(size)
}
}
class Painter {
draw(shape: String, size: number) {
if (shape === "circle") new Circle.draw(size)
else drawSquare(size)
}
}
47
Example (Pattern)
interface Shape { ... }
class Circle implements Shape { ... }
class Square implements Shape {
draw(size: number) {
drawSquare(size)
}
}
class Painter {
draw(shape: String, size: number) {
if (shape === "circle") new Circle.draw(size)
else drawSquare(size)
}
}
48
Example (Pattern)
interface Shape { ... }
class Circle implements Shape { ... }
class Square implements Shape {
draw(size: number) {
drawSquare(size)
}
}
class Painter {
draw(shape: String, size: number) {
if (shape === "circle") new Circle.draw(size)
else new Square.draw(size)
}
}
49
Example (Pattern)
interface Shape { ... }
class Circle implements Shape { ... }
class Square implements Shape { ... }
const shapes = {
circle: new Circle(),
square: new Square(),
};
class Painter {
draw(shape: String, size: number) {
if (shape === "circle") new Circle.draw(size)
else new Circle.draw(size)
}
}
50
Example (Pattern)
interface Shape { ... }
class Circle implements Shape { ... }
class Square implements Shape { ... }
const shapes = { ... };
class Painter {
draw(shape: String, size: number) {
shapes[shape].draw(size)
}
}
51
Example (Pattern)
interface Shape { ... }
class Circle implements Shape { ... }
class Square implements Shape { ... }
const shapes = { ... };
class Painter {
draw(shape: String = "square", size: number) {
shapes[shape].draw(size)
}
}
52
Example (Pattern)
interface Shape { ... }
class Circle implements Shape { ... }
class Square implements Shape { ... }
class Star implements Shape {
draw(size) {}
}
const shapes = { ... };
class Painter {
draw(shape: String = "square", size: number) {
shapes[shape].draw(size);
}
}
53
Example (Pattern)
interface Shape { ... }
class Circle implements Shape { ... }
class Square implements Shape { ... }
class Star implements Shape {
draw(size) {}
}
const shapes = {
circle: new Circle(),
square: new Square(),
star: new Star(),
};
class Painter {
draw(shape: String = "square", size: number) {
shapes[shape].draw(size);
}
}
54
Example (Pattern)
interface Shape { ... }
class Circle implements Shape { ... }
class Square implements Shape { ... }
class Star implements Shape {
draw(size) {
drawStar(size)
}
}
const shapes = { ... };
class Painter {
draw(shape: String = "square", size: number) {
shapes[shape].draw(size);
}
}
55
Small Refactors
• Step by Step

• Always Green
56
Homework
• https://www.agilealliance.org/glossary/acceptance 

• https://www.agilealliance.org/glossary/unit-test 

• https://wiki.c2.com/?ArrangeActAssert 

• http://agileinaflash.blogspot.com/2009/02/first.html
57
The Two Disks Parable
58
TESTS CODE

More Related Content

PDF
ES3-2020-07 Testing techniques
PDF
TDD CrashCourse Part4: Improving Testing
KEY
Inside PyMongo - MongoNYC
PDF
TDD CrashCourse Part5: Testing Techniques
PDF
Introduction to web programming for java and c# programmers by @drpicox
PDF
ReactJS for Programmers
PDF
ES3-2020-P3 TDD Calculator
PDF
Pyconie 2012
ES3-2020-07 Testing techniques
TDD CrashCourse Part4: Improving Testing
Inside PyMongo - MongoNYC
TDD CrashCourse Part5: Testing Techniques
Introduction to web programming for java and c# programmers by @drpicox
ReactJS for Programmers
ES3-2020-P3 TDD Calculator
Pyconie 2012

What's hot (20)

PDF
JS and patterns
PDF
Redux for ReactJS Programmers
PDF
Testing, Learning and Professionalism — 20171214
PPT
2012 JDays Bad Tests Good Tests
PDF
33rd Degree 2013, Bad Tests, Good Tests
PPTX
The secret unit testing tools no one ever told you about
PDF
GMock framework
DOCX
My java file
PDF
Agile Android
PDF
UA testing with Selenium and PHPUnit - PFCongres 2013
PDF
Workshop quality assurance for php projects - ZendCon 2013
PPTX
Ensure code quality with vs2012
PDF
Developer Test - Things to Know
DOCX
201913046 wahyu septiansyah network programing
PPTX
We Make Debugging Sucks Less
PPTX
Introduction to Software Testing
PPTX
Unit testing without Robolectric, Droidcon Berlin 2016
PPT
Clojure in the Wild
PDF
Rechecking SharpDevelop: Any New Bugs?
JS and patterns
Redux for ReactJS Programmers
Testing, Learning and Professionalism — 20171214
2012 JDays Bad Tests Good Tests
33rd Degree 2013, Bad Tests, Good Tests
The secret unit testing tools no one ever told you about
GMock framework
My java file
Agile Android
UA testing with Selenium and PHPUnit - PFCongres 2013
Workshop quality assurance for php projects - ZendCon 2013
Ensure code quality with vs2012
Developer Test - Things to Know
201913046 wahyu septiansyah network programing
We Make Debugging Sucks Less
Introduction to Software Testing
Unit testing without Robolectric, Droidcon Berlin 2016
Clojure in the Wild
Rechecking SharpDevelop: Any New Bugs?
Ad

Similar to ES3-2020-06 Test Driven Development (TDD) (20)

PDF
TDD CrashCourse Part3: TDD Techniques
PDF
Intro To JavaScript Unit Testing - Ran Mizrahi
PDF
Intro to JavaScript Testing
PDF
An Introduction to the World of Testing for Front-End Developers
PDF
FITC Web Unleashed 2017 - Introduction to the World of Testing for Front-End ...
PDF
Don't let your tests slow you down
PDF
Tdd.eng.ver
PDF
Test-driven Development (TDD)
PDF
Reliable Javascript
PDF
Vladyslav Romanchenko "How to keep high code quality without e2e tests"
PDF
Testing for software engineers
PDF
Automated Testing in Angular Slides
PPTX
REST API-LEVEL TDD With Nodejs
PPTX
REST API level TDD with NodeJS
PDF
Testing in FrontEnd World by Nikita Galkin
ODP
Effective TDD - Less is more
PPT
XP through TDD
ODP
Writing useful automated tests for the single page applications you build
PDF
Никита Галкин "Testing in Frontend World"
PDF
Test-Driven Development
TDD CrashCourse Part3: TDD Techniques
Intro To JavaScript Unit Testing - Ran Mizrahi
Intro to JavaScript Testing
An Introduction to the World of Testing for Front-End Developers
FITC Web Unleashed 2017 - Introduction to the World of Testing for Front-End ...
Don't let your tests slow you down
Tdd.eng.ver
Test-driven Development (TDD)
Reliable Javascript
Vladyslav Romanchenko "How to keep high code quality without e2e tests"
Testing for software engineers
Automated Testing in Angular Slides
REST API-LEVEL TDD With Nodejs
REST API level TDD with NodeJS
Testing in FrontEnd World by Nikita Galkin
Effective TDD - Less is more
XP through TDD
Writing useful automated tests for the single page applications you build
Никита Галкин "Testing in Frontend World"
Test-Driven Development
Ad

More from David Rodenas (16)

PDF
TDD CrashCourse Part2: TDD
PDF
TDD CrashCourse Part1: Testing
PDF
Be professional: We Rule the World
PDF
ES3-2020-P2 Bowling Game Kata
PDF
ES3-2020-05 Testing
PDF
Vespres
PDF
Faster web pages
PDF
Basic Tutorial of React for Programmers
PPTX
From high school to university and work
PDF
Modules in angular 2.0 beta.1
PDF
Freelance i Enginyeria
PDF
Angular 1.X Community and API Decissions
PDF
MVS: An angular MVC
PDF
Mvc - Model: the great forgotten
PDF
(automatic) Testing: from business to university and back
PDF
Testing: ¿what, how, why?
TDD CrashCourse Part2: TDD
TDD CrashCourse Part1: Testing
Be professional: We Rule the World
ES3-2020-P2 Bowling Game Kata
ES3-2020-05 Testing
Vespres
Faster web pages
Basic Tutorial of React for Programmers
From high school to university and work
Modules in angular 2.0 beta.1
Freelance i Enginyeria
Angular 1.X Community and API Decissions
MVS: An angular MVC
Mvc - Model: the great forgotten
(automatic) Testing: from business to university and back
Testing: ¿what, how, why?

Recently uploaded (20)

PPTX
bas. eng. economics group 4 presentation 1.pptx
DOCX
573137875-Attendance-Management-System-original
PPTX
Internet of Things (IOT) - A guide to understanding
PDF
Automation-in-Manufacturing-Chapter-Introduction.pdf
PDF
Mohammad Mahdi Farshadian CV - Prospective PhD Student 2026
PPT
Project quality management in manufacturing
PPTX
MCN 401 KTU-2019-PPE KITS-MODULE 2.pptx
PDF
The CXO Playbook 2025 – Future-Ready Strategies for C-Suite Leaders Cerebrai...
PPTX
additive manufacturing of ss316l using mig welding
PDF
Well-logging-methods_new................
PPTX
M Tech Sem 1 Civil Engineering Environmental Sciences.pptx
PPTX
OOP with Java - Java Introduction (Basics)
PPTX
UNIT-1 - COAL BASED THERMAL POWER PLANTS
PPTX
Sustainable Sites - Green Building Construction
PDF
Digital Logic Computer Design lecture notes
PPTX
CYBER-CRIMES AND SECURITY A guide to understanding
PDF
Enhancing Cyber Defense Against Zero-Day Attacks using Ensemble Neural Networks
PPTX
IOT PPTs Week 10 Lecture Material.pptx of NPTEL Smart Cities contd
PPTX
Lecture Notes Electrical Wiring System Components
PDF
TFEC-4-2020-Design-Guide-for-Timber-Roof-Trusses.pdf
bas. eng. economics group 4 presentation 1.pptx
573137875-Attendance-Management-System-original
Internet of Things (IOT) - A guide to understanding
Automation-in-Manufacturing-Chapter-Introduction.pdf
Mohammad Mahdi Farshadian CV - Prospective PhD Student 2026
Project quality management in manufacturing
MCN 401 KTU-2019-PPE KITS-MODULE 2.pptx
The CXO Playbook 2025 – Future-Ready Strategies for C-Suite Leaders Cerebrai...
additive manufacturing of ss316l using mig welding
Well-logging-methods_new................
M Tech Sem 1 Civil Engineering Environmental Sciences.pptx
OOP with Java - Java Introduction (Basics)
UNIT-1 - COAL BASED THERMAL POWER PLANTS
Sustainable Sites - Green Building Construction
Digital Logic Computer Design lecture notes
CYBER-CRIMES AND SECURITY A guide to understanding
Enhancing Cyber Defense Against Zero-Day Attacks using Ensemble Neural Networks
IOT PPTs Week 10 Lecture Material.pptx of NPTEL Smart Cities contd
Lecture Notes Electrical Wiring System Components
TFEC-4-2020-Design-Guide-for-Timber-Roof-Trusses.pdf

ES3-2020-06 Test Driven Development (TDD)

  • 1. 06 Testing - TDD Enginyeria del Software 3 1 @drpicox — 2020
  • 2. Accounting • Accounting is a discipline that is in charge of studying, measuring and analyzing the assets and financial and economic situation of a company or organization, in order to facilitate decision-making within it and external control, presenting the information , previously registered, in a systematic and useful way for the different interested parties.
 
 —Wikipedia 2
  • 3. Double-entry bookkeping • At least two accounting entries are required to record each financial transaction: debit and credit. 3
  • 4. –Merriam-Webster dictionary “Discipline: training that corrects, molds, or perfects the mental faculties or moral character” 4
  • 5. Test Development Driven • The Three rules of TDD:
 1. You are not allowed to write any production code unless it is to make a failing unit test pass.
 2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
 3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test. 5 http://www.butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd
  • 6. Stages of TDD 6 RED GREEN REFACTOR TEST CODE CLEAN Write production code until tests pass. Write tests until a test fails. Clean code while tests passes. Clean tests while tests passes. Clean ... ... YOU START HERE 30s loop
  • 7. Common Pitfalls (1/2) • Typical individual mistakes include: • forgetting to run tests frequently, • writing too many tests at once, • writing tests that are too large or coarse-grained, • writing overly trivial tests, for instance omitting assertions, • writing tests for trivial code, for instance accessors. 7
  • 8. Common Pitfalls (2/2) • Typical team mistakes include: • partial adoption: only a few developers on the team use TDD, • poor maintenance of the test suite: most commonly leading to a test suite with a prohibitively long running time, • abandoned test suite (i.e. seldom or never run): sometimes as a result of poor maintenance, sometimes as a result of team turnover. 8
  • 9. London vs Chicago TDD • London School of TDD: Mockists • use spies to verify implementation • higher coupling • Chicago School of TDD: Statists • depend on results, not on imlpementation details • our objective 9
  • 10. London Example import { add } from "math" import multiply from "./multiply" let log function addMock(a, b) { log.push(`${a}+${b}`) return add(a, b) } test("multiplies two numbers", () => { log = [] const result = multiply(3, 4, { add: addMock }) expect(log).toEqual("0+3", "3+3", "6+3", "9+3") expect(result).toEqual(12) }) 10
  • 11. Chicago Example import multiply from "./multiply" test("multiplies two numbers", () => { const result = multiply(3, 4) expect(result).toEqual(12) }) 11
  • 12. Craftsman Recipe • You already know how to create tests and do TDD • Probably you already do some kind of TDD manually • You just need to write it first • There are 5 steps to help you 12
  • 13. Step 1: First tests • Write first the test that gives you the excuse for writing the first code. 13 test("create a game", () => { const game = new Game() }) class Game {}
  • 14. Step 2: Progressing • You already test manually things before and after code • Write the test code that test that for you 14
  • 15. Step 2: Progressing // 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() }) 15
  • 16. Step 2: Progressing // 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") }) 16
  • 17. Step 2: Progressing // 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() }) 17
  • 18. Step 2: Progressing // 4: I want to know that clicking in search triggers fetch test("The search button makes a fetch from the service", async () => { const searchBox = getByPlaceholderText( container, "search" ) userEvent.type(searchBox, "apollo x") const searchButton = getByText(container, "search") searchButton.click() await fetchMock.expectGET("/api/v1/search?q=apollo%20x") }) 18
  • 19. Step 2: Progressing // 5: I want to know if there is the response area test("There is a response area after doing a search", 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).toBeInTheDocument() }) 19
  • 20. Step 2: Progressing // 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" ) }) 20
  • 21. Step 3: Remove UI use • Make test express your intention, not how to use the UI • Protect your tests from the UI changes 21
  • 22. Step 3: Remove UI use • Make test express your intention, not how to use the UI • Protect your tests from the UI changes 22
  • 23. Step 3: Remove UI use 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" ) }) 23
  • 24. Step 3: Remove UI use 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" ) }) 24
  • 25. Step 3: Remove UI use // __helpers__/ui.js export function search(container, text) { const searchBox = getByPlaceholderText( container, "search" ) userEvent.type(searchBox, text) const searchButton = getByText(container, "search") searchButton.click() } export async function findSearchResult(container) { const results = await findByTestId( container, "search-result" ) return results } 25
  • 26. Step 4: Refactor • Make test and code easy to read and understand 26 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" ) })
  • 27. Step 5: Business value • All tests should directly express requirements 27 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" ) })
  • 28. Bonus Step: Use tables • Refactor your test to accommodate tables 28 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) } )
  • 29. Refactors • Big Refactors • Small Refactors 29
  • 30. Big Refactors • Programmers always want to throw away the code and start over • They think the old code is a mess • They are probably wrong. 30 https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/ by Joel Spolsky It’s harder to read code than to write it.
  • 31. Small Refactors • Leverage on Tests: small change, test, small change, test, ... • Clean the code and adjust the architecture, slowly and firmly 31 REFACTOR CLEAN
  • 32. Small Refactors • 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. Moddify 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 32
  • 33. Example (BGK) export default class Game { _score = 0; roll(pins) { this._score += pins; } score() { return this._score; } } 33
  • 34. Example (BGK) export default class Game { _score = 0; _rolls = []; roll(pins) { this._score += pins; } score() { return this._score; } } 34 Step 1: new representation
  • 35. Example (BGK) export default class Game { _score = 0; _rolls = []; roll(pins) { this._score += pins; this._rolls.push(pins); } score() { return this._score; } } 35 Step 2: setters
  • 36. Example (BGK) export default class Game { _score = 0; _rolls = []; roll(pins) { this._score += pins; this._rolls.push(pins); } score() { let score = 0; for (let i = 0; i < this._rolls.length; i++) { score += this._rolls[i]; } return score; } } 36 Step 4: getters
  • 37. Example (BGK) export default class Game { _score = 0; _rolls = []; roll(pins) { this._score += pins; this._rolls.push(pins); } score() { let score = 0; for (let i = 0; i < this._rolls.length; i++) { score += this._rolls[i]; } return score; } } 37 Step 7: clean setters
  • 38. Example (BGK) export default class Game { _score = 0; _rolls = []; roll(pins) { this._rolls.push(pins); } score() { let score = 0; for (let i = 0; i < this._rolls.length; i++) { score += this._rolls[i]; } return score; } } 38 Step 8: clean old represent.
  • 39. Example (BGK) export default class Game { _score = 0; roll(pins) { this._rolls.push(pins); } score() { let score = 0; for (let i = 0; i < this._rolls.length; i++) { score += this._rolls[i]; } return score; } } 39 Done
  • 40. Example (Pattern) // Painter should draw a square class Painter { draw() { drawSquare() } } 40
  • 41. Example (Pattern) // The square size may change class Painter { draw(size: number) { drawSquare(size) } } 41
  • 42. Example (Pattern) // The painter can draw also circles with size class Painter { draw(shape: String, size: number) { if (shape === "Circle") drawCircle(size) else drawSquare(size) } } 42
  • 43. Example (Pattern) // The painter can draw also stars class Painter { draw(shape: String, size: number) { if (shape === "Star") drawStar(size) else if (shape === "Circle") drawCircle(size) else drawSquare(size) } } 43 This is not right, too complex if/switch
  • 44. Example (Pattern) // The painter can draw also circles with size class Painter { draw(shape: String, size: number) { if (shape === "circle") drawCircle(size) else drawSquare(size) } } 44
  • 45. Example (Pattern) interface Shape { draw(size: number); } class Painter { draw(shape: String, size: number) { if (shape === "circle") drawCircle(size) else drawSquare(size) } } 45
  • 46. Example (Pattern) interface Shape { ... } class Circle implements Shape { draw(size: number) { drawCircle(size) } } class Painter { draw(shape: String, size: number) { if (shape === "circle") drawCircle(size) else drawSquare(size) } } 46
  • 47. Example (Pattern) interface Shape { ... } class Circle implements Shape { draw(size: number) { drawCircle(size) } } class Painter { draw(shape: String, size: number) { if (shape === "circle") new Circle.draw(size) else drawSquare(size) } } 47
  • 48. Example (Pattern) interface Shape { ... } class Circle implements Shape { ... } class Square implements Shape { draw(size: number) { drawSquare(size) } } class Painter { draw(shape: String, size: number) { if (shape === "circle") new Circle.draw(size) else drawSquare(size) } } 48
  • 49. Example (Pattern) interface Shape { ... } class Circle implements Shape { ... } class Square implements Shape { draw(size: number) { drawSquare(size) } } class Painter { draw(shape: String, size: number) { if (shape === "circle") new Circle.draw(size) else new Square.draw(size) } } 49
  • 50. Example (Pattern) interface Shape { ... } class Circle implements Shape { ... } class Square implements Shape { ... } const shapes = { circle: new Circle(), square: new Square(), }; class Painter { draw(shape: String, size: number) { if (shape === "circle") new Circle.draw(size) else new Circle.draw(size) } } 50
  • 51. Example (Pattern) interface Shape { ... } class Circle implements Shape { ... } class Square implements Shape { ... } const shapes = { ... }; class Painter { draw(shape: String, size: number) { shapes[shape].draw(size) } } 51
  • 52. Example (Pattern) interface Shape { ... } class Circle implements Shape { ... } class Square implements Shape { ... } const shapes = { ... }; class Painter { draw(shape: String = "square", size: number) { shapes[shape].draw(size) } } 52
  • 53. Example (Pattern) interface Shape { ... } class Circle implements Shape { ... } class Square implements Shape { ... } class Star implements Shape { draw(size) {} } const shapes = { ... }; class Painter { draw(shape: String = "square", size: number) { shapes[shape].draw(size); } } 53
  • 54. Example (Pattern) interface Shape { ... } class Circle implements Shape { ... } class Square implements Shape { ... } class Star implements Shape { draw(size) {} } const shapes = { circle: new Circle(), square: new Square(), star: new Star(), }; class Painter { draw(shape: String = "square", size: number) { shapes[shape].draw(size); } } 54
  • 55. Example (Pattern) interface Shape { ... } class Circle implements Shape { ... } class Square implements Shape { ... } class Star implements Shape { draw(size) { drawStar(size) } } const shapes = { ... }; class Painter { draw(shape: String = "square", size: number) { shapes[shape].draw(size); } } 55
  • 56. Small Refactors • Step by Step • Always Green 56
  • 57. Homework • https://www.agilealliance.org/glossary/acceptance • https://www.agilealliance.org/glossary/unit-test • https://wiki.c2.com/?ArrangeActAssert • http://agileinaflash.blogspot.com/2009/02/first.html 57
  • 58. The Two Disks Parable 58 TESTS CODE