Transcript
Page 1: Essential Test-Driven Development

9/9/13  

1  

As you enter the room…

1.  Take a 3x5 card or piece of paper, create two columns.

2.  Write these skills in the first column: TDD, Refactoring,

OO, Java (or C#), Eclipse (or IDEA or VisualStudio)

3.  For each skill, rate yourself from 0 (never heard of it) to

10 (invented it), and record in the second column.

4.  Find someone (a) whom you don’t usually work with; and

(b) who has somewhat complementary skill levels (with

them, your average is 4 to 6). Write down this person’s

name, but only if both a and b are true.

5.  Repeat step 4 until you have two names.

9 September 2013 © Agile Institute 2008-2013 1

…get comfortable.

1.  Choose someone from

your list to work with.

2.  Gather your belongings

and select a workstation

for you and your new

lab partner.

3.  Settle in at that

workstation.

9 September 2013 © Agile Institute 2008-2013 2

Page 2: Essential Test-Driven Development

9/9/13  

2  

9 September 2013 © Agile Institute 2008-2013 3

Essential Test-Driven Development

Rob Myers for

Agile Development Practices East

12 November 2013

Café

9 September 2013 © Agile Institute 2008-2013 4

Unit testing is soooo

DEPRESSING

Page 3: Essential Test-Driven Development

9/9/13  

3  

9 September 2013 © Agile Institute 2008-2013 5

TDD is fun, and provides

much more than just unit-tests!

9 September 2013 © Agile Institute 2008-2013 6

“The results of the case studies indicate

that the pre-release defect density of the

four products decreased between 40%

and 90% relative to similar projects that

d i d n o t u s e t h e T D D p r a c t i c e .

Subjectively, the teams experienced a

15–35% increase in initial development

time after adopting TDD.” http://research.microsoft.com/en-us/projects/esm/nagappan_tdd.pdf, Nagappan et al,

© Springer Science + Business Media, LLC 2008

Page 4: Essential Test-Driven Development

9/9/13  

4  

9 September 2013 © Agile Institute 2008-2013 7

TDD Demo

No Magic

9 September 2013 © Agile Institute 2008-2013 8

import org.junit.*;

public class FooTests {

@Test

public void fooBarIsBaz() {

Foo foo = new Foo();

Assert.assertEquals("Baz", foo.bar()); }

}

Page 5: Essential Test-Driven Development

9/9/13  

5  

Requirements & Conventions

9 September 2013 © Agile Institute 2008-2013 9

import org.junit.*;

public class FooTests {

@Test

public void fooBarIsBaz() {

Foo foo = new Foo();

Assert.assertEquals("Baz", foo.bar()); }

}

Expected Actual

9 September 2013 © Agile Institute 2008-2013 10

Bar

+ void AbstractMethod( object parameter)

Baz

- void PrivateMethod()

Foo

- int privateVariable

+ int PublicMethod()

Basic UML Class Diagrams

Page 6: Essential Test-Driven Development

9/9/13  

6  

9 September 2013 © Agile Institute 2008-2013 11

Global Currency Money-Market Account

9 September 2013 © Agile Institute 2008-2013 12

Global ATM

Access

Page 7: Essential Test-Driven Development

9/9/13  

7  

Stories

•  As Rob the US account holder, I want to be able to

withdraw USD from a US ATM, but select which currency

holdings to withdraw from.

•  As Rob, traveling in Europe, I want to be able to withdraw

EUR from either my EUR holdings or my USD holdings. The

default should be the most beneficial to me at the time.

•  Shortly after the end of each month, I want to receive a

report of my holdings and balance. The total balance

should appear in the currency of my account address

(USD).

9 September 2013 © Agile Institute 2008-2013 13

Primary Objects

9 September 2013 © Agile Institute 2008-2013 14

Account

Currency

Holding

Page 8: Essential Test-Driven Development

9/9/13  

8  

More Detail

9 September 2013 © Agile Institute 2008-2013 15

CurrencyConverter

Currency

Holding

value currency currencyConverter

convert value to another currency

Start Simple

To Do q When currencies are the same. q When value is zero.

9 September 2013 © Agile Institute 2008-2013 16

Page 9: Essential Test-Driven Development

9/9/13  

9  

Specification, and Interface

9 September 2013 © Agile Institute 2008-2013 17

@Test public void givesSameValueWhenSameCurrencyRequested() {

CurrencyConverter converter = new CurrencyConverter();

String sameCurrency = "USD";

double sameValue = 100.0000;

double converted = converter.convert(

sameValue, sameCurrency, sameCurrency);

Assert.assertEquals(sameValue, converted, 0.00004);

}

Will not compile. L

“Thank You, But…”

9 September 2013 © Agile Institute 2008-2013 18

public class CurrencyConverter { public double convert(

double value, String from, String to) {

throw new RuntimeException(

"D'oh! convert() not YET implemented!");

}

}

Page 10: Essential Test-Driven Development

9/9/13  

10  

“…I Want to See it Fail Successfully”

9 September 2013 © Agile Institute 2008-2013 19

public class CurrencyConverter { public double convert(

double value, String from, String to) {

return 0.0; }

}

Technique: “Fake It”

9 September 2013 © Agile Institute 2008-2013 20

public class CurrencyConverter { public double convert(

double value, String from, String to) {

return 100.0; }

}

Page 11: Essential Test-Driven Development

9/9/13  

11  

a. Refactor Away the Duplication

9 September 2013 © Agile Institute 2008-2013 21

@Test public void givesSameValueWhenSameCurrencyRequested() {

CurrencyConverter converter = new CurrencyConverter();

String sameCurrency = "USD";

double sameValue = 100.0000;

double converted = converter.convert(

sameValue, sameCurrency, sameCurrency);

Assert.assertEquals(sameValue, converted, 0.00004);

}

public double convert( double value, String from, String to) {

return 100.0;

}

b. Triangulate

9 September 2013 © Agile Institute 2008-2013 22

Page 12: Essential Test-Driven Development

9/9/13  

12  

9 September 2013 © Agile Institute 2008-2013 23

Rodin’s The Thinker, photo by CJ on Wikipedia

We don’t add any behavior

without a failing test…

Technique: “Triangulation”

9 September 2013 © Agile Institute 2008-2013 24

@Test public void givesZeroWhenValueIsZero() {

CurrencyConverter converter = new CurrencyConverter();

double zero = 0.0000;

double converted = converter.convert(

zero, "USD", "EUR");

Assert.assertEquals(zero, converted, 0.00004);

}

Page 13: Essential Test-Driven Development

9/9/13  

13  

Still “Fake”?

9 September 2013 © Agile Institute 2008-2013 25

public class CurrencyConverter { public double convert(

double value, String from, String to) {

return value; }

}

Maintain the Tests

9 September 2013 © Agile Institute 2008-2013 26

@Test public void givesZeroWhenValueIsZero() {

CurrencyConverter converter = new CurrencyConverter();

// ...

}

@Test

public void givesSameValueWhenSameCurrencyRequested() {

CurrencyConverter converter = new CurrencyConverter(); // ...

}

Page 14: Essential Test-Driven Development

9/9/13  

14  

@Before Runs Before Each Test

9 September 2013 © Agile Institute 2008-2013 27

private CurrencyConverter converter; @Before public void initialize() { converter = new CurrencyConverter(); }

@Test

public void givesZeroWhenValueIsZero() { // ...

}

@Test

public void givesSameValueWhenSameCurrencyRequested() {

// ...

}

What is the Expected Answer?

9 September 2013 © Agile Institute 2008-2013 28

@Test public void convertsDollarsToEuros() {

double converted = converter.convert(

100.0000, "USD", "EUR");

Assert.assertEquals(???, converted, 0.00004);

}

Page 15: Essential Test-Driven Development

9/9/13  

15  

Let’s Invent a Scenario…

9 September 2013 © Agile Institute 2008-2013 29

@Test public void convertsDollarsToEuros() {

double converted = converter.convert(

100.0000, "USD", "EUR");

Assert.assertEquals(50.0000, converted, 0.00004); }

…“Fake It”…

9 September 2013 © Agile Institute 2008-2013 30

public double convert( double value, String from, String to) {

if (to.equals(from)) return value; return value * 0.5000; }

Page 16: Essential Test-Driven Development

9/9/13  

16  

…Refactor…

9 September 2013 © Agile Institute 2008-2013 31

public double convert( double value, String from, String to) {

if (to.Equals(from))

return value;

return value * conversionRate(from, to); }

private double conversionRate(String from, String to) { return 0.5000; }

…And Make a Note of It

To Do ü When currencies are the same. ü When value is zero. q Fix the hard-coded conversionRate

9 September 2013 © Agile Institute 2008-2013 32

Page 17: Essential Test-Driven Development

9/9/13  

17  

9 September 2013 © Agile Institute 2008-2013 33

Where does it come from?

Well, What is It???

Circumstantial Coupling?

9 September 2013 © Agile Institute 2008-2013 34

CurrencyConverter

Page 18: Essential Test-Driven Development

9/9/13  

18  

Managing the Transaction

9 September 2013 © Agile Institute 2008-2013 35

CurrencyConverter

ConversionRates

Account

Holding

Holding

Holding

Holding

Another Object Emerges

To Do

ü When currencies are the same.

ü When value is zero.

q Fix hard-coded conversionRate().

q Write ConversionRates.

q Make a factory for ConversionRates that uses the FOREX.com Web Service.

9 September 2013 © Agile Institute 2008-2013 36

Page 19: Essential Test-Driven Development

9/9/13  

19  

import org.junit.Assert; import org.junit.Test;

public class ConversionRatesTest {

@Test

public void storesAndRetrievesRates() {

ConversionRates rates = new ConversionRates();

String from = "USD";

String to = "EUR"; double rate = 0.7424;

rates.putRate(from, to, rate);

Assert.assertEquals(rate, rates.getRate(from, to),

0.0004);

}

}

New Test Class

9 September 2013 © Agile Institute 2008-2013 37

Fail

9 September 2013 © Agile Institute 2008-2013 38

public class ConversionRates { public void putRate(String from, String to, double rate) { } public double getRate(String from, String to) { return 0; } }

Page 20: Essential Test-Driven Development

9/9/13  

20  

Technique: “Obvious Implementation”

9 September 2013 © Agile Institute 2008-2013 39

import java.util.HashMap; import java.util.Map;

public class ConversionRates {

private Map<String, Double> rates = new HashMap<String, Double>();

public void putRate(String from, String to, double rate) {

rates.put(from + to, rate); }

public double getRate(String from, String to) {

return rates.get(from + to); } }

Isolate Behavior

9 September 2013 © Agile Institute 2008-2013 40

public class ConversionRates { private Map<String, Double> rates

= new HashMap<String, Double>();

public void putRate(String from, String to, double rate) {

rates.put(key(from, to), rate); }

public double getRate(String from, String to) {

return rates.get(key(from, to)); }

private String key(String from, String to) { return from + to; } }

Page 21: Essential Test-Driven Development

9/9/13  

21  

Next?

To Do

ü When currencies are the same.

ü When value is zero.

q Fix hard-coded conversionRate().

ü Write ConversionRates.

q Make a factory for ConversionRates that uses the FOREX.com Web Service.

9 September 2013 © Agile Institute 2008-2013 41

Fix @Before Method

9 September 2013 © Agile Institute 2008-2013 42

public class CurrencyConverterTests { private CurrencyConverter converter;

@Before

public void initialize() { ConversionRates rates = new ConversionRates(); rates.putRate("USD", "EUR", 0.5000); converter = new CurrencyConverter(rates); }

// ...

Page 22: Essential Test-Driven Development

9/9/13  

22  

Partially Refactored…

9 September 2013 © Agile Institute 2008-2013 43

public class CurrencyConverter { private ConversionRates rates; public CurrencyConverter(ConversionRates rates) { this.rates = rates; }

private double conversionRate(String from, String to) {

return 0.5000;

}

// ...

Replace the Hard-Coded Value

9 September 2013 © Agile Institute 2008-2013 44

public class CurrencyConverter { private ConversionRates rates;

public CurrencyConverter(ConversionRates rates) {

this.rates = rates;

}

private double conversionRate(String from, String to) {

return rates.getRate(from, to); }

// ...

Page 23: Essential Test-Driven Development

9/9/13  

23  

What Remains?

To Do ü When currencies are the same. ü When value is zero. ü Fix hard-coded conversionRate(). ü Write ConversionRates. q Make a factory for ConversionRates

that uses the FOREX.com Web Service.

9 September 2013 © Agile Institute 2008-2013 45

What is the Missing Piece?

9 September 2013 © Agile Institute 2008-2013 46

ConversionRates rates =

ConversionRates.byAccountAnd???( account, ???);

Page 24: Essential Test-Driven Development

9/9/13  

24  

Ask What If…?

To Do q Make a byAccountAndDate() factory

for ConversionRates that uses the FOREX.com Web Service.

q ConversionRates returns 1/rate if inverse rate found.

q ConversionRates throws when rate not found.

9 September 2013 © Agile Institute 2008-2013 47

Testing Exceptional Behavior

9 September 2013 © Agile Institute 2008-2013 48

@Test(expected=RateNotFoundException.class) public void throwsExceptionIfRateNotFoundII() {

ConversionRates rates = new ConversionRates();

String from = "BAR";

String to = "BAZ";

rates.getRate(from, to);

}

Page 25: Essential Test-Driven Development

9/9/13  

25  

A New Exception

9 September 2013 © Agile Institute 2008-2013 49

public class RateNotFoundException extends RuntimeException { }

9 September 2013 © Agile Institute 2008-2013 50

Page 26: Essential Test-Driven Development

9/9/13  

26  

Back in ConversionRates

9 September 2013 © Agile Institute 2008-2013 51

public double getRate(String from, String to) { if (!rates.containsKey(key(from, to))) throw new RateNotFoundException(); return rates.get(key(from, to));

}

9 September 2013 © Agile Institute 2008-2013 52

1.  Write one unit test.

2.  Build or add to the object under test until everything compiles.

3.  Red: Watch the test fail!

4.  Green: Make all the tests pass by changing the object under test.

5.  Clean: Refactor mercilessly!

6.  Repeat.

steps

Page 27: Essential Test-Driven Development

9/9/13  

27  

9 September 2013 © Agile Institute 2008-2013 53

Runs all the tests. Expresses every idea required.

Says everything once and only once. Has no superfluous parts.

Exercise A: Password-Strength Checker

In order to be an acceptable password, a string must:

q Have a length greater than 7

characters.

q Contain at least one alphabetic character.

q Contain at least one digit.

9 September 2013 © Agile Institute 2008-2013 54

Page 28: Essential Test-Driven Development

9/9/13  

28  

9 September 2013 © Agile Institute 2008-2013 55

1.  Write one unit test.

2.  Build or add to the object under test until everything compiles.

3.  Red: Watch the test fail!

4.  Green: Make all the tests pass by changing the object under test.

5.  Clean: Refactor mercilessly!

6.  Repeat.

steps

Iteration 2

•  Admins and regular users:

•  Admin passwords must also...

•  Be > 10 chars long

•  Contain a special character

•  People want to know all the reasons why their password has failed.

•  Other stronger rules may apply later. ;-)

•  HINT: What is varying most frequently?

Be sure to take a break when you need one!

9 September 2013 © Agile Institute 2008-2013 56

Page 29: Essential Test-Driven Development

9/9/13  

29  

9 September 2013 © Agile Institute 2008-2013 57

1.  Write one unit test.

2.  Build or add to the object under test until everything compiles.

3.  Red: Watch the test fail!

4.  Green: Make all the tests pass by changing the object under test.

5.  Clean: Refactor mercilessly!

6.  Repeat.

steps

CLOSING

9 September 2013 © Agile Institute 2008-2013 58

1.  _______________________________________

2.  _______________________________________

3.  _______________________________________

Page 30: Essential Test-Driven Development

9/9/13  

30  

9 September 2013 © Agile Institute 2008-2013 59

9 September 2013 © Agile Institute 2008-2013 60

[email protected]

http://PowersOfTwo.agileInstitute.com/

@agilecoach