Essential Test-Driven Development

30
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

description

Test-driven development (TDD) is a powerful technique for combining software design, unit testing, and coding in a continuous process to increase reliability and produce better code design. Using the TDD approach, developers write programs in very short development cycles: first the developer writes a failing automated test case that defines a new function or improvement, then produces code to pass that test, and finally refactors the new code to acceptable standards. The developer repeats this process many times until the behavior is complete and fully tested. Rob Myers demonstrates the essential TDD techniques, including unit testing with the common xUnit family of open source development frameworks, refactoring as just-in-time design, plus Fake It, Triangulate, and Obvious Implementation. During this hands-on session, you’ll use exercises to practice the techniques. With many years of product development experience using TDD, Rob will address the questions that arise during your own relaxed exploration of test-driven development.

Transcript of Essential Test-Driven Development

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