Slide 1

Slide 1 text

Test-Driven Development – Is it as good as it seems? Exercise

Slide 2

Slide 2 text

2 Design goals • How to teach TDD in 30 minutes?! • tutorials usually take much longer! • live-programming in 30 minutes: not a good idea! ! ! • Main goal of the exercise:! • to get you into the rhythm of the “red, green, refactor” cycle

Slide 3

Slide 3 text

Test-Driven Development – Is it as good as it seems? Daniel Bader AT 11 The task • Create a class “Euro” that represents the currency (€)! • We will add requirements as we go through the exercise! ! ! • Note: This exercise may seem trivial. ! • The difficulty is not in the solution of the problem ! • This exercise will give you a brief glimpse into how to apply TDD 3

Slide 4

Slide 4 text

4 import org.junit.Test; import static junit.framework.Assert.*; ! /* Euro class - to do: */ ! public class EuroTest { ! } Project setup

Slide 5

Slide 5 text

5 import org.junit.Test; import static junit.framework.Assert.*; ! /* Euro class - to do: - convert to string */ ! public class EuroTest { ! } A new requirement appears! We want to create Euro objects and get string representations for them,! e.g. “EUR 2.00”

Slide 6

Slide 6 text

6 import org.junit.Test; import static junit.framework.Assert.*; ! /* Euro class - to do: - convert to string */ ! public class EuroTest { @Test public void testToString() { assertEquals("EUR 2.00", new Euro(2).toString()); } } Write a new test Run it ... Cannot find symbol class Euro

Slide 7

Slide 7 text

7 public class Euro { public Euro(int amount) { } ! @Override public String toString() { return null; } } Make the code compile @Test public void testToString() { assertEquals("EUR 2.00", new Euro(2).toString()); } Run it again ... junit.framework.ComparisonFailure: ! Expected: EUR 2.00! Actual:

Slide 8

Slide 8 text

8 public class Euro { public Euro(int amount) { } ! @Override public String toString() { return null; } } Add a tiny bit of production code @Test public void testToString() { assertEquals("EUR 2.00", new Euro(2).toString()); }

Slide 9

Slide 9 text

9 public class Euro { public Euro(int amount) { } ! @Override public String toString() { return "EUR 2.00"; } } Add a tiny bit of production code @Test public void testToString() { assertEquals("EUR 2.00", new Euro(2).toString()); } Run once more ... Passed!

Slide 10

Slide 10 text

10 import org.junit.Test; import static junit.framework.Assert.*; ! /* Euro class - to do: - convert to string */ ! public class EuroTest { @Test public void testToString() { assertEquals("EUR 2.00", new Euro(2).toString()); assertEquals("EUR 7.50", new Euro(7.50).toString()); } } What about fractions of Euros (e.g. Cents)? What about other values than “2 €”? Run it ... Cannot find symbol constructor Euro(double)

Slide 11

Slide 11 text

11 public class Euro { public Euro(double amount) { } ! @Override public String toString() { return "EUR 2.00"; } } Make the code compile @Test public void testToString() { assertEquals("EUR 2.00", new Euro(2).toString()); assertEquals("EUR 7.50", new Euro(7.50).toString()); } Run ... junit.framework.ComparisonFailure: ! Expected: EUR 7.50! Actual: EUR 2.00! at EuroTest.testToString

Slide 12

Slide 12 text

12 public class Euro { private double amount; public Euro(double amount) { this.amount = amount; } ! @Override public String toString() { return String.format("EUR %.2f", amount); } } Add more production code @Test public void testToString() { assertEquals("EUR 2.00", new Euro(2).toString()); assertEquals("EUR 7.50", new Euro(7.50).toString()); } Run ... Passed!

Slide 13

Slide 13 text

13 /* Euro class - to do: X convert to string - equality */ ! public class EuroTest { @Test public void testToString() { assertEquals("EUR 2.00", new Euro(2).toString()); assertEquals("EUR 7.50", new Euro(7.50).toString()); } @Test public void testEquality() { Euro sevenFifty = new Euro(7.50); Euro sevenFiftyToo = new Euro(7.50); assertTrue(sevenFifty.equals(sevenFiftyToo)); } } A new requirement appears ... We want to check Euro objects for equality

Slide 14

Slide 14 text

14 public class Euro { private double amount; public Euro(double amount) { this.amount = amount; } ! @Override public String toString() { return String.format("EUR %.2f", amount); } ! @Override public boolean equals(Object o) { return true; } } Add a tiny bit of production code @Test public void testEquality() { Euro sevenFifty = new Euro(7.50); Euro sevenFiftyToo = new Euro(7.50); assertTrue(sevenFifty.equals(sevenFiftyToo)); } Run ... Passed!

Slide 15

Slide 15 text

15 public class EuroTest { @Test public void testToString() { assertEquals("EUR 2.00", new Euro(2).toString()); assertEquals("EUR 7.50", new Euro(7.50).toString()); } @Test public void testEquality() { Euro sevenFifty = new Euro(7.50); Euro sevenFiftyToo = new Euro(7.50); assertTrue(sevenFifty.equals(sevenFiftyToo)); } ! @Test public void testInequality() { Euro sevenEuros = new Euro(7); Euro threeEuros = new Euro(3); assertFalse(sevenEuros.equals(threeEuros)); } } We should also check for inequality … :) Run it ... junit.framework.AssertionFailedError! ! at EuroTest.testInequality

Slide 16

Slide 16 text

16 public class Euro { protected double amount; public Euro(double amount) { this.amount = amount; } ! @Override public String toString() { return String.format("EUR %.2f", amount); } ! @Override public boolean equals(Object o) { return (o instanceof Euro) && amount == ((Euro) o).amount; } } Let’s do this properly… @Test public void testInequality() { Euro sevenEuros = new Euro(7); Euro threeEuros = new Euro(3); assertFalse(sevenEuros.equals(threeEuros)); } Run ... Passed!

Slide 17

Slide 17 text

17 /* Euro class - to do: X convert to string X equality - subtraction */ ! public class EuroTest { ! /* ... */ ! @Test public void testSubtraction() { assertEquals(new Euro(1), new Euro(3).minus(new Euro(2))); assertEquals(new Euro(2), new Euro(5).minus(new Euro(3))); } } A new requirement appears ... We want to be able to subtract Euros from each other

Slide 18

Slide 18 text

18 public class Euro { protected double amount; ! public Euro(double amount) { this.amount = amount; } @Override public String toString() { return String.format("EUR %.2f", amount); } ! @Override public boolean equals(Object o) { return (o instanceof Euro) && amount == ((Euro) o).amount; } ! public Euro minus(Euro subtrahend) { return new Euro(amount - subtrahend.amount); } } Write production code @Test public void testSubtraction() { assertEquals(new Euro(1), new Euro(3).minus(new Euro(2))); assertEquals(new Euro(2), new Euro(5).minus(new Euro(3))); }

Slide 19

Slide 19 text

19 Run the tests ... Passed!

Slide 20

Slide 20 text

20 /* Euro class - to do: X convert to string X equality X subtraction - numeric safety? */ ! public class EuroTest { ! /* ... */ ! @Test public void testNumericSafety() { assertEquals(new Euro(0.61), new Euro(1.03).minus(new Euro(0.42))); } } We have just finished a book on numerical computing... “One should never use floats or doubles to store currencies” -! Example: 1.03 - 0.42 == 0.6100000000000001 != 0.61! ! Improve numeric safety! Run it ... junit.framework.AssertionFailedError: ! Expected: EUR 0.61! Actual: EUR 0.6100000000000001

Slide 21

Slide 21 text

21 How to fix this numeric problem? Solution: Store the amount in Cents,  e.g. 7.50 € == 750 Cents

Slide 22

Slide 22 text

22 public class Euro { protected int amount; ! public Euro(double amount) { this.amount = (int) (amount * 100.0); } ! @Override public String toString() { return String.format("EUR %.2f", (double) amount / 100.0); } ! @Override public boolean equals(Object o) { return (o instanceof Euro) && amount == ((Euro) o).amount; } ! public Euro minus(Euro subtrahend) { return new Euro(amount - subtrahend.amount); } } Refactor amount from double to int Run it ...

Slide 23

Slide 23 text

23 junit.framework.AssertionFailedError: ! Expected: EUR 1.00! Actual: EUR 100.00! at EuroTest.testSubtraction junit.framework.AssertionFailedError: ! Expected: EUR 0.61! Actual: EUR 61.00! at EuroTest.testNumericSafety What happened?

Slide 24

Slide 24 text

24 junit.framework.AssertionFailedError: ! Expected: EUR 1.00! Actual: EUR 100.00! at EuroTest.testSubtraction junit.framework.AssertionFailedError: ! Expected: EUR 0.61! Actual: EUR 61.00! at EuroTest.testNumericSafety What happened: ! We broke existing functionality (subtraction) … but our test suite warned us! ! (That’s good)

Slide 25

Slide 25 text

25 public class Euro { protected int amount; ! public Euro(double amount) { this.amount = (int) (amount * 100.0); } ! @Override public String toString() { return String.format("EUR %.2f", (double) amount / 100.0); } ! @Override public boolean equals(Object o) { return (o instanceof Euro) && amount == ((Euro) o).amount; } ! public Euro minus(Euro subtrahend) { Euro result = new Euro(0); result.amount = amount - subtrahend.amount; return result; } } Fix subtraction ... Run ... Passed!

Slide 26

Slide 26 text

26 public class Euro { protected int amount; ! private static final double CENTS_PER_EURO = 100; ! public Euro(double amount) { this.amount = (int) (amount * CENTS_PER_EURO); } ! @Override public String toString() { return String.format("EUR %.2f", (double) amount / CENTS_PER_EURO); } ! @Override public boolean equals(Object o) { return (o instanceof Euro) && amount == ((Euro) o).amount; } ! public Euro minus(Euro subtrahend) { Euro result = new Euro(0); result.amount = amount - subtrahend.amount; return result; } } Great, now let’s refactor ... Run ... Still passes!

Slide 27

Slide 27 text

27 public class EuroTest { @Test public void testToString() { assertEquals("EUR 2.00", new Euro(2).toString()); assertEquals("EUR 7.50", new Euro(7.50).toString()); } ! @Test public void testEquality() { Euro sevenFifty = new Euro(7.50); Euro sevenFiftyToo = new Euro(7.50); assertTrue(sevenFifty.equals(sevenFiftyToo)); } ! @Test public void testInequality() { Euro sevenEuros = new Euro(7); Euro threeEuros = new Euro(3); assertFalse(sevenEuros.equals(threeEuros)); } @Test public void testSubtraction() { Euro twoEuros = new Euro(2); Euro threeEuros = new Euro(3); assertEquals(new Euro(1), threeEuros.minus(twoEuros)); assertEquals(new Euro(2), new Euro(5).minus(new Euro(3))); } ! @Test public void testNumericSafety() { assertEquals(new Euro(0.61), new Euro(1.03).minus(new Euro(0.42))); } } Final test code

Slide 28

Slide 28 text

28 public class Euro { protected int amount; ! private static final double CENTS_PER_EURO = 100; ! public Euro(double amount) { this.amount = (int) (amount * CENTS_PER_EURO); } ! @Override public String toString() { return String.format("EUR %.2f", (double) amount / CENTS_PER_EURO); } ! @Override public boolean equals(Object o) { return (o instanceof Euro) && amount == ((Euro) o).amount; } ! public Euro minus(Euro subtrahend) { Euro result = new Euro(0); result.amount = amount - subtrahend.amount; return result; } } Final production code

Slide 29

Slide 29 text

29 Discussion