Scrum and Hyper-productivity Object Mentor, Inc. Copyright 1998-2007 by Object Mentor, Inc All...

Preview:

Citation preview

Scrumand

Hyper-productivity

Object Mentor, Inc.

Copyright 1998-2007 by Object Mentor, IncAll Rights Reserved

fitnesse.org

www.objectmentor.com

www.junit.org

Robert C. Martin

2

The inevitable trade-off.

Good (Quality)

Fast (Time to Market)

Cheap (Cost Effectiveness)

Done

Pick any three…

3

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.

4

Wouldn’t this be great?

Velocity

0

10

20

30

40

50

60

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

5

…and this…

Story Points Remaining

0

100

200

300

400

500

600

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

6

If we had these two charts on the wall…

Story Points Remaining

0

100

200

300

400

500

600

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

Velocity

0

10

20

30

40

50

60

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

Then managers could just look at them to seeThe status of the project.

7

What is the first thing

known about a project?

The Management Paradox

8

! !

! !

9

The Delivery Date is Frozen

10

The

Spec

V1

The

Spec

V2

The

Spec

V3

The

Spec

V3

The

Spec

VN.1

The

Spec

VN.2

The Spec is Never Frozen

11

Analysis

Design

Implementation

DFDERD

DDST

1 May 1 Nov1 Jul 1 Sep

The Waterfall Model

Managing the Development of Large Software SystemsDr. Winston W. Royce — 1970

12

Royce’s actual diagram.

13

Royce’s Observation

14

Royce’s Conclusion

15

Analysis

Design

Implementation

DFDERD

DDST

1 May 1 Nov1 Jul 1 Sep

Let’s go to a meeting.

16

Iterative Development.

Exploration

Slices cut across all sub-systems

UI

Comms

ControlSome UI, Comms, and

Control for

some behavior

Data is generated and used to calibrate the plan

17

Calculate the Date.

High level analysis and Design

Slices cut across all sub-systems

The Calculated Date

. . . . . . . . [ ]

18

More data shrinks the error bars.

High level analysis and Design

Slices cut across all sub-systems

The Calculated Date

. . . . . . . . [ ]

19

But when we have data…

Story Points Remaining

0

100

200

300

400

500

600

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

Velocity

0

10

20

30

40

50

60

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

Managers can…. manage.

20

The control knobs of project mgt.

Schedule

Quality

Staff Scope

21

SCRUM

Small Releases

WholeTeam

PlanningGame

DailyScrum

22

This can lead to a Mess.

Messy Code is:rigid

fragile

not reusable

has high viscosity

23

Rigidity

The impact of a change cannot be predictedIf not predicted, it cannot be estimatedTime and cost cannot be quantifiedManagers become reluctant to authorize changeOfficial Rigidity for “Roach Motel” modules

Rigidity is the inability to be changed

24

Fragility

A single change requires a cascade of subsequent changesNew errors appear in areas that seem unconnected to the changed areasQuality is unpredictable.The development team loses credibility

Software changes seem to exhibit non-local effects

25

Immobility

Desirable parts of the design are dependent upon undesirable partsThe work and risk of extracting the desirable part may exceed the cost of redeveloping from scratch.

26

Viscosity

When the “right changes” are much more difficult than hacking, the viscosity of the system is high.

Over time, it will become increasingly difficult to continue developing the product.

Viscosity is resistance to fluid motion.

27

What to do?

A good SCRUM team solves this problemBy adopting corrective disciplines.

What are those disciplines?

28

Hyper-productive SCRUM

Metaphor

CollectiveOwnership

SustainablePace

ContinuousIntegration

Small Releases

Acceptance Tests

WholeTeam

PlanningGame

SimpleDesign

Pairing

Test Driven Development

Refactoring

Daily Scrum

29

The Three Laws of TDD

You cannot write any production code until you have written a failing unit test.

You cannot write more of a unit test than is sufficient to fail.

You cannot write more production code than is sufficient to pass the failing test.

30

The Litany of TDD Benefits

30 Second cycle time means less debugging.

A bevy of tests keeps the code flexible

The tests are design documents

Testable code is decoupled code.

User Stories

32

Stories

Name of a feature, or short description

Brief specifications of customer requirementsIs a token for a conversation

Owned by the customer

A good user story criteriaHas business value

Is estimable

Is testable

Return Car

33

Story EstimationTeam 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

Agile Planning

User Stories

Release Planning

Iteration Planning

36

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 Mentor’s Third Law ofAgile Development

“In preparing for battle I have always found that plans are useless, but

planning isindispensable. ”

Dwight David Eisenhower

38

Exploration

AttendeesWhole team

InputProduct VisionInitial Story List (verbal)Critical dates

Output – on Flip Chart SheetsDeck of initial stories.Stakeholder IdentificationCustomer Team IdentificationArchitectural VisionInitial EstimatesVelocity guestimate

39

Release Planning

AttendeesWhole team (as appropriate)

InputsStack of estimated stories

Team Velocity

Critical dates or initial release functionality

OutputsRelease plan – series of iterations, with significant dates

1 2 3 4 5 6 7

40

Release Planning

The 4-way Decision.

ImportantExpensive

ImportantCheap

UnimportantExpensive

UnimportantCheap

41

USER

STORY

USER

STORY

USER

STORY

Managing Scope

USER

STORY

USER

STORY

USER

STORY

USER

STORY

USER

STORY

USER

STORY

USER

STORY

USER

STORY

USER

STORYUSER

STORY

USER

STORY

USER

STORYUSER

STORY

USER

STORY

USER

STORYUSER

STORY

USER

STORY

USER

STORYUSER

STORY

USER

STORY

USER

STORYUSER

STORY

USER

STORY

USER

STORYUSER

STORY

USER

STORY

USER

STORYUSER

STORY

USER

STORY

USER

STORY

GetHigher Business Value

Lower Business Value

Source: Object Mentor Training

Don’t get (yet)

42

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

Yesterday’s weather

43

Release Plan

1 2 3 4 5 6 7

Release 1 Release 2

44

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 pageReview acceptance testsBreak story into engineering tasksDo quick design session as neededSign 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 AcceptedStarted Done?

47

Iteration Tracking Bulletin Board

Selected AcceptedStarted Done?

48

How do you know a story is done?

1 2 3 4 5 6 7

Working CodePassing Tests

Writing AutomatedAcceptance Tests

Object Mentor, Inc.

Copyright 1998-2001 by Object Mentor, IncAll Rights Reserved

The Role of QA in an Agile Project

51

The Traditional schedule for Quality.

Waiting at the sphincter muscle.

Analysis

Design

Implementation

1 May 1 Nov1 Jul 1 Sep

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.

The Agile View:

Quality is a specification role…

…not a verification role!

A feature is not specified…

Until it’s acceptance test is written.

55

Who Writes Acceptance Tests?

Business AnalystsHappy path.

QA Test Writers, and TestersCorner 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.

When is the best time to write Automated Acceptance Tests?

60

At the start of each iteration.

Exploration

Slices cut across all sub-systems

Test

61

Tests specify each iteration.

Iteration

AcceptanceTests

Defines done-ness.

62

Tests specify each iteration.

Iteration

AcceptanceTests

Defines done-ness.

How do you know a feature is done?

A feature is not done…

Until all it’s acceptance tests pass.

65

Knowing when a feature is done allows...

Story Points Remaining

0

100

200

300

400

500

600

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

Velocity

0

10

20

30

40

50

60

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

These features have passed their AutomatedAcceptance Tests.

What do they look like?

A Simple Example

The result.

69

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

This keeps the data accurate…

Story Points Remaining

0

100

200

300

400

500

600

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

Velocity

0

10

20

30

40

50

60

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

…and actionable.

73

Evolution: Manual Tests

Used for lack of any better approachAre better than nothingTerribly InefficientIn a crunch they are not runUnfortunately still among the most commonly used approach

74

Evolution: Ad Hoc Text-based

CSV or Tab delimited filesAllowed for automation of testsDoes not scale wellVery unconventionalEveryone used a different schemeNot very customer friendly

75

Evolution: XML-based

Still automatedScalableVisions of frameworks beganXML introduces extra complexityNot customer friendly

76

GUI Testers

Mercury

Robot

Canoo

WebRunnerEtc.

77

Testing through GUI is a trap.

We have a client with 15,000 acceptance tests run through the GUI.

Old DOS based GUI.

Can’t update GUI because tests would fail.

Stuck!

FitNesse

The solution we all agreed upon.

79

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 tablesSome 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 MechanicsWhen 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

The Application InterfaceUI

Fit Fixtures Fit/FitNesse

The ApplicationLogic

The Test-Bus Imperative

84

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.

Network

Network

READY

Too often applications directly depend on external and third party resources.

87

Dependency Management

Network

Network

READY

Interfaces isolate the system from external dependencies.

88

Testability!

Tests andSimulations

Tests andSimulations

Tests andSimulations

Measuring Project Status

90

Project Management Tool

When done properly, Acceptance Tests produce valuable data.

0102030405060708090

100

1 3 5 7 9 11

Total AT'sFailing AT'sPassing AT's

91

ATs keep the PM data accurate…

Story Points Remaining

0

100

200

300

400

500

600

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

Velocity

0

10

20

30

40

50

60

1/20/20031/27/20032/3/20032/10/20032/17/20032/24/20033/3/20033/10/2003

Story Points

…and actionable.

92

Iteration Management

93

Agile Software Development Practices

Metaphor

CollectiveOwnership

SustainablePace

ContinuousIntegration

Small Releases

Acceptance Tests

CustomerCollaboration

Scrum

SimpleDesign

Pairing

Test Driven Development

Refactoring

Bowling Game Kata

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

95

Scoring Bowling.

The game consists of 10 frames as shown above. In each frame the player hastwo opportunities to knock down 10 pins. The score for the frame is the totalnumber 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 forthat frame is the number of pins knocked down by the next roll. So in frame 3above, the score is 10 (the total number knocked down) plus a bonus of 5 (thenumber of pins knocked down on the next roll.)

A strike is when the player knocks down all 10 pins on his first try. The bonusfor 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 extraballs to complete the frame. However no more than three balls can be rolled intenth frame.

96

The Requirements.

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

Game

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

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

Game

Clearly we need the Game class.

98

A quick design session

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

Game Frame10

A game has 10 frames.

99

A quick design session

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

Game Frame

- pins : int

Roll10 1..2

A frame has 1 or two rolls.

100

A quick design session

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

Game Frame

Tenth Frame

- pins : int

Roll10 1..2

1

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

101

A quick design session

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

Game

+ score() : int

Frame

Tenth Frame

- pins : int

Roll10 1..2

1The score function mustiterate through all theframes, and calculateall their scores.

102

A quick design session

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

Game

+ score() : int

Frame

Tenth Frame

- pins : int

Roll10 1..2

1

next frame The score for a spare or a strike depends on the frame’s successor

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()); }}

public class Game { public void roll(int pins) { }

public int score() { return 0; }}

- Game creation is duplicated- roll loop is duplicated

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()); }}

public class Game { public void roll(int pins) { }

public int score() { return 0; }}

- Game creation is duplicated- roll loop is duplicated

expected:<20> but was:<0>

116

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; }}

- roll loop is duplicated

117

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; }}

- roll loop is duplicated

118

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; }}

- roll loop is duplicated

119

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; }}

- roll loop is duplicated

120

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; }}

- roll loop is duplicated

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

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; }}

- ugly comment in test.

123

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; }}

- ugly comment in test.

expected:<16> but was:<13>

124

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; }}

- ugly comment in test.

tempted to use flag to remember previous roll. So design must be wrong.

125

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; }}

- ugly comment in test.

roll() calculates score, but name does not imply that.

score() does not calculate score, but name implies that it does.

Design is wrong. Responsibilities are misplaced.

126

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; }}

- ugly comment in test.

127

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; }}

- ugly comment in test.

128

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; }}

- ugly comment in test.

129

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; }}

- ugly comment in test.

130

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; }}

expected:<16> but was:<13>

- ugly comment in test.

131

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 isn’t 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.

- ugly comment in test.

132

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; }}

- ugly comment in test.

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());// }}

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; }}

- ugly comment in test.

134

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; }}

expected:<16> but was:<13>

- ugly comment in test.

135

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; }}

- ugly comment in test.

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()); }}

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; }}

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

137

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; }}

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

138

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; }}

-ugly comment in test.

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

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; }}

- ugly comment in testOneStrike.

expected:<24> but was:<17>

141

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 (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; }}

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

142

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 (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]; }

private boolean isSpare(int frameIndex) { return rolls[frameIndex]+rolls[frameIndex+1] == 10; }}

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

143

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; }}

-ugly comment in testOneStrike.

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; }}

End

147

Contact Information

Robert C. Martinunclebob@objectmentor.com

Website:www.objectmentor.com

FitNesse:www.fitnesse.org

Recommended