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
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
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
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()); }
}
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
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
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
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
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!");
}
}
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; }
}
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
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);
}
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(); // ...
}
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);
}
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; }
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
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
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
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; } }
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; } }
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); }
// ...
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); }
// ...
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, ???);
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);
}
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
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
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
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
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. _______________________________________
9/9/13
30
9 September 2013 © Agile Institute 2008-2013 59
9 September 2013 © Agile Institute 2008-2013 60
http://PowersOfTwo.agileInstitute.com/
@agilecoach
Top Related