You are on page 1of 148

Agile/XP Overview

Object Mentor, Inc.

www.objectmentor.com

Robert C. Martin

www.junit.org fitnesse.org
Copyright 1998-2005 by Object Mentor, Inc All Rights Reserved

How do you manage a software project?


Badly? Hope and prayer? With Great Difficulty Dictate and Motivate?`

Mismanagement of a project leads to:


Producing the wrong product. Producing a product of inferior quality. Being late. Working 80 hour weeks.

The inevitable trade-off


Good (Quality) Fast (Time to Market) Cheap (Cost Effectiveness) Done

Pick any three

Finding the optimum solution


We need to manage the project to the best possible outcome.
An outcome that maximizes all four qualities.

To do this, we need:

Data
5

6
Story Points 10 20 30 40 50 60 0

1/ 20 /2 00 3 1/ 27 /2 00 3 2/ 3/ 20 03
Velocity

Wouldnt this be great?

2/ 10 /2 00 3 2/ 17 /2 00 3 2/ 24 /2 00 3 3/ 3/ 20 03 3/ 10 /2 00 3

7
Story Points 100 200 300 400 500 600 0

and this

1/ 20 /2 00 3 1/ 27 /2 00 3 2/ 3/ 20 03 2/ 10 /2 00 3 2/ 17 /2 00 3 2/ 24 /2 00 3 3/ 3/ 20 03 3/ 10 /2 00 3
Story Points Remaining

If we had these two charts on the wall

Velocity 60 Story Points

Story Points Remaining 600 Story Points 500 400 300 200 100 0

50 40 30 20 10 0

00

00

00

/2 00

00

/2 00

00

00

00

00

00

00

00

2/ 3/ 20 0

1/ 20 /2

1/ 27 /2

2/ 17 /2

2/ 24 /2

2/ 10 /2

3/ 3/ 20 0

0/ 2

7/ 2

7/ 2

4/ 2

0/ 2

0/ 2

Then managers could just look at them to see The status of the project.
8

3/ 10 /2

2/ 3

2/ 1

2/ 1

3/ 3

3/ 1

1/ 2

1/ 2

2/ 2

00

The Management Paradox

What is the first thing known about a project?

! !
10

! !

The Delivery Date is Frozen

11

The Spec is Never Frozen

The Spec V1

The Spec V2

The Spec V3

The Spec V3

The Spec VN.1

The Spec VN.2

12

The Waterfall Model


Managing the Development of Large Software Systems
Dr. Winston W. Royce 1970
1 May Analysis Design Implementation DFD ERD DD ST 1 Jul 1 Sep 1 Nov

13

Royces actual diagram

14

Royces Observation

15

Royces Conclusion

16

How did W/F get to be the norm?


DoD asked a Tech Writer to create 2167 Countries and Companies copied the good ol DoD.

17

Requirements and Failure


$37B worth of DoD projects using 2167A

Jarzombek Study.

Required extensive rework to meet true needs. 20%

Never used. Egregiously failed to meet needs. 46%

Failure attributed to use of waterfall.

18

Over specification
Actual use of Waterfall Requested Features
Never 45%

Always 7%

Often 13%

Rarely 19%

Sometimes 16%

19

Long Projects Fail


Project Success. 23,000 projects
45 40 35 Percent success 30 25 20 15 10 5 0 6 9 12 Months 18 24 36

20

Object Mentors Second Law of Agile Development

...the document-driven, specify-then-build approach... lies at the heart of so many... software problems.
Fred Brooks in his 1987 Report of the Defense Science Board Task Force on Military Software

21

Lets go to a meeting
1 May Analysis Design Implementation DFD ERD DD ST 1 Jul 1 Sep 1 Nov

22

Iterative Development
Exploration

UI Comms
Some UI, Comms, and Control for some behavior

Control
Slices cut across all sub-systems

Data is generated and used to calibrate the plan

23

Calculate the Date


High level analysis and Design

Slices cut across all sub-systems

. . . . . . . .

The Calculated Date

24

More data shrinks the error bars


High level analysis and Design

Slices cut across all sub-systems

. . . . . . . .

The Calculated Date

25

Without data, all you can manage is:

! !
26

Without data managers can:


This project will be done on time! Or HEADS will ROLL!

27

OR.
You guys are great. I have faith in you. I know you can do it! I sure hope you can

28

29
Story Points

1/ 2 0/ 2
10 20 30 40 50 60 0

00 3

1/ 2 7/ 2 00 3

2/ 3 /2 00 3 2/ 1 0/ 2
Velocity

00 3

2/ 1 7/ 2 00 3 4/ 2 00 3

2/ 2

Managers can.
3/ 3 /2 00 3 3/ 1 0/ 2 00 3

Story Points

1/ 20 /2 1/ 27 /2

100 0

200

300

400

500

600

00

3 00 2/ 3/ 20 0 2/ 10 /2 2/ 17 /2 2/ 24 /2

But when we have data

3 00

3 00

Story Points Remaining

3 00 3/ 3/ 20 0 3/ 10 /2

3 00

manage

The control knobs of project mgt


Schedule

Staff

Scope

Quality

30

Agile Software Development Practices


Whole Team Continuous Integration

Test Driven Development

Collective Ownership

Acceptance Tests

Pairing Simple Design

Refactoring

Planning Game

Metaphor

Sustainable Pace Small Releases

31

User Stories

Stories
Name of a feature, or short description Brief specifications of customer requirements
Is a token for a conversation

Owned by the customer A good user story criteria


Has business value Is estimable Is testable

Return Car

33

Story Estimation
Team assigns a estimate or price to each story Estimates are in dimensionless but proportional units
A 2 takes twice as much effort as a 1

Customer uses price to juggle priorities and acquire resources Unknowns can be eliminated through use of spikes, a research story

34

User Stories Define the Project Backlog

35

Agile Planning
User Stories Release Planning Iteration Planning

The Purpose of Planning


It is more important to work on the most valuable things first than to predict the future.

Even the best plans must be continually refined Requires continuous and meaningful feedback

37

Object Mentors Third Law of Agile Development

In preparing for battle I have always found that plans are useless, but planning is indispensable.
Dwight David Eisenhower

38

Exploration
Attendees
Whole team

Input
Product Vision Initial Story List (verbal) Critical dates

Output on Flip Chart Sheets


Deck of initial stories. Stakeholder Identification Customer Team Identification Architectural Vision Initial Estimates Velocity guestimate

39

Release Planning
Attendees
Whole team (as appropriate)

Inputs
Stack of estimated stories Team Velocity Critical dates or initial release functionality

Outputs
Release plan series of iterations, with significant dates

40

Release Planning
The 4-way Decision.
Important Expensive Important Cheap

Unimportant Expensive

Unimportant Cheap

41

Source: Object Mentor Training

Managing Scope
USER STORY USER STORY USER STORY

USER STORY USER STORY USER STORY USER STORY USER STORY USER

USER

USER

STORY STORY USER USER STORY USER STORY USER STORY STORY USER USER STORY USER STORY USER STORY STORY USER USER STORY USER STORY USER STORY STORY USER USER STORY USER STORY USER STORY STORY

Higher Business Value

STORY USER STORY USER STORY USER STORY

Get

USER STORY

USER STORY

USER STORY

Lower Business Value 42

Dont get (yet)

Release Planning Summary


Customers write stories Programmers estimate cost of stories Customers prioritize based on business value and estimated costs Release velocity: number of story points completed Yesterdays weather

43

Release Plan

Release 1
44

Release 2

Iteration Planning
Monday Morning:
Confirm the stories for the iteration

Revise estimates based on new information For each story


Write story on top of flip chart page Review acceptance tests Break story into engineering tasks Do quick design session as needed Sign up.
45

Iteration Planning Summary


Developers break stories into tasks Developers sign up for stories. Customers may need to change, split, merge stories to fit iteration
Once in the iteration, the stories are fixed

Iteration velocity: number of story points completed Never extend the iteration date
46

Iteration Tracking Bulletin Board


Selected Started Done? Accepted

47

Iteration Tracking Bulletin Board


Selected Started Done? Accepted

48

How do you know a story is done?


Working Code Passing Tests

49

Writing Automated Acceptance Tests


Object Mentor, Inc.

Copyright 1998-2001 by Object Mentor, Inc All Rights Reserved

The Role of QA in an Agile Project

The Traditional schedule for Quality.


Waiting at the sphincter muscle.

1 May Analysis

1 Jul

1 Sep

1 Nov

Design Implementation Test

52

When Quality comes at the end.


It is under the most pressure. It has the least flexibility. It is a high stress tedious job. It is error prone.

Quality cannot be tested in.

53

The Agile View: Quality is a specification role


not a verification role!

A feature is not specified


Until its acceptance test is written.

Who Writes Acceptance Tests?


Business Analysts
Happy path.

QA Test Writers, and Testers


Corner and boundary cases.
Try to break it.

56

Acceptance Tests Are

Automated
written in a very high-level language executed frequently written by the stakeholders

57

Imagine you have a button to press that would tell you if the system worked

How often would you press it?


58

It becomes a conditioned response

59

When is the best time to write Automated Acceptance Tests?

At the start of each iteration.


Exploration

Slices cut across all sub-systems

Test
61

Tests specify each iteration.

Iteration

Acceptance Tests

Defines doneness.

62

Tests specify each iteration.

Iteration

Acceptance Tests

Defines doneness.

63

How do you know a feature is done?

A feature is not done


Until all its acceptance tests pass

66
Story Points

1/ 2 0/ 2
10 20 30 40 50 60 0

00 3

1/ 2 7/ 2 00 3

2/ 3 /2 00 3 2/ 1 0/ 2
Velocity

00 3

2/ 1 7/ 2 00 3 4/ 2 00 3

2/ 2

3/ 3 /2 00 3 3/ 1 0/ 2 00 3

Story Points

1/ 20 /2 1/ 27 /2

100 0

200

300

400

500

600

00

3 00 2/ 3/ 20 0 2/ 10 /2 2/ 17 /2 2/ 24 /2

3 00

3 00

Story Points Remaining

3 00 3/ 3/ 20 0 3/ 10 /2

Knowing when a feature is done allows...

3 00

These features have passed their Automated Acceptance Tests.


3

What do they look like?

A Simple Example

The result

When is the best time to write AATs?


Astute test managers
Talk to the stakeholders days before each iteration Write initial acceptance tests for the features they plan to schedule Elaborate those acceptance tests once scheduled

70

When is the best time to run them?


Continuously At every check-in No change is allowed to break any passing tests Continuous Integration

71

Who runs them?


Developers Testers Managers Stakeholders They are run automatically Results are displayed on the wall

72

73
Story Points

1/ 2 0/ 2
10 20 30 40 50 60 0

00 3

1/ 2 7/ 2 00 3

2/ 3 /2 00 3 2/ 1 0/ 2
Velocity

00 3

2/ 1 7/ 2 00 3 4/ 2 00 3

2/ 2

and actionable
3/ 3 /2 00 3 3/ 1 0/ 2 00 3

Story Points

1/ 20 /2 1/ 27 /2

100 0

200

300

400

500

600

00

3 00 2/ 3/ 20 0 2/ 10 /2 2/ 17 /2 2/ 24 /2

3 00

3 00

Story Points Remaining

3 00 3/ 3/ 20 0 3/ 10 /2

This keeps the data accurate

3 00

Evolution: Manual Tests


Used for lack of any better approach Are better than nothing Terribly inefficient In a crunch they are not run Unfortunately still among the most commonly used approaches

74

Evolution: Ad Hoc Text-based


CSV or Tab delimited files Allowed for automation of tests Do not scale well Very unconventional Everyone used a different scheme Not very customer friendly

75

Evolution: XML-based
Still automated Scalable Visions of frameworks began XML introduces extra complexity Not customer friendly

76

GUI Testers
Mercury Robot Canoo WebRunner etc.

77

Testing through GUI is a trap


We have a client with 15,000 acceptance tests run through the GUI. Old DOS-based GUI. Cant update GUI because tests would fail. Stuck!

78

FitNesse
The solution we all agreed upon.

Writing Requirements as Tests

Requirements are written with example tables The tables are really tests Writing tests as a table is an interesting paradigm Some tests naturally fit into tables Some tests require thought to put them in the form of a table

80

The Mechanics
The first row of a FIT table is the title of the table This title is the name of something called a fixture Fixtures are simple programs written by the developers to connect the application to the data in the table.

81

The Mechanics
When you run a test the fixture gathers the data from the table and calls the appropriate functions in the application The fixture then gathers data from the application and compares them with entries in the table, turning them green or red

82

Acceptance Testing with Fit/FitNesse


Fit Fixtures UI The Application Interface Fit/FitNesse

The Application Logic


83

The Test-Bus Imperative

Many Industries build testability in


Telecommunications Manufacturing Electronic Hardware

85

But not the software industry


Testability is almost always an afterthought

If it is thought of at all

86

Untestable Dependencies
Too often applications directly depend on external and third party resources
Networ k

READY

87

Dependency Management
Interfaces isolate the system from external dependencies
Networ k

READY

88

Testability!

Tests and Simulations

Tests and Simulations

89

Tests and Simulations

Measuring Project Status

Project Management Tool


When done properly, Acceptance Tests produce valuable data

100 90 80 70 60 50 40 30 20 10 0 1 3 5 7 9 11

Total AT's Failing AT's Passing AT's

91

92
Story Points

1/ 2 0/ 2
10 20 30 40 50 60 0

00 3

1/ 2 7/ 2 00 3

2/ 3 /2 00 3 2/ 1 0/ 2
Velocity

00 3

2/ 1 7/ 2 00 3 4/ 2 00 3

2/ 2

and actionable
3/ 3 /2 00 3 3/ 1 0/ 2 00 3

Story Points

1/ 20 /2 1/ 27 /2

100 0

200

300

400

500

600

00

3 00 2/ 3/ 20 0 2/ 10 /2 2/ 17 /2 2/ 24 /2

3 00

3 00

Story Points Remaining

3 00 3/ 3/ 20 0 3/ 10 /2

ATs keep the PM data accurate

3 00

Iteration Management

93

Agile Software Development Practices


Customer Collaboration Continuous Integration

Test Driven Development

Collective Ownership

Acceptance Tests

Pairing Simple Design

Refactoring

Scrum

Metaphor

Sustainable Pace Small Releases

94

Bowling Game Kata

Copyright 2005 by Object Mentor, Inc All copies must retain this page unchanged.

Scoring Bowling.
The game consists of 10 frames as shown above. In each frame the player has two opportunities to knock down 10 pins. The score for the frame is the total number of pins knocked down, plus bonuses for strikes and spares. A spare is when the player knocks down all 10 pins in two tries. The bonus for that frame is the number of pins knocked down by the next roll. So in frame 3 above, the score is 10 (the total number knocked down) plus a bonus of 5 (the number of pins knocked down on the next roll.) A strike is when the player knocks down all 10 pins on his first try. The bonus for that frame is the value of the next two balls rolled. In the tenth frame a player who rolls a spare or strike is allowed to roll the extra balls to complete the frame. However, no more than three balls can be rolled in the tenth frame.

96

The Requirements.
Game + roll(pins : int) + score() : int

Write a class named Game that has two methods


roll(pins : int) is called each time the player rolls a ball. The argument is the number of pins knocked down. score() : int is called only at the very end of the game. It returns the total score for that game.
97

A quick design session


Game + roll(pins : int) + score() : int

Clearly we need the Game class.

98

A quick design session


Game + roll(pins : int) + score() : int 10 Frame

A game has 10 frames.

99

A quick design session


Game + roll(pins : int) + score() : int 10 Frame 1..2 Roll - pins : int

A frame has 1 or two rolls.

100

A quick design session


Game + roll(pins : int) + score() : int 10 Frame 1..2 Roll - pins : int 1

Tenth Frame

The tenth frame has two or three rolls. It is different from all the other frames.

101

A quick design session


Game + roll(pins : int) + score() : int 10 Frame + score() : int - pins : int 1 1..2 Roll

The score function must iterate through all the frames, and calculate all their scores.

Tenth Frame

102

A quick design session


next frame

The score for a spare or a strike depends on the frames successor


1..2 Roll - pins : int 1

Game + roll(pins : int) + score() : int

10

Frame + score() : int

Tenth Frame

103

Begin
Create a project named BowlingGame Create a unit test named BowlingGameTest

import junit.framework.TestCase; public class BowlingGameTest extends TestCase { }

104

Begin.
Create a project named BowlingGame Create a unit test named BowlingGameTest

import junit.framework.TestCase; public class BowlingGameTest extends TestCase { }

Execute this program and verify that you get the following error:
No tests found in BowlingGameTest

105

The first test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { public void testGutterGame() throws Exception { Game g = new Game(); } }

106

The first test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { public void testGutterGame() throws Exception { Game g = new Game(); } } public class Game { }

107

The first test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { public void testGutterGame() throws Exception { Game g = new Game(); } } public class Game { }

108

The first test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { public void testGutterGame() throws Exception { Game g = new Game(); for (int i=0; i<20; i++) g.roll(0); } } public class Game { }

109

The first test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { public void testGutterGame() throws Exception { Game g = new Game(); for (int i=0; i<20; i++) g.roll(0); } } public class Game { public void roll(int pins) { } }

110

The first test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { public void testGutterGame() throws Exception { Game g = new Game(); for (int i=0; i<20; i++) g.roll(0); assertEquals(0, g.score()); } } public class Game { public void roll(int pins) { } }

111

The first test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { public void testGutterGame() throws Exception { Game g = new Game(); for (int i=0; i<20; i++) g.roll(0); assertEquals(0, g.score()); } } public class Game { public void roll(int pins) { } public int score() { return -1; } }

expected:<0> but was:<-1>

112

The first test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { public void testGutterGame() throws Exception { Game g = new Game(); for (int i=0; i<20; i++) g.roll(0); assertEquals(0, g.score()); } } public class Game { public void roll(int pins) { } public int score() { return 0; } }

113

The second test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { public void testGutterGame() throws Exception { Game g = new Game(); for (int i = 0; i < 20; i++) g.roll(0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { Game g = new Game(); for (int i = 0; i < 20; i++) g.roll(1); assertEquals(20, g.score()); } } public class Game { public void roll(int pins) { } public int score() { return 0; } }

114

The second test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { public void testGutterGame() throws Exception { Game g = new Game(); for (int i = 0; i < 20; i++) g.roll(0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { Game g = new Game(); for (int i = 0; i < 20; i++) g.roll(1); assertEquals(20, g.score()); } }

creation is duplicated - roll loop is duplicated


- Game

public class Game { public void roll(int pins) { } public int score() { return 0; } }

115

The second test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { public void testGutterGame() throws Exception { Game g = new Game(); for (int i = 0; i < 20; i++) g.roll(0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { Game g = new Game(); for (int i = 0; i < 20; i++) g.roll(1); assertEquals(20, g.score()); }

creation is duplicated - roll loop is duplicated


- Game

public class Game { public void roll(int pins) { } public int score() { return 0; } }

expected:<20> but was:<0>

116

- roll loop is duplicated

The Second test.


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } public void testGutterGame() throws Exception { for (int i = 0; i < 20; i++) g.roll(0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { for (int i = 0; i < 20; i++) g.roll(1); assertEquals(20, g.score()); } } } public class Game { private int score = 0; public void roll(int pins) { score += pins; } public int score() { return score; }

117

- roll loop is duplicated

The second test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } public void testGutterGame() throws Exception { int n = 20; int pins = 0; for (int i = 0; i < n; i++) { g.roll(pins); } assertEquals(0, g.score()); } public void testAllOnes() throws Exception { for (int i = 0; i < 20; i++) g.roll(1); assertEquals(20, g.score()); } } } public class Game { private int score = 0; public void roll(int pins) { score += pins; } public int score() { return score; }

118

- roll loop is duplicated

The second test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } public void testGutterGame() throws Exception { int n = 20; int pins = 0; rollMany(n, pins); assertEquals(0, g.score()); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testAllOnes() throws Exception { for (int i = 0; i < 20; i++) g.roll(1); assertEquals(20, g.score()); } } } public class Game { private int score = 0; public void roll(int pins) { score += pins; } public int score() { return score; }

119

- roll loop is duplicated

The second test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testAllOnes() throws Exception { for (int i = 0; i < 20; i++) g.roll(1); assertEquals(20, g.score()); } } public class Game { private int score = 0; public void roll(int pins) { score += pins; } public int score() { return score; } }

120

- roll loop is duplicated

The second test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } } } public class Game { private int score = 0; public void roll(int pins) { score += pins; } public int score() { return score; }

121

The second test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } } public class Game { private int score = 0; public void roll(int pins) { score += pins; } public int score() { return score; } }

122

- ugly comment in test.

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } public class Game { private int score = 0; public void roll(int pins) { score += pins; } public int score() { return score; } }

123

- ugly comment in test.

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } public class Game { private int score = 0; public void roll(int pins) { score += pins; } public int score() { return score; } }

124

expected:<16> but was:<13>

- ugly comment in test

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } public class Game { private int score = 0;

tempted to use flag to remember previous roll. So design must be public void roll(int pins) { wrong.
score += pins; } public int score() { return score; }

125

- ugly comment in test

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } public class Game { private int score = 0; roll() calculates score, but name does not imply that. public void roll(int pins) { score += pins; score() does not calculate score, but } name implies that it does. public int score() { return score; } }

Design is wrong. Responsibilities are misplaced.

126

- ugly

comment in test

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } // // // // // // // } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } public class Game { private int score = 0; public void roll(int pins) { score += pins; } public int score() { return score; } }

127

- ugly comment in test

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } // // // // // // // } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } public class Game { private int score = 0; private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { score += pins; rolls[currentRoll++] = pins; } public int score() { return score; }

128

- ugly comment in test.

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } // // // // // // // } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } public class Game { private int score = 0; private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { score += pins; rolls[currentRoll++] = pins; } public int score() { int score = 0; for (int i = 0; i < rolls.length; i++) score += rolls[i]; return score; } }

129

- ugly

comment in test.

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } // // // // // // // } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; for (int i = 0; i < rolls.length; i++) score += rolls[i]; return score; }

130

- ugly comment in test

The Third test.


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; for (int i = 0; i < rolls.length; i++) score += rolls[i]; return score; }

131

expected:<16> but was:<13>

- ugly comment in test.

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; for (int i = 0; i < rolls.length; i++) { if (rolls[i] + rolls[i+1] == 10) // spare score += ... score += rolls[i]; } return score; } }

This isnt going to work because i might not refer to the first ball of the frame. Design is still wrong. Need to walk through array two balls (one frame) at a time.

132

- ugly comment in test.

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } // // // // // // // } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; for (int i = 0; i < rolls.length; i++) score += rolls[i]; return score; }

133

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } // // // // // // // } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); }

- ugly comment in test.

public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int i = 0; for (int frame = 0; frame < 10; frame++) score += rolls[i] + rolls[i+1]; i += 2; } return score; } }

134

- ugly comment in test

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int i = 0; for (int frame = 0; frame < 10; frame++) score += rolls[i] + rolls[i+1]; i += 2; } return score; } }

135

expected:<16> but was:<13>

- ugly comment in test

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int i = 0; for (int frame = 0; frame < 10; frame++) { if (rolls[i] + rolls[i + 1] == 10) // spare { score += 10 + rolls[i + 2]; i += 2; } else { score += rolls[i] + rolls[i + 1]; i += 2; } } return score; }

136

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } }

-ugly comment in test. -ugly comment in conditional. -i is a bad name for this variable

public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int i = 0; for (int frame = 0; frame < 10; frame++) { if (rolls[i] + rolls[i + 1] == 10) // spare { score += 10 + rolls[i + 2]; i += 2; } else { score += rolls[i] + rolls[i + 1]; i += 2; } } return score; }

137

-ugly comment in test. -ugly comment in conditional.

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int frameIndex = 0; for (int frame = 0; frame < 10; frame++) { if (rolls[frameIndex] + rolls[frameIndex + 1] == 10) // spare { score += 10 + rolls[frameIndex + 2]; frameIndex += 2; } else { score += rolls[frameIndex] + rolls[frameIndex + 1]; frameIndex += 2; } } return score; } }

138

-ugly comment in test.

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { g.roll(5); g.roll(5); // spare g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int frameIndex = 0; for (int frame = 0; frame < 10; frame++) { if (isSpare(frameIndex)) { score += 10 + rolls[frameIndex + 2]; frameIndex += 2; } else { score += rolls[frameIndex] + rolls[frameIndex + 1]; frameIndex += 2; } } return score; } private boolean isSpare(int frameIndex) { return rolls[frameIndex] + rolls[frameIndex + 1] == 10; } }

139

The third test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { private Game g; protected void setUp() throws Exception { g = new Game(); } private void rollMany(int n, int pins) { for (int i = 0; i < n; i++) g.roll(pins); } public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { rollSpare(); g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } private void rollSpare() { g.roll(5); g.roll(5); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int frameIndex = 0; for (int frame = 0; frame < 10; frame++) { if (isSpare(frameIndex)) { score += 10 + rolls[frameIndex + 2]; frameIndex += 2; } else { score += rolls[frameIndex] + rolls[frameIndex + 1]; frameIndex += 2; } } return score; } private boolean isSpare(int frameIndex) { return rolls[frameIndex] + rolls[frameIndex + 1] == 10; } }

140

- ugly comment in testOneStrike.

The fourth test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { ... public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { rollSpare(); g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } public void testOneStrike() throws Exception { g.roll(10); // strike g.roll(3); g.roll(4); rollMany(16, 0); assertEquals(24, g.score()); } } private void rollSpare() { g.roll(5); g.roll(5); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int frameIndex = 0; for (int frame = 0; frame < 10; frame++) { if (isSpare(frameIndex)) { score += 10 + rolls[frameIndex + 2]; frameIndex += 2; } else { score += rolls[frameIndex] + rolls[frameIndex + 1]; frameIndex += 2; } } return score; } private boolean isSpare(int frameIndex) { return rolls[frameIndex] + rolls[frameIndex + 1] == 10; }

141

expected:<24> but was:<17>

The fourth test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { ... public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { rollSpare(); g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } public void testOneStrike() throws Exception { g.roll(10); // strike g.roll(3); g.roll(4); rollMany(16, 0); assertEquals(24, g.score()); } private void rollSpare() { g.roll(5); g.roll(5); } } }

-ugly comment in testOneStrike. -ugly comment in conditional. -ugly expressions.

public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int frameIndex = 0; for (int frame = 0; frame < 10; frame++) { if (rolls[frameIndex] == 10) // strike { score += 10 + rolls[frameIndex+1] + rolls[frameIndex+2]; frameIndex++; } else if (isSpare(frameIndex)) { score += 10 + rolls[frameIndex + 2]; frameIndex += 2; } else { score += rolls[frameIndex] + rolls[frameIndex + 1]; frameIndex += 2; } } return score; } private boolean isSpare(int frameIndex) { return rolls[frameIndex] + rolls[frameIndex + 1] == 10; }

142

-ugly comment in testOneStrike. -ugly comment in conditional.

The fourth test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { ... public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { rollSpare(); g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } public void testOneStrike() throws Exception { g.roll(10); // strike g.roll(3); g.roll(4); rollMany(16, 0); assertEquals(24, g.score()); } private void rollSpare() { g.roll(5); g.roll(5); } } private boolean isSpare(int frameIndex) { return rolls[frameIndex]+rolls[frameIndex+1] == 10; } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int frameIndex = 0; for (int frame = 0; frame < 10; frame++) { if (rolls[frameIndex] == 10) // strike { score += 10 + strikeBonus(frameIndex); frameIndex++; } else if (isSpare(frameIndex)) { score += 10 + spareBonus(frameIndex); frameIndex += 2; } else { score += sumOfBallsInFrame(frameIndex); frameIndex += 2; } } return score; } private int sumOfBallsInFrame(int frameIndex) { return rolls[frameIndex]+rolls[frameIndex+1]; } private int spareBonus(int frameIndex) { return rolls[frameIndex + 2]; } private int strikeBonus(int frameIndex) { return rolls[frameIndex+1]+rolls[frameIndex+2]; }

143

-ugly comment in testOneStrike.

The fourth test


import junit.framework.TestCase; public class BowlingGameTest extends TestCase { ... public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { rollSpare(); g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } public void testOneStrike() throws Exception { g.roll(10); // strike g.roll(3); g.roll(4); rollMany(16, 0); assertEquals(24, g.score()); } private void rollSpare() { g.roll(5); g.roll(5); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int frameIndex = 0; for (int frame = 0; frame < 10; frame++) { if (isStrike(frameIndex)) { score += 10 + strikeBonus(frameIndex); frameIndex++; } else if (isSpare(frameIndex)) { score += 10 + spareBonus(frameIndex); frameIndex += 2; } else { score += sumOfBallsInFrame(frameIndex); frameIndex += 2; } } return score; } private boolean isStrike(int frameIndex) { return rolls[frameIndex] == 10; } private int sumOfBallsInFrame(int frameIndex) { return rolls[frameIndex] + rolls[frameIndex+1]; } private int spareBonus(int frameIndex) { return rolls[frameIndex+2]; } private int strikeBonus(int frameIndex) { return rolls[frameIndex+1] + rolls[frameIndex+2]; } private boolean isSpare(int frameIndex) { return rolls[frameIndex]+rolls[frameIndex+1] == 10; } }

144

The fourth test


... public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { rollSpare(); g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } public void testOneStrike() throws Exception { rollStrike(); g.roll(3); g.roll(4); rollMany(16, 0); assertEquals(24, g.score()); } private void rollStrike() { g.roll(10); } private void rollSpare() { g.roll(5); g.roll(5); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int frameIndex = 0; for (int frame = 0; frame < 10; frame++) { if (isStrike(frameIndex)) { score += 10 + strikeBonus(frameIndex); frameIndex++; } else if (isSpare(frameIndex)) { score += 10 + spareBonus(frameIndex); frameIndex += 2; } else { score += sumOfBallsInFrame(frameIndex); frameIndex += 2; } } return score; } private boolean isStrike(int frameIndex) { return rolls[frameIndex] == 10; } private int sumOfBallsInFrame(int frameIndex) { return rolls[frameIndex] + rolls[frameIndex+1]; } private int spareBonus(int frameIndex) { return rolls[frameIndex+2]; } private int strikeBonus(int frameIndex) { return rolls[frameIndex+1] + rolls[frameIndex+2]; } private boolean isSpare(int frameIndex) { return rolls[frameIndex]+rolls[frameIndex+1] == 10; } }

145

The fifth test


... public void testGutterGame() throws Exception { rollMany(20, 0); assertEquals(0, g.score()); } public void testAllOnes() throws Exception { rollMany(20,1); assertEquals(20, g.score()); } public void testOneSpare() throws Exception { rollSpare(); g.roll(3); rollMany(17,0); assertEquals(16,g.score()); } public void testOneStrike() throws Exception { rollStrike(); g.roll(3); g.roll(4); rollMany(16, 0); assertEquals(24, g.score()); } public void testPerfectGame() throws Exception { rollMany(12,10); assertEquals(300, g.score()); } private void rollStrike() { g.roll(10); } private void rollSpare() { g.roll(5); g.roll(5); } } public class Game { private int rolls[] = new int[21]; private int currentRoll = 0; public void roll(int pins) { rolls[currentRoll++] = pins; } public int score() { int score = 0; int frameIndex = 0; for (int frame = 0; frame < 10; frame++) { if (isStrike(frameIndex)) { score += 10 + strikeBonus(frameIndex); frameIndex++; } else if (isSpare(frameIndex)) { score += 10 + spareBonus(frameIndex); frameIndex += 2; } else { score += sumOfBallsInFrame(frameIndex); frameIndex += 2; } } return score; } private boolean isStrike(int frameIndex) { return rolls[frameIndex] == 10; } private int sumOfBallsInFrame(int frameIndex) { return rolls[frameIndex] + rolls[frameIndex+1]; } private int spareBonus(int frameIndex) { return rolls[frameIndex+2]; } private int strikeBonus(int frameIndex) { return rolls[frameIndex+1] + rolls[frameIndex+2]; } private boolean isSpare(int frameIndex) { return rolls[frameIndex]+rolls[frameIndex+1] == 10; } }

146

End

Contact Information
Robert C. Martin unclebob@objectmentor.com Website: www.objectmentor.com FitNesse: www.fitnesse.org

148

You might also like