Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering...

158
Software Engineering, 2005 Test-Driven Development 1 Software Engineering Test-Driven Test-Driven Development Development Mira Balaban Department of Computer Science Ben-Gurion university Based on: K. Beck: Test-Driven Development by Example. And slides of: Grenning

Transcript of Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering...

Page 1: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 1

Software Engineering

Test-Driven DevelopmentTest-Driven Development

Mira Balaban

Department of Computer Science

Ben-Gurion university

Based on: K. Beck: Test-Driven Development by Example.

And slides of: Grenning

Page 2: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 2

What is Test-Driven Development?

An iterative technique for developing software.

As much about design as testing.• Encourages design from a user’s point of view.

• Encourages testing classes in isolation.

• Produces loosely-coupled, highly-cohesive systems

Grenning

Page 3: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 3

What is TDD?

Must be learned and practiced.

• If it feels natural at first, you’re probably doing it wrong.

More productive than debug-later programming.

It’s an addiction rather than a discipline – Kent Beck

Grenning

Page 4: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 4

TDD Mantra Red / Green / Refactor

1. Red – Write a little test that doesn’t work, and perhaps doesn’t even compile at first.

2. Green – Make the test work quickly, committing whatever sins necessary in the process.

3. Refactor – Eliminate all of the duplication created in merely getting the test to work.

Beck

Page 5: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 5

TDD Development CycleStart

Refactor as needed

Write a test for a new capability

Run all teststo see them pass

Write the code

Run the testand see it fail

Fix compile errors

Compile

Grenning

Run all teststo see them pass

Page 6: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 6

Do the Simplest Thing

Assume simplicity- Consider the simplest thing that could possibly work.

- Iterate to the needed solution.

When Coding:- Build the simplest possible code that will pass the tests.

- Refactor the code to have the simplest design possible.

- Eliminate duplication.

Grenning

Page 7: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 7

TDD Process

Beck

Once a test passes, it is re-run with every change.

Broken tests are not tolerated.

Side-effects defects are detected immediately.

Assumptions are continually checked.

Automated tests provide a safety net that gives you the courage to refactor.

Page 8: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 8

TDD Process What do you test? …

everything that could possibly break - Ron Jefferies

Don’t test anything that could not possibly break (always a judgment call)

Example: Simple accessors and mutators

Beck

Page 9: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 9

TDD Process1. Start small or not at all (select one small piece of functionality

that you know is needed and you understand).

2. Ask “what set of tests, when passed, will demonstrate the presence of code we are confident fulfills this functionality correctly?”

3. Make a to-do list, keep it next to the computer

1. Lists tests that need to be written

2. Reminds you of what needs to be done

3. Keeps you focused

4. When you finish an item, cross it off

5. When you think of another test to write, add it to the list

Beck

Page 10: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 10

Test Selection

Which test should you pick from the list? Pick a test that will teach you something and that you are confident you can implement.

Each test should represent one step toward your overall goal.

How should the running of tests affect one another? Not at all – isolated tests.

Beck

Page 11: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 11

Assert First

Where should you start building a system? With the stories you want to be able to tell about the finished system.

Where should you start writing a bit of functionality? With the tests you want to pass on the finished code.

Where should you start writing a test? With the asserts that will pass when it is done.

Beck

Page 12: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 12

TDD techniques:

Beck

Fake It (‘Til You Make It)

Triangulate

Obvious Implementation

Page 13: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 13

After the first cycle you have:

1. Made a list of tests we knew we needed to have working.

2. Told a story with a snippet of code about how we wanted to view one operation.

3. Made the test compile with stubs.

4. Made the test run by committing horrible sins.

5. Gradually generalized the working code, replacing constants with variables.

6. Added items to our to-do list rather than addressing then all at once.Beck

Page 14: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 14

“Expected surprises”: How each test can cover a small increment of

functionality.

How small and ugly the changes can be to make the new tests run.

How often the tests are run.

How many teensy-weensy steps make up the refactorings.

Page 15: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 15

Motivating story

WyCash – A company that sold bond portfolio management systems in US dollars.

Requirement: Add multi-currencies.

Method used: Replace the former basic Dollar objects by Create Multi-Currency Money objects.• Instead of – revise all existing services.

Page 16: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 16

Requirement – Create a report:Like this:

Instrument Shares Price Total IBM 1000 25 USD 25000 USD Novartis 400 150 CHF 60000 CHF   

Total 65000 USD

Exchange rates: From To Rate

CHF USD 1.5

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10

Page 17: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 17

The 1st to-do list:

We need to be able to add amounts in two different currencies and convert the result given a set of exchange rates.

We need to be able to multiply an amount (price per share) by a number (number of shares) and receive an amount.

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10

Page 18: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 18

The First test:

We work on multiplication first, because it looks simpler. What object we need – wrong question! What test we need?

public void testMultiplication() {

Dollar five= new Dollar(5);

five.times(2);

assertEquals(10, five.amount);

}

Page 19: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 19

2nd to-do list:

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?

Test does not compile:1. No class Dollar.2. No constructor.3. No method times(int).4. No field amount.

Page 20: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 20

Make the test compile:

class Dollar{

public int amount;

public Dollar(int amount){}

public void times(int multiplier){}

}

Page 21: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 21

Test fails.

Is it bad? It’s great, we have something to work on!

Our programming problem has been transformed from "give me multi-currency" to "make this test work, and then make the rest of the tests work."

Much simpler.

Much smaller scope for fear.

We can make this test work.

Page 22: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 22

Make it run -- Green bar:.The test:public void testMultiplication() {

Dollar five= new Dollar(5); five.times(2); assertEquals(10, five.amount);

}

class Dollar{public int amount = 10;public Dollar(int amount){}public void times(int multiplier){}

}

Page 23: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 23

The refactor (generalize) step: Quickly add a test.

Run all tests and see the new one fail.

Make a little change.

Run all tests and see them all succeed.

Refactor to remove duplication between code and test –duplication is a source for dependencies between code and test, and dependency test cannot reasonably change independently of the code!

Page 24: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 24

Remove Duplication:.

The test:public void testMultiplication() {

Dollar five= new Dollar(5); five.times(2); assertEquals(10, five.amount);

}

class Dollar{public int amount = 5 * 2;public Dollar(int amount){}public void times(int multiplier){}

}

Page 25: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 25

Remove Duplication:class Dollar{

public int amount;public Dollar(int amount){}public void times(int multiplier){

amount = 5 * 2;}

}

Where did the 5 come from? The argument to the constructor:class Dollar{

public int amount;public Dollar(int amount){this.amount = amount}public void times(int multiplier){

amount = amount * 2;}

}

Page 26: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 26

Remove duplication:The test:public void testMultiplication() {

Dollar five= new Dollar(5); five.times(2); assertEquals(10, five.amount);

}

The 2 is the value of the multiplier argument passed to times. Using *= removes duplication:class Dollar{

public int amount;public Dollar(int amount){this.amount = amount}public void times(int multiplier){

amount *= multiplier;}

}

The test is still green.

Page 27: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 27

2nd to-do list:

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?

The current to-do list:

Page 28: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 28

Our achievements:

Made a list of the tests we knew we needed to have working.

Told a story with a snippet of code about how we wanted to view one operation.

Made the test compile with stubs. Made the test run by committing horrible sins. Gradually generalized the working code, replacing

constants with variables. Added items to our to-do list rather than addressing

them all at once.

Page 29: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 29

Our method: Write a test: Think about the operation as a story. Invent the interface

you wish you had. Include all elements necessary for the right answers.

Make it run: Quickly getting that bar to go to green dominates everything else. If the clean, simple solution is obvious but it will take you a minute, then make a note of it and get back to the main problem -- getting the bar green in seconds.

Quick green excuses all sins. But only for a moment. Make it right: Remove the duplication that you have introduced, and

get to green quickly.

The goal is clean code that works:Test-Driven Development: 1. “that works”, 2. “clean”.Model Driven Development: 1. “clean”, 2. “that

works”.

Page 30: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 30

2nd to-do list:

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?

Next we’ll try to get rid of the side effects.

The current to-do list:

We’ll remove side effects:

Page 31: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 31

Degenerate objects:

Write a new test for successive multiplications – that will test side effects:

public void testMultiplication() { Dollar five= new Dollar(5);five.times(2);assertEquals(10, five.amount);five.times(3); assertEquals(15, five.amount);

}

The test fails – red bar!

Decision: Change the interface of Dollar. Times() will return a new object.

Page 32: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 32

Degenerate objects: Write another test for successive multiplications:

public void testMultiplication() { Dollar five= new Dollar(5); Dollar product= five.times(2); assertEquals(10, product.amount);product= five.times(3);assertEquals(15, product.amount);

}

The test does not compile!

Page 33: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 33

Degenerate objects: Change Dollar.times() :

public Dollar times(int multiplier){amount *= multiplier;return null;

}

The test compiles but does not run!

public Dollar times(int multiplier){return new Dollar(amount * multiplier);

}

The test passes!

Page 34: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 34

Achieved item in the to-do list:

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?

The current to-do list:

The “side-effects” item is achieved by turning Dollar.times() into a non-destructive (non-mutator) operation. This is the characterisation of Value Objects.

Page 35: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 35

Our method in the last step:

We have translated a feeling -- disgust at side effects –

into a test -- multiply a Dollar object twice.

This is a common theme of TDD:

Translate aesthetic judgments into to-do items and into tests.

Translated a design objection (side effects) into a test case that failed because of the objection.

Got the code to compile quickly with a stub implementation.

Made the test work by typing in what seemed to be the right code.

Page 36: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 36

Two strategies for test passing:

Fake It— Return a constant and gradually replace constants with variables until you have the real code.

Use Obvious Implementation— Type in the real implementation.

Page 37: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 37

Equality for all – Value objects:

Dollar is now used as a value object.

The Value Object pattern constraints:

– they are not changed by operations that are applied on them.

– Values of instance variables are not changed.

– No aliasing problems (think of 2 $5 checks).

– All operations must return a new object.

– Value objects must implement equals() (all $5 objects are the same).

Page 38: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 38

3rd to-do list:

If Dollar is the key to a hash table, then implementing equals() requires also an implementation of hashcode() (the equals – hash tables contract).

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()

Page 39: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 39

Handling equals()Implementing equals()? – no no!

Writing a test for equality!

public void testEquality() { assertTrue(new Dollar(5).equals(new Dollar(5)));

}

The test fails (bar turns red).

Fake implementation in class Dollar: Just return true.public boolean equals(Object object) {

return true; }

Duplication: true is 5 == 5 which is amount == 5.

Page 40: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 40

Triangulation generalization technique:

Triangulation is a generalization that is motivated by 2 examples or more.

Triangulation ignores duplication between test and model code.Technique: invent another example and extend the test:We add $5 != $6?

public void testEquality() { assertTrue(new Dollar(5).equals(new Dollar(5)));

assertFalse(new Dollar(5).equals(new Dollar(6))); }

Generalize Dollar.equality:public boolean equals(Object object) {

Dollar dollar= (Dollar) object; return amount == dollar.amount;

}

Page 41: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 41

Triangulation considrations:

Triangulation could have been used in the generalization of Dollar.times().• Instead of following test-model duplications we could have

invented another multiplication example.

Beck’s suggestion: Use triangulation only if you do not know how to refactor using duplication removal.

Triangulation means trying some variation in a dimension of the design.

Page 42: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 42

Review of handling the 4th to-do list:

Noticed that our design pattern (Value Object) implied an operation.

Tested for that operation.

Implemented it simply.

Didn't refactor immediately, but instead tested further.

Refactored to capture the two cases at once.

Page 43: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 43

4th to-do list:

Equality requires also: Comparison with null. Comparison with other, non Dollar objects.

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object

Page 44: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 44

Privacy: 5th to-do list:

Equality of Dollar objects enables making the instance

variable amount private:

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object

Page 45: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 45

Improve the multiplication test: Turn the integers equality test into Dollar equality test:Replace:

public void testMultiplication() { Dollar five= new Dollar(5); Dollar product= five.times(2); assertEquals(10, product.amount);product= five.times(3);assertEquals(15, product.amount);

}

withpublic void testMultiplication() {

Dollar five= new Dollar(5); Dollar product= five.times(2); assertEquals(new Dollar(10), product);product= five.times(3);assertEquals(new Dollar(15), product);

}

Page 46: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 46

Improve the multiplication test:

Get rid of the product variable:

public void testMultiplication() {

Dollar five= new Dollar(5);

assertEquals(new Dollar(10), five.times(2));

assertEquals(new Dollar(15), five.times(3));

}

This is a truly declarative test.

Now Dollar is the only class using the amount instance variable.

Therefore in the Dollar class:

Private int amount;

Page 47: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 47

Achieved the 5th to-do list:

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object

Page 48: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 48

Achieved the 5th to-do list:

Note: The multiplication test is now based on equality! dependency among tests.If the equality test fails, then also multiplication fails!

The operations in the 5th to-do list step: Used functionality just developed to improve a test. Noticed that if two tests fail at once we're sunk. Proceeded in spite of the risk. Used new functionality in the object under test to reduce

coupling between the tests and the code (removal of the amount instance variable from the test).

Page 49: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 49

Frank-ly speaking: the 6th to-do list:Observe the first test: It requires handling Francs. Seems TOO BIG for a “little step”. We insert a new object type Franc, that should pass the same test we

have for Dollar.

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object5 CHF * 2 = 10 CHF

Page 50: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 50

Franc multiplication test:

Copy the Dollar test:

public void testFrancMultiplication() {

Franc five= new Franc(5);

assertEquals(new Franc(10), five.times(2));

assertEquals(new Franc(15), five.times(3));

}

Page 51: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 51

Pass the Franc multiplication test:

Recall the cycle steps:

Write a test. Make it compile. Run it to see that it fails. Make it run. Remove duplication.

We now have to accomplish step 4.We’ll copy the Dollar class! And create robust duplication!

Page 52: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 52

Class Franc:

class Franc { private int amount; Franc(int amount) {

this.amount= amount; } Franc times(int multiplier) {

return new Franc(amount * multiplier); } public boolean equals(Object object) {

Franc franc= (Franc) object; return amount == franc.amount;

} }

Page 53: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 53

Refactor – Following Franc Multiplication test:

We created robust duplication:

Classes Dollar and Franc.

Common equals.

Common times.

Too much for a single refactoring step.

We add these to our to-do list and move to the next cycle.

Page 54: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 54

Equality for all: the 7th to-do list:

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object5 CHF * 2 = 10 CHFDollar/Franc duplicationCommon equalsCommon times

Page 55: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 55

Cleanup duplication: Insert a common superclass Money for the Dollar and Franc classes. We do it gradually, applying the tests all the time:

1. class Money All tests pass.

2. class Dollar extends Money { private int amount;

} All tests pass.

3. class Money { protected int amount;

} class Dollar extends Money { } All tests pass.

Page 56: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 56

Work on equals:

4. Dollarpublic boolean equals(Object object) {

Money dollar = (Dollar) object; return amount == dollar.amount;

} All tests pass.

5. Dollarpublic boolean equals(Object object) {

Money dollar = (Money) object; return amount == dollar.amount;

} All tests pass.

6. Dollarpublic boolean equals(Object object) {

Money money = (Money) object; return amount == money. amount;

} All tests pass.

Page 57: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 57

Move Dollar equals up:

7. Money

public boolean equals(Object object) { Money money = (Money) object; return amount == money. amount;

} All tests pass.

Page 58: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 58

Eliminate Franc.equals():

We are going to work on Franc.equals(), but

there is no test for it!

Happens regularly:

Missing tests are revealed while refactoring.

A refactoring mistake is not be captured by the tests!

Write a test – extend the existing one, by duplicating the Dollar equality test:

public void testEquality() {

assertTrue(new Dollar(5).equals(new Dollar(5))); assertFalse(new Dollar(5).equals(new Dollar(6)));

assertTrue(new Franc(5).equals(new Franc(5))); assertFalse(new Franc(5).equals(new Franc(6)));

}

Page 59: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 59

Eliminate Franc.equals():

8. class Franc extends Money { private int amount;

}

All tests pass.

9. class Franc extends Money { }

All tests pass.

Page 60: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 60

10. Francpublic boolean equals(Object object) {

Money franc = (Franc object; return amount == franc.amount;

} All tests pass.

11. Francpublic boolean equals(Object object) {

Money franc = (Money) object; return amount == dollar.amount;

} All tests pass.

12. Francpublic boolean equals(Object object) {

Money money = (Money) object; return amount == money. amount;

} All tests pass.

Eliminate Franc.equals():

Page 61: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 61

Now we can eliminate it. All tests are passed.

But – a new to-do entry: Compare Dollars with Francs.

The steps in this cycle: Stepwise moved common code from one class (Dollar) to

a superclass (Money).

Made a second class (Franc) a subclass also.

Reconciled two implementations—equals()—before eliminating the redundant one.

Eliminate Franc.equals():

Page 62: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 62

Equality for all: the 8th to-do list:$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object5 CHF * 2 = 10 CHFDollar/Franc duplicationCommon equalsCommon timesCompare Francs with Dollars

Page 63: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 63

Comparing Dollars with Francs:

Add such a comparison:

public void testEquality() {

assertTrue(new Dollar(5).equals(new Dollar(5))); assertFalse(new Dollar(5).equals(new Dollar(6)));

assertTrue(new Franc(5).equals(new Franc(5))); assertFalse(new Franc(5).equals(new Franc(6)));

assertFalse(new Franc(5).equals(new Dollar(5)));

}

The test fails!

Page 64: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 64

Comparing Dollars with Francs:

The test fails because equality does not check for being the same currency – Dollars ARE Francs!.

But there are no currency objects a new concept is needed. Can use Java same class comparison (smelly!).

Money

public boolean equals(Object object) { Money money = (Money) object; return amount == money. Amount && getClass().equals(money.getClass());

} All tests pass.

Page 65: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 65

Comparing Dollars with Francs cycle:

Took an objection that was bothering us and turned it into a test.

Made the test run a reasonable, but not perfect way —getClass().

Decided not to introduce more design until we had a better motivation.

Page 66: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 66

Making Objects: the 9th to-do list:$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object5 CHF * 2 = 10 CHFDollar/Franc duplicationCommon equalsCommon timesCompare Francs with DollarsCurrency?

Page 67: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 67

Get rid of the common times():

Similar implementations for times():

Franc:

Franc times(int multiplier) {

return new Franc(amount * multiplier);

}

Dollar:

Dollar times(int multiplier) {

return new Dollar(amount * multiplier);

}

Page 68: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 68

Get rid of the common times():

1. Insert a common return type:

Franc:

Money times(int multiplier) {

return new Franc(amount * multiplier);

}

Dollar:

Money times(int multiplier) {

return new Dollar(amount * multiplier);

}

All tests pass!

Page 69: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 69

Get rid of the common times():

2. Aim: eliminate the Money subclasses – not doing enough work to justify their existence.

Can do it only if there are no explicit references to their objects.

Method: • Insert factory methods for Dollar and for Franc in the

Money class.

• Replace the Dollar and France explicit references with that factory method.

Page 70: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 70

A factory method:

public void testMultiplication() {

Dollar five= Money.dollar(5);

assertEquals(new Dollar(10), five.times(2));

assertEquals(new Dollar(15), five.times(3));

}

The factory method in Money:

static Dollar dollar(int amount) {

return new Dollar(amount);

}

Page 71: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 71

Remove explicit references to Dollar:public void testMultiplication() {

Money five= Money.dollar(5); assertEquals(new Dollar(10), five.times(2));assertEquals(new Dollar(15), five.times(3));

}

Compilation problem: times is not defined for Money.There is no implementation for times in Money

Make Money abstract:

abstract class Money{abstract Money times(int multiplier); static Money dollar(int amount) {

return new Dollar(amount); }

} All tests pass!

Page 72: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 72

Remove explicit references to Dollar:3. Use the factory method everywhere in the tests:public void testMultiplication() {

Money five= Money.dollar(5); assertEquals(Money.dollar(10), five.times(2));assertEquals(Money.dollar(15), five.times(3));

}

public void testEquality() { assertTrue(Money.dollar(5).equals(Money.dollar(5)));

assertFalse(Money.dollar(5).equals(Money.dollar(6))); assertTrue(new Franc(5).equals(new Franc(5)));

assertFalse(new Franc(5).equals(new Franc(6)));assertFalse(new Franc(5).equals(Money.dollar(5)));

} The tests are passed!

Page 73: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 73

Remove explicit references to Dollar

Achieved: No client code knows that there is a subclass called

Dollar.

By decoupling the tests from the existence of the subclasses, we have given ourselves freedom to change inheritance without affecting any model code.

Page 74: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 74

Remove explicit references to Franc:4. Use the factory method everywhere in the tests:public void testFrancMultiplication() {

Money five= Money.franc(5); assertEquals(Money.franc(10), five.times(2));assertEquals(Money.franc(15), five.times(3));

}

public void testEquality() { assertTrue(Money.dollar(5).equals(Money.dollar(5)));

assertFalse(Money.dollar(5).equals(Money.dollar(6))); assertTrue(Money.franc(5).equals(Money.franc(5)));

assertFalse(Money.franc(5).equals(Money.franc(6)));assertFalse(Money.franc(5).equals(Money.dollar(5)));

} The tests are passed!

Page 75: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 75

The factory method for Franc:

The factory method in Money:

static Money franc(int amount) {

return new Franc(amount);

}

Note: The testFrancMultiplication() test maybe redundant –

Same like testMultiplication() for Dollar.

Page 76: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 76

Achievements in this cycle:

Took a step toward eliminating duplication by reconciling the signatures of two variants of the same method—times().

Moved at least a declaration of the method to the common superclass.

Decoupled test code from the existence of concrete subclasses by introducing factory methods.

Noticed that when the subclasses disappear some tests will be redundant, but took no action.

Page 77: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 77

Currency: the 10th to-do list:$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10 Delete testFrancMultiplication?Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object5 CHF * 2 = 10 CHFDollar/Franc duplicationCommon equalsCommon timesCompare Francs with DollarsCurrency?

Page 78: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 78

Currency? How to implement currency? –

No, no. Wrong question! How to test currencies! In the future – complicated objects with flyweight

factories (to ensure controlled creation). Now – only strings:

public void testCurrency() {assertEquals("USD", Money.dollar(1).currency()); assertEquals("CHF", Money.franc(1).currency());

}

Page 79: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 79

1. Insert a currency() method:

Money

abstract String currency();

Implement it in both subclasses:

Franc

String currency() { return "CHF"; }

Dollar

String currency() { return "USD"; }

All tests pass!

Page 80: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 80

2. Refactoring: Achieving a common implementation for currency() in Dollar and Franc:

Francprivate String currency;Franc(int amount) {

this.amount = amount; currency = "CHF";

} String currency() {

return currency; }

Dollarprivate String currency;Dollar(int amount) {

this.amount = amount; currency = “USD";

} String currency() {

return currency; }

Page 81: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 81

3. Refactoring: Push up the variable and the implementation of currency():

Money

protected String currency;

String currency() {

return currency;

}

Achieved: All tests pass!

Page 82: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 82

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10 Delete testFrancMultiplication?Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object5 CHF * 2 = 10 CHFDollar/Franc duplicationCommon equalsCommon timesCompare Francs with DollarsCurrency?

Currency achieved!

Page 83: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 83

Refactoring: Making the constructors of Dollar and Franc identical (1):

Add a parameter to the constructor – the idea is to remove the Explicit currency from the constructor:Franc

Franc(int amount, String currency) { this.amount = amount; this.currency = "CHF";

} This breaks the two callers of the constructor:Money

static Money franc(int amount) { return new Franc(amount, null); }

FrancMoney times(int multiplier) {

return new Franc(amount * multiplier, null); }

Page 84: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 84

Intrrupt:Correct times() to call the factory method:

Franc

Money times(int multiplier) {

return Money.franc(amount * multiplier);

}

Interruption rules:

1. Entertain a brief interruption, but only a brief one.

2. Never interrupt an interruption (Jim Coplien)

Page 85: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 85

Refactoring: Making the constructors of Dollar and Franc identical (2):

The factory method can pass "CHF":Money

static Money franc(int amount) { return new Franc(amount, "CHF");

} Assign the parameter to the instance variable:Franc

Franc(int amount, String currency) { this.amount = amount; this.currency = currency;

}

Page 86: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 86

Refactoring: Making the constructors of Dollar and Franc identical (3):

Analogous change to Dollar in one “big” step:Money

static Money dollar(int amount) { return new Dollar(amount, “USD");

} Dollar

Dollar(int amount, String currency) { this.amount = amount; this.currency = currency;

} Money times(int multiplier) {

return Money.dollar(amount * multiplier); }

Page 87: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 87

Refactoring: Push up the implementation of the constructors:

The 2 constructors are identical! Push up the implementation:

MoneyMoney(int amount, String currency) {

this.amount = amount; this.currency = currency;

} Dollar

Dollar(int amount, String currency) { super(amount, currency);

} Franc

Franc(int amount, String currency) { super(amount, currency);

}

Page 88: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 88

Achievements in this cycle:

Were a little stuck on big design ideas, so we worked on something small we noticed earlier.

Reconciled the two constructors by moving the variation to the caller (the factory method).

Interrupted a refactoring for a little twist, using the factory method in times().

Repeated an analogous refactoring (doing to Dollar what we just did to Franc) in one big step.

Pushed up the identical constructors.

Page 89: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 89

Interesting Times: the 11th to-do list:

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10 Delete testFrancMultiplication?Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object5 CHF * 2 = 10 CHFDollar/Franc duplicationCommon equalsCommon timesCompare Francs with DollarsCurrency?

Page 90: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 90

Refactoring -- Interesting Times: Making Dollar.times() and Franc.times() identical:

The two implementations are still not identical:

Franc

Money times(int multiplier) {

return Money.franc(amount * multiplier);

}

Dollar

Money times(int multiplier) {

return Money.dollar(amount * multiplier);

}

Page 91: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 91

1. Refactoring: Making Dollar.times() and Franc.times() identical:

Inline the factory methods:

Franc

Money times(int multiplier) {

return new Franc(amount * multiplier, “CHF”);

}

Dollar

Money times(int multiplier) {

return new Dollar(amount * multiplier, “USD”);

}

Page 92: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 92

2. Refactoring: Making Dollar.times() and Franc.times() identical:

In Franc: the currency instance variable is always "CHF":Franc

Money times(int multiplier) { return new Franc(amount * multiplier, currency);

} In Dollar: the currency instance variable is always “USD":Dollar

Money times(int multiplier) { return new Dollar(amount * multiplier, currency);

}

Page 93: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 93

3. Refactoring: Making Dollar.times() and Franc.times() identical:

Try to replace Franc with Money. Does it matter? -------- ask the tests!

FrancMoney times(int multiplier) {

return new Money(amount * multiplier, currency); }

Money cannot be abstract:Money

class Money Money times(int multiplier) {

return null; }

Page 94: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 94

4. Refactoring: Making Dollar.times() and Franc.times() identical:

Red bar! Error message:

"expected:<Money.Franc@31aebf> but was: <Money.Money@478a43>".

Not as helpful.

Define toString() for a better error message:

public String toString() {

return amount + " " + currency;

}

Code without a test? Exception cause: Results on the screen. toString() is used only for debug output. the risk of it failing is low. Already have a red bar! Prefer not to write a test within a red bar.

Page 95: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 95

5. Refactoring: Making Dollar.times() and Franc.times() identical:

New error message:"expected:<10 CHF> but was:<10 CHF>".

Right data but wrong class: Money instead of Franc. Problem results from testFrancMultiplcation(), that is based on the

implementation of equals():

public void testFrancMultiplication() { Money five= Money.franc(5); a MoneyassertEquals(Money.franc(10), five.times(2));assertEquals(Money.franc(15), five.times(3));

}

Moneypublic boolean equals(Object object) {

Money money = (Money) object; return amount == money.amount

&& getClass().equals(money.getClass()); }

Page 96: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 96

6. Refactoring: Making Dollar.times() and Franc.times() identical:

Should check for same currencies not same classes.

A new requirement –

Assert Money(10, “CHF”), Franc(10, “CHF) as equal!

TDD Methodology write a test. TDD rule: Do not write a test on a red bar! RETREAT Green bar!

Franc

Money times(int multiplier) {

return new Franc(amount * multiplier, currency);

}

Page 97: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 97

7. An “inner” cycle: Making Moneys and Francs equal:

A new test:public void testDifferentClassEquality() {

assertTrue(new Money(10, "CHF").equals(new Franc(10, "CHF")));

}

Red bar!

equals() compares currencies, not classes:

Money

public boolean equals(Object object) {

Money money = (Money) object;

return amount == money.amount

&& currency().equals(money.currency());

}

Page 98: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 98

8. Refactoring: Making Dollar.times() and Franc.times() identical:

Return a Money object from Franc.times():Franc

Money times(int multiplier) { return new Money(amount * multiplier, currency);

} Same for Dollar:Dollar

Money times(int multiplier) { return new Money(amount * multiplier, currency);

} Identical implementations! push up!Money

Money times(int multiplier) { return new Money(amount * multiplier, currency);

}

Page 99: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 99

Achievements in this cycle:

Reconciled two methods—times()—by first inlining the methods they called and then replacing constants with variables.

Wrote a toString() without a test just to help us debug.

Tried a change (returning Money instead of Franc) and let the tests tell us whether it worked.

Backed out an experiment and wrote another test. Making the test work made the experiment work.

Page 100: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 100

Eliminate subclasses: the 12th to-do list:

$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10 Delete testFrancMultiplication?Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object5 CHF * 2 = 10 CHFDollar/Franc duplicationCommon equalsCommon timesCompare Francs with DollarsCurrency?

Page 101: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 101

1. Refactoring: Eliminate the Dollar and Franc subclasses:

Dollar and Franc have only their constructors – which are identical.

No reason to have the subclasses.

Delete the subclasses.

Replace references to the subclasses with references to the superclass without changing the meaning of the code.

• Change the reference to Franc in the factory nethod:

static Money franc(int amount){

return new Money(amount, "CHF");

} Change the reference to Dollar in the factory method:

static Money dollar(int amount) {

return new Money(amount, "USD");

}

Page 102: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 102

2. Refactoring: Eliminate the Dollar and Franc subclasses:

Dollar has no references can be removed!

Franc has a reference – in the equality test:

public void testDifferentClassEquality() {assertTrue(new Money(10, "CHF").equals(

new Franc(10, "CHF"))); }

Do we need this test?

Page 103: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 103

3. Refactoring: Eliminate the Dollar and Franc subclasses:

Look at the other equality test:public void testEquality() { assertTrue(Money.dollar(5).equals(Money.dollar(5))); assertFalse(Money.dollar(5).equals(Money.dollar(6))); assertTrue(Money.franc(5).equals(Money.franc(5))); assertFalse(Money.franc(5).equals(Money.franc(6))); assertFalse(Money.franc(5).equals(Money.dollar(5))); }

Too detailed: (1) = (3); (2) = (4)! Simplify!

public void testEquality() { assertTrue(Money.dollar(5).equals(Money.dollar(5))); assertFalse(Money.dollar(5).equals(Money.dollar(6))); assertFalse(Money.franc(5).equals(Money.dollar(5))); }

==

Page 104: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 104

4. Refactoring: Eliminate the Dollar and Franc subclasses:

The test testDifferentClassEquality() is needed only if there are

multiple classes.

But, we try to remove the subclasses.

The test can be removed! The Franc class is removed.

All tests are passed!

Multiplication tests:• There are separate tests for Dollar and for Franc.

• Same logic.

Remove testFrancMultiplication()

Page 105: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 105

Achievements in this cycle:

Finished gutting subclasses and deleted them.

Eliminated tests that made sense with the old code structure but were redundant with the new code structure.

Page 106: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 106

The 13th to-do list:$5 + 10 CHF = $10 if rate is 2:1$5 * 2 = $10 Delete testFrancMultiplication?Make "amount" privateDollar side-effects?Money rounding?equals()hashcode()Equal nullEqual object5 CHF * 2 = 10 CHFDollar/Franc duplicationCommon equalsCommon timesCompare Francs with DollarsCurrency?

Page 107: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 107

Current Tests:public void testMultiplication() {

Money five= Money.dollar(5); assertEquals(Money.dollar(10), five.times(2));assertEquals(Money.dollar(15), five.times(3));

}

public void testEquality() { assertTrue(Money.dollar(5).equals(Money.dollar(5))); assertFalse(Money.dollar(5).equals(Money.dollar(6))); assertFalse(Money.franc(5).equals(Money.dollar(5)));

}

public void testCurrency() {assertEquals("USD", Money.dollar(1).currency()); assertEquals("CHF", Money.franc(1).currency());

}

Page 108: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 108

Current Code:public class Money {

protected int amount;protected String currency;public Money(int amount, String currency) {

this.amount = amount; this.currency = currency;

}public Money times(int multiplier) {

return new Money(amount * multiplier, currency);; } public static Money dollar(int amount) {

return new Money(amount, “USD”); } public static Money franc(int amount) {

return new Money(amount, “CHF”); } public boolean equals(Object object) {

Money money = (Money) object; return amount == money. Amount

&& currency().equals(money.currency()); }public String currency() {

return currency;}public String toString() {

return amount + " " + currency; }

}

Page 109: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 109

14. Addition – at last: A new to-do list:

$5 + 10 CHF = $10 if rate is 2:1$5 + $5 = $10

Remove achieved items. Handle simple items.

The new list:

$5 + 10 CHF = $10 if rate is 2:1

Start with a simpler addition:

Page 110: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 110

Test for simple addition:Start with………. A Test:

public void testSimpleAddition() { Money sum= Money.dollar(5).plus(Money.dollar(5));

assertEquals(Money.dollar(10), sum); }

Make the test pass:1. Fake the implementation: Return "Money.dollar(10)“. 2. Obvious implementation.

MoneyMoney plus(Money addend) {

return new Money(amount + addend.amount, currency); }

All tests are passed!

Page 111: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 111

Think Again About the Test: We know that we’ll need muti-currency arithmetic. Constraint: System must be unaware that it is dealing with

multiple currencies. Possible solution: Convert into a single currency.

Rejected. Too rigid.

We’ll try to rewrite the test so that it reflects the context of multi-currency arithmetic:

Question:

What is a multi-currency object?

Page 112: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 112

Multi-currency objects:Example: ($2 + 3CHF) * 5

$5 + 10 CHF = $10 if rate is 2:1$5 + $5 = $10

A multi-currency object can be reduced to a single currency, if an exchange rate is given.

A multi-currency object can be added.

Require a multi-currency object Expression.think about an expression as a… Wallet!

Money is an atomic expression.Sum is an expression.Expressions can be reduced, WRT an exchange rate.

Page 113: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 113

Rewrite the test to reflect multi-currencies context:

Replace:public void testSimpleAddition() {

Money sum= Money.dollar(5).plus(Money.dollar(5)); assertEquals(Money.dollar(10), sum);

}

With:public void testSimpleAddition() {

Money reduced = … addition of $5 expressions and reduction of the sum …;

assertEquals(Money.dollar(10), reduced); }

Page 114: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 114

Who is responsible for exchange of Moneys?

The Bank, of course!Need a reduced Money: public void testSimpleAddition() {

Money reduced = bank.reduce(sum, “USD”);

assertEquals(Money.dollar(10), reduced); }

Need a benk:public void testSimpleAddition() {

... Bank bank= new Bank(); Money reduced = bank.reduce(sum, “USD”);

assertEquals(Money.dollar(10), reduced); }

Page 115: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 115

A Revised Addition Test: Need a sum of Moneys which is an Expression:public void testSimpleAddition() {

...Expression sum= five.plus(five); Bank bank= new Bank(); Money reduced = bank.reduce(sum, “USD”); assertEquals(Money.dollar(10), reduced);

} Need 5 dollars Money:

public void testSimpleAddition() { Money five= Money.dollar(5); Expression sum= five.plus(five); Bank bank= new Bank(); Money reduced = bank.reduce(sum, “USD”); assertEquals(Money.dollar(10), reduced);

}

Page 116: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 116

Make the Test compile:public void testSimpleAddition() {

Money five= Money.dollar(5); Expression sum= five.plus(five); Bank bank= new Bank(); Money reduced = bank.reduce(sum, “USD”); assertEquals(Money.dollar(10), reduced);

}Needed: interface Expression{} Money:

Expression plus(Money addend) { return new Money(amount + addend.amount, currency);

} class Money implements Expression class Bank{

Money reduce(Expression source, String to) { return null;

}}

Page 117: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 117

Pass the Test – Fake it!public void testSimpleAddition() {

Money five= Money.dollar(5);

Expression sum= five.plus(five);

Bank bank= new Bank();

Money reduced = bank.reduce(sum, “USD”);

assertEquals(Money.dollar(10), reduced);

}

Bank

Money reduce(Expression source, String to) {

return Money.dollar(10);

}

All tests are passed!

Page 118: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 118

Cycle Review: Reduced a big test to a smaller test that represented progress ($5 +

10 CHF to $5 + $5).

Thought carefully about the possible metaphors for our computation.

Rewrote our previous test based on our new metaphor.

Got the test to compile quickly.

Made it run.

Looked forward with a bit of trepidation to the refactoring necessary to make the implementation real.

Refactor!

Page 119: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 119

15. Refactor: Remove Duplicationpublic void testSimpleAddition() {

Money five= Money.dollar(5); Expression sum= five.plus(five); Bank bank= new Bank(); Money reduced = bank.reduce(sum, “USD”); assertEquals(Money.dollar(10), reduced);

}

BankMoney reduce(Expression source, String to) {

return Money.dollar(10); }

and $10 is $5 + $5!

????? No idea how to refactor! ????? No idea how to replace the constant by a variable!

Page 120: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 120

Work forward:public void testSimpleAddition() {

Money five= Money.dollar(5); Expression sum= five.plus(five); Bank bank= new Bank(); Money reduced = bank.reduce(sum, “USD”); assertEquals(Money.dollar(10), reduced);

}Think about the test implications and write more tests:1. The Money.plus() operation must return a non-atomic Expression: ---- a Sum. It cannot return a Money.2. Add a to-do item: Addition of identical currency is a Money.

$5 + 10 CHF = $10 if rate is 2:1$5 + $5 = $10Return Money from $5 + $5

Page 121: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 121

Test – The sum of 2 Moneys is a Sum:

public void testPlusReturnsSum() {

Money five= Money.dollar(5);

Expression result= five.plus(five);

Sum sum= (Sum) result;

assertEquals(five, sum.augend);

assertEquals(five, sum.addend);

}

For compilation:

1. Class Sum with fields augend, addend.

2. Sum constructor.

3. Money.plus() returns a Sum not a Money (otherwise, ClassCastException – Money.plus() returns a Money not a Sum).

3. Sum implements Expression.

Page 122: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 122

Compile Test – The sum of 2 Moneys is a Sum:

Sum

class Sum implements Expression{

Money augend;

Money addend;

Sum(Money augend, Money addend) { }

}

Money

Expression plus(Money addend) {

return new Sum(this, addend);

}

Compilation passes!

Page 123: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 123

Pass Test – The sum of 2 Moneys is a Sum:

Just correct the Sum constructor:

Sum

Sum(Money augend, Money addend) {

this.augend= augend;

this.addend= addend;

}

All tests pass!

Page 124: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 124

Back to Bank.reduce():

BankMoney reduce(Expression source, String to) {

return Money.dollar(10); }

Recall: we did not know how to refactor! But now we have a Sum concept Write a test for Bank.reduce():

public void testReduceSum() { Expression sum= new Sum(Money.dollar(3), Money.dollar(4));Bank bank= new Bank(); Money result= bank.reduce(sum, "USD");

assertEquals(Money.dollar(7), result); }

The test fails because…. 3 + 4 != 10

Page 125: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 125

Pass the Test for Bank.reduce():

Bank

Money reduce(Expression source, String to) {

Sum sum= (Sum) source;

int amount= sum.augend.amount + sum.addend.amount;

return new Money(amount, to);

} All tests pass!

Problems: The cast. This code should work with any Expression.

The public fields, and two levels of references at that.

What about Bank reduction of a Money: Bank.reduce(Money)?

Page 126: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 126

Refactor for Bank.reduce():

Bank

Money reduce(Expression source, String to) {

Sum sum= (Sum) source;

return sum.reduce(to);

}

Sum

public Money reduce(String to) {

int amount= augend.amount + addend.amount;

return new Money(amount, to);

} All tests pass!

Page 127: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 127

A new item on the to-do list:

First write a …..test:

public void testReduceMoney() {

Bank bank= new Bank();

Money result= bank.reduce(Money.dollar(1), "USD"); assertEquals(Money.dollar(1), result);

}

$5 + 10 CHF = $10 if rate is 2:1$5 + $5 = $10Return Money from $5 + $5Bank.reduce(Money)

Page 128: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 128

Pass Bank.reduce(Money) test:

Bank

Money reduce(Expression source, String to) {

if (source instanceof Money) return (Money) source; Sum sum= (Sum) source;

return sum.reduce(to); }

All tests pass! Really ugly! Use polymorphism rather than checking

classes explicitly.

Page 129: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 129

Refactor for the Bank.reduce(Money) test:

Bank

Money reduce(Expression source, String to) {

if (source instanceof Money)

return (Money) source.reduce(to);

Sum sum= (Sum) source;

return sum.reduce(to); }

Money

public Money reduce(String to) {

return this;

} All tests pass!

Page 130: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 130

Further Refactor for the Bank.reduce(Money) Test:

Add reduce(String) to the Expression interface,

Expression

Money reduce(String to);

Use polymorphism for reduce in Bank:

Bank

Money reduce(Expression source, String to) {

return source.reduce(to);

}

All tests pass!

Page 131: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 131

Achieved in this cycle:

Didn't mark a test as done because the duplication had not been eliminated.

Worked forward instead of backward to realize the implementation.

Wrote a test to force the creation of an object we expected to need later (Sum).

Started implementing faster (the Sum constructor).

Implemented code with casts in one place, then moved the code where it belonged once the tests were running.

Introduced polymorphism to eliminate explicit class checking.

Page 132: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 132

16. Currency exchange:

The exchange problem:

Reduce a Money with conversion – like:

“Reduce 2 francs to one dollar, if the exchange rate is 2:1”.

Start with ….. Writing a test!

$5 + 10 CHF = $10 if rate is 2:1$5 + $5 = $10Return Money from $5 + $5Bank.reduce(Money)Reduce Money with conversionMoney rounding?

Page 133: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 133

Currency Exchange Test:Reduce a Money with conversion – like:“Reduce 2 francs to one dollar, if the exchange rate is 2:1”.

public void testReduceMoneyDifferentCurrency() { Bank bank= new Bank(); bank.addRate("CHF", "USD", 2); Money result= bank.reduce(Money.franc(2), "USD");

assertEquals(Money.dollar(1), result); }

Pass the test:Money

public Money reduce(String to) { int rate = (currency.equals("CHF") && to.equals("USD"))

? 2: 1;

return new Money(amount / rate, to); } All tests pass!

Page 134: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 134

Refactor for the Currency exchange Test (1) – remove test-code duplication – the “2”:

Problem: Money knows about exchange rates!

Question: Who should know about exchange rates?

Answer: Only the Bank! Bank should be passed as an argument to Expression.reduce().

Needed changes: Caller: Bank.reduce(). Implementors: expression.reduce(), Sum.reduce(),

Money.reduce().

Page 135: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 135

Refactor for the Currency exchange Test (2) – Pass Bank as a parameter:

BankMoney reduce(Expression source, String to) {

return source.reduce(this, to); }

ExpressionMoney reduce(Bank bank, String to);

Sumpublic Money reduce(Bank bank, String to) { int amount= augend.amount + addend.amount; return new Money(amount, to); }

Moneypublic Money reduce(Bank bank, String to) {

int rate = (currency.equals("CHF") && to.equals("USD")) ? 2

: 1; return new Money(amount / rate, to);

} All tests pass!

Page 136: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 136

Refactor for the Currency exchange Test (3) – Calculate rate in bank:

Bankint rate(String from, String to) {

return (from.equals("CHF") && to.equals("USD")) ? 2 : 1;

}

Moneypublic Money reduce(Bank bank, String to) {

int rate = bank.rate(currency, to); return new Money(amount / rate, to);

} All tests pass!

Page 137: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 137

Refactor for the Currency exchange Test (4) – Use a hash table for rates:

Still there is test-code duplication (the “2”). Keep a table of rates in the Bank and look up a rate when needed. Can use a hashtable that maps pairs of currencies to rates. What should be the key to the table? – a currency pair. How to implement the key?

• Two-element array containing the two currencies:

Test if Array.equals() checks to see if the elements are equal?

public void testArrayEquals() {

assertEquals(new Object[] {"abc"}, new Object[] {"abc"});

}

Test fails! • Create a real Pair object for the key.

Page 138: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 138

Refactor for the Currency exchange Test (5) – Use a hash table for rates:

Create a real Pair object for the key. Because we are using Pairs as keys, we have to implement equals() and

hashCode().

Pairprivate class Pair {

private String from; private String to; public Pair(String from, String to) {

this.from= from; this.to= to; }

public boolean equals(Object object) { Pair pair= (Pair) object; return from.equals(pair.from) && to.equals(pair.to);

} public int hashCode() {

return 0; // A terrible hash value!}

}

Note: Implementation without a test! – forgive us!

Page 139: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 139

Refactor for the Currency exchange Test (6) – Add the rates table to the Bank:

1. Store the rates:

Bank

private Hashtable rates= new Hashtable();

2. Set the rate when told:

Bank

void addRate(String from, String to, int rate) {

rates.put(new Pair(from, to), new Integer(rate));

}

3. Look up the rate when asked:

Bank

int rate(String from, String to) {

Integer rate= (Integer) rates.get(new Pair(from, to));

return rate.intValue();

} Tests fail!

Page 140: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 140

Refactor for the Currency exchange Test (7) – Missing:handling self exchange rate:

1. Write a test:

public void testIdentityRate() {

assertEquals(1, new Bank().rate("USD", "USD"));

}

2. Pass it:

Bank

int rate(String from, String to) {

if (from.equals(to)) return 1;

Integer rate= (Integer) rates.get(new Pair(from, to));

return rate.intValue();

}

All tests pass!

Page 141: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 141

Achievements in this cycle:

Added a parameter, in seconds, that we expected we would need.

Factored out the data duplication between code and tests.

Wrote a test (testArrayEquals) to check an assumption about the operation of Java.

Introduced a private helper class without distinct tests of its own.

Made a mistake in a refactoring and chose to forge ahead, writing another test to isolate the problem.

Page 142: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 142

17. Mixed Currencies:

$5 + 10 CHF = $10 if rate is 2:1$5 + $5 = $10Return Money from $5 + $5Bank.reduce(Money)Reduce Money with conversionMoney rounding?

Page 143: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 143

Mixed Currencies test:

public void testMixedAddition() {

Expression fiveBucks= Money.dollar(5);

Expression tenFrancs= Money.franc(10);

Bank bank= new Bank();

bank.addRate("CHF", "USD", 2);

Money result= bank.reduce(fiveBucks.plus(tenFrancs), "USD");

assertEquals(Money.dollar(10), result); }

• The test does not compile – because of Money - Expression disagreements.

• Approach: Simplify the test and then generalize.

Page 144: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 144

A Simpler Mixed Currencies test:

public void testMixedAddition() {

Money fiveBucks= Money.dollar(5);

Money tenFrancs= Money.franc(10);

Bank bank= new Bank();

bank.addRate("CHF", "USD", 2);

Money result= bank.reduce(fiveBucks.plus(tenFrancs), "USD");

assertEquals(Money.dollar(10), result); }

• The test compiles but fails: result is $15.

• As if Sum.reduce() is not reducing its arguments.

Page 145: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 145

Get Sum to reduce its components:

The older Sum.reduce():Sumpublic Money reduce(Bank bank, String to) {

int amount= augend.amount + addend.amount; return new Money(amount, to);

} The new Sum.reduce:

Sumpublic Money reduce(Bank bank, String to) {

int amount= augend.reduce(bank, to).amount + addend.reduce(bank, to).amount;

return new Money(amount, to); }

All tests pass!

Page 146: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 146

Replacing Moneys by Expressions (1):

Method: from “leaves” upwards.

Sum

Expression augend;

Expression addend;

Arguments to Sum constructor:

Sum

Sum(Expression augend, Expression addend) {

this.augend= augend;

this.addend= addend;

} All tests pass!

Page 147: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 147

Replacing Moneys by Expressions (2):

Money

Expression plus(Expression addend) {

return new Sum(this, addend);

}

Arguments to Sum constructor:

Money

Expression times(int multiplier) {

return new Money(amount * multiplier, currency);

} All tests pass!

Page 148: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 148

Replacing Moneys by Expressions (3):

Get back the original mixed addition test:public void testMixedAddition() {

Expression fiveBucks= Money.dollar(5); Expression tenFrancs= Money.franc(10); Bank bank= new Bank(); bank.addRate("CHF", "USD", 2);Money result= bank.reduce(fiveBucks.plus(tenFrancs), "USD"); assertEquals(Money.dollar(10), result); }

Actually: Do it one by one – it’s TDD!1. Change the type of tenFrancs – All tests pass!2. Change the type of fiveBucks -- Test fails!

– plus() is not defined for expression.

Page 149: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 149

Replacing Moneys by Expressions (4):

ExpressionExpression plus(Expression addend);

Moneypublic Expression plus(Expression addend) {

return new Sum(this, addend); }

Fake the implementation in Sum and add it to the next to-do:Sum

public Expression plus(Expression addend) { return null;

} All tests pass!

Page 150: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 150

Achievements in this cycle:

Wrote the test we wanted, then backed off to make it achievable in one step.

Generalized (used a more abstract declaration) from the leaves back to the root (the test case).

Followed the compiler when we made a change (Expression fiveBucks), which caused changes to ripple (added plus() to Expression, and so on).

Page 151: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 151

18. Abstraction, finally!

$5 + 10 CHF = $10 if rate is 2:1$5 + $5 = $10Return Money from $5 + $5Bank.reduce(Money)Reduce Money with conversionMoney rounding?Sum.plusExpression.times

Expression (and therefore Sum) still needs plus() and times():

Page 152: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 152

Test for Sum.plus():

public void testSumPlusMoney() { Expression fiveBucks= Money.dollar(5); Expression tenFrancs= Money.franc(10); Bank bank= new Bank(); bank.addRate("CHF", "USD", 2); Expression sum= new Sum(fiveBucks, tenFrancs).plus(fiveBucks); Money result= bank.reduce(sum, "USD"); assertEquals(Money.dollar(15), result);

}

explicit Sum creation – for better communication!

Sum – same code as in Money:public Expression plus(Expression addend) {

return new Sum(this, addend); } All tests pass!

Page 153: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 153

Achieving Expression.times():

In order to achieve Expression.times() we need Sum.times()

$5 + 10 CHF = $10 if rate is 2:1$5 + $5 = $10Return Money from $5 + $5Bank.reduce(Money)Reduce Money with conversionMoney rounding?Sum.plusExpression.times

Page 154: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 154

Test for Sum.times():public void testSumTimes() {

Expression fiveBucks= Money.dollar(5); Expression tenFrancs= Money.franc(10); Bank bank= new Bank(); bank.addRate("CHF", "USD", 2); Expression sum= new Sum(fiveBucks, tenFrancs).times(2); Money result= bank.reduce(sum, "USD"); assertEquals(Money.dollar(20), result);

}

SumExpression times(int multiplier) {

return new Sum(augend.times(multiplier),addend.times(multiplier)); }

Times() must be defined in Expression -- augend and addend are Expressions:Expression

Expression times(int multiplier); All tests pass!

Page 155: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 155

Achieving Money + Money is a Money:

$5 + 10 CHF = $10 if rate is 2:1$5 + $5 = $10Return Money from $5 + $5Bank.reduce(Money)Reduce Money with conversionMoney rounding?Sum.plusExpression.times

Page 156: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 156

Test for Money + Money = Money:

public void testPlusSameCurrencyReturnsMoney() {

Expression sum= Money.dollar(1).plus(Money.dollar(1)); assertTrue(sum instanceof Money);

}

An ugly test – tests the implementation and not the observable!

Code to modify:

Money

public Expression plus(Expression addend) {

return new Sum(this, addend);

}

Test fails! We hate it! we cross it! All tests pass!

Page 157: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 157

The end:

$5 + 10 CHF = $10 if rate is 2:1$5 + $5 = $10Return Money from $5 + $5Bank.reduce(Money)Reduce Money with conversionMoney rounding?Sum.plusExpression.times

Page 158: Software Engineering, 2005Test-Driven Development 1 Test-Driven Development Software Engineering Test-Driven Development Mira Balaban Department of Computer.

Software Engineering, 2005 Test-Driven Development 158

Summary – 3 basics of TDD:

The three approaches to making a test work cleanly—fake it, triangulation, and obvious implementation.

Removing duplication between test and code as a way to drive the design.

The ability to control the gap between tests to increase traction when the road gets slippery and cruise faster when conditions are clear.