essential test-driven development
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
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