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

ES3-2020-06 Test Driven Development (TDD)

  • 1.
    06 Testing -TDD Enginyeria del Software 3 1 @drpicox — 2020
  • 2.
    Accounting • Accounting isa 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 • Atleast two accounting entries are required to record each financial transaction: debit and credit. 3
  • 4.
    –Merriam-Webster dictionary “Discipline: trainingthat 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 TESTCODE 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 ChicagoTDD • 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 multiplyfrom "./multiply" test("multiplies two numbers", () => { const result = multiply(3, 4) expect(result).toEqual(12) }) 11
  • 12.
    Craftsman Recipe • Youalready 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: Firsttests • 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: RemoveUI use • Make test express your intention, not how to use the UI • Protect your tests from the UI changes 21
  • 22.
    Step 3: RemoveUI use • Make test express your intention, not how to use the UI • Protect your tests from the UI changes 22
  • 23.
    Step 3: RemoveUI 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: RemoveUI 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: RemoveUI 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: Businessvalue • 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: Usetables • 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.
  • 30.
    Big Refactors • Programmersalways 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 • Leverageon Tests: small change, test, small change, test, ... • Clean the code and adjust the architecture, slowly and firmly 31 REFACTOR CLEAN
  • 32.
    Small Refactors • Finda 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 defaultclass Game { _score = 0; roll(pins) { this._score += pins; } score() { return this._score; } } 33
  • 34.
    Example (BGK) export defaultclass Game { _score = 0; _rolls = []; roll(pins) { this._score += pins; } score() { return this._score; } } 34 Step 1: new representation
  • 35.
    Example (BGK) export defaultclass 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 defaultclass 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 defaultclass 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 defaultclass 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 defaultclass 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) // Paintershould draw a square class Painter { draw() { drawSquare() } } 40
  • 41.
    Example (Pattern) // Thesquare size may change class Painter { draw(size: number) { drawSquare(size) } } 41
  • 42.
    Example (Pattern) // Thepainter 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) // Thepainter 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) // Thepainter 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 • Stepby 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 DisksParable 58 TESTS CODE