Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Hey, I'm @searls! [email protected]

Slide 3

Slide 3 text

Hey, I'm @searls! [email protected]

Slide 4

Slide 4 text

Hey, I'm @αʔϧζ! [email protected]

Slide 5

Slide 5 text

ൃԻ͕೉͍͠ͷͰɺ೔ຊޠ Ͱδϡʔεͱਃ͠·͢ɻ [email protected]

Slide 6

Slide 6 text

ൃԻ͕೉͍͠ͷͰɺ೔ຊޠ Ͱδϡʔεͱਃ͠·͢ɻ [email protected]

Slide 7

Slide 7 text

I come from @testdouble. We are software consultants. Say [email protected].

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

1. I talk fast when I'm nervous

Slide 10

Slide 10 text

1. I talk fast when I'm nervous

Slide 11

Slide 11 text

1. I talk fast when I'm nervous 2. I am always nervous

Slide 12

Slide 12 text

1. I talk fast when I'm nervous 2. I am always nervous

Slide 13

Slide 13 text

օ͞Μ΁ɺ͝ΊΜͳ͍ $

Slide 14

Slide 14 text

օ͞Μ΁ɺ͝ΊΜͳ͍ ؤுͬͯԼ͍͞ʂ $

Slide 15

Slide 15 text

I was very nervous about screen size

Slide 16

Slide 16 text

16 9

Slide 17

Slide 17 text

4 3

Slide 18

Slide 18 text

Wait! ☝

Slide 19

Slide 19 text

The secret to Ruby 3×3!

Slide 20

Slide 20 text

3 3

Slide 21

Slide 21 text

3 3 ✅

Slide 22

Slide 22 text

_____ is a massively successful language!

Slide 23

Slide 23 text

Early success

Slide 24

Slide 24 text

Early success

Slide 25

Slide 25 text

Early success

Slide 26

Slide 26 text

Early success

Slide 27

Slide 27 text

Early success: Making it easy to make new things

Slide 28

Slide 28 text

Early success: Making it easy to make new things

Slide 29

Slide 29 text

Later success

Slide 30

Slide 30 text

Later success

Slide 31

Slide 31 text

Later success

Slide 32

Slide 32 text

Later success

Slide 33

Slide 33 text

Later success: Making it easy to maintain old things

Slide 34

Slide 34 text

Later success: Making it easy to maintain old things

Slide 35

Slide 35 text

Can we make it easier to maintain old Ruby? Today's Question:

Slide 36

Slide 36 text

Can we make it easier to maintain old Ruby? Today's Question:

Slide 37

Slide 37 text

Today, let's refactor some legacy code

Slide 38

Slide 38 text

Today, let's refactor some legacy code

Slide 39

Slide 39 text

Refactor - verb

Slide 40

Slide 40 text

Refactor - verb To change the design of code without changing its observable behavior.

Slide 41

Slide 41 text

Refactor - verb To change in advance of a new feature or bug fix, making the job easier.

Slide 42

Slide 42 text

Today, let's refactor some legacy code

Slide 43

Slide 43 text

Today, let's refactor some legacy code

Slide 44

Slide 44 text

Legacy code has many definitions

Slide 45

Slide 45 text

Legacy Code - noun

Slide 46

Slide 46 text

Legacy Code - noun Old code.

Slide 47

Slide 47 text

Legacy Code - noun Code without tests.

Slide 48

Slide 48 text

Legacy Code - noun Code that we don't like.

Slide 49

Slide 49 text

Today, my definition is:

Slide 50

Slide 50 text

Legacy Code - noun Code we don't understand well enough to change confidently.

Slide 51

Slide 51 text

Today, let's refactor some legacy code

Slide 52

Slide 52 text

Refactoring is hard

Slide 53

Slide 53 text

Refactoring legacy code is very hard

Slide 54

Slide 54 text

Easy to accidentally break functionality

Slide 55

Slide 55 text

Legacy refactors often feel unsafe

Slide 56

Slide 56 text

Legacy refactors are hard to sell

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

Business Priority

Slide 59

Slide 59 text

Business Priority Cost/Risk

Slide 60

Slide 60 text

Business Priority Cost/Risk New Features

Slide 61

Slide 61 text

Business Priority Cost/Risk New Features Bug Fixes

Slide 62

Slide 62 text

Business Priority Cost/Risk New Features Bug Fixes Testing

Slide 63

Slide 63 text

Business Priority Cost/Risk New Features Bug Fixes Testing

Slide 64

Slide 64 text

Business Priority Cost/Risk New Features Bug Fixes Testing ???

Slide 65

Slide 65 text

Business Priority Cost/Risk New Features Bug Fixes Testing Refactoring

Slide 66

Slide 66 text

Business Priority Cost/Risk New Features Bug Fixes Testing Refactoring No selling Needed

Slide 67

Slide 67 text

Business Priority Cost/Risk New Features Bug Fixes Testing Refactoring Easy to sell

Slide 68

Slide 68 text

Business Priority Cost/Risk New Features Bug Fixes Testing Refactoring Can often sell

Slide 69

Slide 69 text

Business Priority Cost/Risk New Features Bug Fixes Testing Refactoring Very hard to sell

Slide 70

Slide 70 text

Refactors are hard

Slide 71

Slide 71 text

Refactors are hard ⏲

Slide 72

Slide 72 text

Refactors are hard ⏲

Slide 73

Slide 73 text

Refactors are hard ⏲

Slide 74

Slide 74 text

As complexity goes up

Slide 75

Slide 75 text

As complexity goes up Greater importance

Slide 76

Slide 76 text

As complexity goes up Less certain

Slide 77

Slide 77 text

As complexity goes up More costly

Slide 78

Slide 78 text

Make Refactors Great Again

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

Make Refactors Great for the 1st Time

Slide 81

Slide 81 text

Business Priority Cost/Risk Refactoring

Slide 82

Slide 82 text

Business Priority Cost/Risk Refactoring

Slide 83

Slide 83 text

Business Priority Cost/Risk Refactoring

Slide 84

Slide 84 text

Selling refactoring to businesspeople ⌨

Slide 85

Slide 85 text

Selling refactoring to businesspeople ⌨

Slide 86

Slide 86 text

Selling refactoring to businesspeople ⌨

Slide 87

Slide 87 text

1. Scare them!

Slide 88

Slide 88 text

1. Scare them! "If we don't refactor, then . !"

Slide 89

Slide 89 text

1. Scare them! "If we don't refactor, then . !" to rewrite everything someday we'll need

Slide 90

Slide 90 text

1. Scare them! "If we don't refactor, then . !" to rewrite everything someday we'll need Far in the future

Slide 91

Slide 91 text

1. Scare them! "If we don't refactor, then . !" costs will be much higher your maintenance

Slide 92

Slide 92 text

1. Scare them! "If we don't refactor, then . !" costs will be much higher your maintenance Hard to quantify

Slide 93

Slide 93 text

2. Absorb the cost

Slide 94

Slide 94 text

2. Absorb the cost New Feature Activities

Slide 95

Slide 95 text

2. Absorb the cost Planning New Feature Activities

Slide 96

Slide 96 text

2. Absorb the cost Development Planning New Feature Activities

Slide 97

Slide 97 text

2. Absorb the cost Development Testing Planning New Feature Activities

Slide 98

Slide 98 text

2. Absorb the cost Development Testing Planning New Feature Activities

Slide 99

Slide 99 text

2. Absorb the cost Development Testing Planning New Feature Activities Refactoring

Slide 100

Slide 100 text

2. Absorb the cost Development Testing Planning Refactoring Requires extreme discipline

Slide 101

Slide 101 text

2. Absorb the cost Development Testing Planning Refactoring Collapses under pressure

Slide 102

Slide 102 text

3. Take hostages

Slide 103

Slide 103 text

3. Take hostages Feature #1

Slide 104

Slide 104 text

3. Take hostages Feature #1 Feature #2

Slide 105

Slide 105 text

3. Take hostages Feature #1 Feature #2 Feature #3

Slide 106

Slide 106 text

3. Take hostages Feature #1 Feature #2 Feature #3 Feature #4

Slide 107

Slide 107 text

3. Take hostages Feature #1 Feature #2 Feature #3 Technical.Debt

Slide 108

Slide 108 text

3. Take hostages Feature #1 Feature #2 Technical Debt Technical.Debt

Slide 109

Slide 109 text

3. Take hostages Feature #1 Feature #2 Blames business for rushing Technical Debt Technical.Debt

Slide 110

Slide 110 text

3. Take hostages Feature #1 Feature #2 Erodes trust in the team Technical Debt Technical.Debt

Slide 111

Slide 111 text

Refactoring is hard to sell

Slide 112

Slide 112 text

Business Priority Cost/Risk Refactoring

Slide 113

Slide 113 text

Business Priority Cost/Risk Refactoring

Slide 114

Slide 114 text

Business Priority Cost/Risk Refactoring

Slide 115

Slide 115 text

Business Priority Cost/Risk Refactoring

Slide 116

Slide 116 text

Too much pressure!

Slide 117

Slide 117 text

Too much pressure!

Slide 118

Slide 118 text

Too much pressure! ⌛

Slide 119

Slide 119 text

Too much pressure! ⌛ ⛏

Slide 120

Slide 120 text

Refactors are scary!

Slide 121

Slide 121 text

You should buy my book!

Slide 122

Slide 122 text

THE FRIGHTENED PROGRAMMER JUSTIN SEARLS UGH SOFTWARE

Slide 123

Slide 123 text

Business Priority Cost/Risk Refactoring

Slide 124

Slide 124 text

Business Priority Cost/Risk Refactoring

Slide 125

Slide 125 text

1. Refactoring Patterns

Slide 126

Slide 126 text

1. Refactoring Patterns

Slide 127

Slide 127 text

1. Refactoring Patterns • Extract method

Slide 128

Slide 128 text

1. Refactoring Patterns • Extract method • Pull up / push down

Slide 129

Slide 129 text

1. Refactoring Patterns • Extract method • Pull up / push down • Split loop

Slide 130

Slide 130 text

1. Refactoring Patterns • Extract method • Pull up / push down • Split loop Safer with good tools

Slide 131

Slide 131 text

No content

Slide 132

Slide 132 text

1. Refactoring Patterns • Extract method • Pull up / push down • Split loop

Slide 133

Slide 133 text

1. Refactoring Patterns Not very expressive • Extract method • Pull up / push down • Split loop

Slide 134

Slide 134 text

2. Characterization Testing

Slide 135

Slide 135 text

2. Characterization Testing

Slide 136

Slide 136 text

2. Characterization Testing

Slide 137

Slide 137 text

2. Characterization Testing

Slide 138

Slide 138 text

2. Characterization Testing

Slide 139

Slide 139 text

2. Characterization Testing

Slide 140

Slide 140 text

2. Characterization Testing

Slide 141

Slide 141 text

2. Characterization Testing

Slide 142

Slide 142 text

2. Characterization Testing

Slide 143

Slide 143 text

2. Characterization Testing

Slide 144

Slide 144 text

2. Characterization Testing

Slide 145

Slide 145 text

2. Characterization Testing

Slide 146

Slide 146 text

2. Characterization Testing No wrong answers!

Slide 147

Slide 147 text

2. Characterization Testing

Slide 148

Slide 148 text

2. Characterization Testing

Slide 149

Slide 149 text

2. Characterization Testing

Slide 150

Slide 150 text

2. Characterization Testing

Slide 151

Slide 151 text

2. Characterization Testing

Slide 152

Slide 152 text

2. Characterization Testing

Slide 153

Slide 153 text

2. Characterization Testing

Slide 154

Slide 154 text

2. Characterization Testing

Slide 155

Slide 155 text

2. Characterization Testing

Slide 156

Slide 156 text

2. Characterization Testing That's a lot of testing!

Slide 157

Slide 157 text

2. Characterization Testing

Slide 158

Slide 158 text

2. Characterization Testing It's hard to let go of characterization tests

Slide 159

Slide 159 text

2. Characterization Testing Tempting to quit halfway through

Slide 160

Slide 160 text

3. A/B Testing / Experiments

Slide 161

Slide 161 text

3. A/B Testing / Experiments

Slide 162

Slide 162 text

3. A/B Testing / Experiments

Slide 163

Slide 163 text

3. A/B Testing / Experiments Old code

Slide 164

Slide 164 text

3. A/B Testing / Experiments Old code New code

Slide 165

Slide 165 text

3. A/B Testing / Experiments Old code New code if rand < 0.2

Slide 166

Slide 166 text

3. A/B Testing / Experiments Old code New code if rand < 0.2 true

Slide 167

Slide 167 text

3. A/B Testing / Experiments Old code New code if rand < 0.2 false true

Slide 168

Slide 168 text

3. A/B Testing / Experiments

Slide 169

Slide 169 text

3. A/B Testing / Experiments Old code New code if rand < 0.2 false true

Slide 170

Slide 170 text

3. A/B Testing / Experiments Old code New code if rand < 0.2 false true Rewriting in big steps is confusing & error-prone

Slide 171

Slide 171 text

3. A/B Testing / Experiments Old code New code if rand < 0.2 false true Heavy monitoring & analysis required

Slide 172

Slide 172 text

3. A/B Testing / Experiments Old code New code if rand < 0.2 false true Experimenting on humans is risky

Slide 173

Slide 173 text

No content

Slide 174

Slide 174 text

Characterization Testing

Slide 175

Slide 175 text

No content

Slide 176

Slide 176 text

A/B Experiments

Slide 177

Slide 177 text

Development

Slide 178

Slide 178 text

Development Testing

Slide 179

Slide 179 text

Development Testing Staging

Slide 180

Slide 180 text

Development Testing Staging Production

Slide 181

Slide 181 text

Development Testing Staging Production

Slide 182

Slide 182 text

Development Testing Staging Production Development

Slide 183

Slide 183 text

Development Testing Staging Production Development Testing

Slide 184

Slide 184 text

Development Testing Staging Production Development Testing Staging

Slide 185

Slide 185 text

Development Testing Staging Production Development Testing Staging Production

Slide 186

Slide 186 text

Development Testing Staging Production De Te St Pr

Slide 187

Slide 187 text

Development Testing Staging Production De Te St Pr

Slide 188

Slide 188 text

Development Testing Staging Production De Te St Pr Development

Slide 189

Slide 189 text

Development Testing Staging Production De Te St Pr Development Testing

Slide 190

Slide 190 text

Development Testing Staging Production De Te St Pr Development Testing Staging

Slide 191

Slide 191 text

Development Testing Staging Production De Te St Pr Development Testing Staging Production

Slide 192

Slide 192 text

Development Testing Staging Production De Te St Pr Development Testing Staging Production ❓

Slide 193

Slide 193 text

Slide 194

Slide 194 text

"Oh no, I have to give a talk on this"

Slide 195

Slide 195 text

No content

Slide 196

Slide 196 text

No content

Slide 197

Slide 197 text

Slide 198

Slide 198 text

"Instead of slides, I'll write a gem!"

Slide 199

Slide 199 text

TDD

Slide 200

Slide 200 text

TDD (Talk-Driven Development)

Slide 201

Slide 201 text

suture

Slide 202

Slide 202 text

github.com/testdouble/suture

Slide 203

Slide 203 text

No content

Slide 204

Slide 204 text

$ gem install suture

Slide 205

Slide 205 text

Refactors as Surgeries

Slide 206

Slide 206 text

Refactors as Surgeries

Slide 207

Slide 207 text

Refactors as Surgeries Serve a common purpose

Slide 208

Slide 208 text

Refactors as Surgeries Serve a common purpose ☺

Slide 209

Slide 209 text

Refactors as Surgeries Require careful planning

Slide 210

Slide 210 text

Refactors as Surgeries Flexible tools

Slide 211

Slide 211 text

Refactors as Surgeries Flexible tools

Slide 212

Slide 212 text

Refactors as Surgeries Flexible tools

Slide 213

Slide 213 text

Refactors as Surgeries Follow a process

Slide 214

Slide 214 text

Refactors as Surgeries Follow a process

Slide 215

Slide 215 text

Refactors as Surgeries Follow a process

Slide 216

Slide 216 text

Refactors as Surgeries Multiple Observations

Slide 217

Slide 217 text

Refactors as Surgeries Multiple Observations

Slide 218

Slide 218 text

Refactors as Surgeries Multiple Observations

Slide 219

Slide 219 text

9

Slide 220

Slide 220 text

9 F E A T U R E S

Slide 221

Slide 221 text

Plan

Slide 222

Slide 222 text

Plan Cut

Slide 223

Slide 223 text

Plan Cut Record

Slide 224

Slide 224 text

Plan Cut Record Validate

Slide 225

Slide 225 text

Plan Cut Record Validate Refactor

Slide 226

Slide 226 text

Plan Cut Record Validate Refactor Verify

Slide 227

Slide 227 text

Plan Cut Record Validate Refactor Verify ⚖ Compare

Slide 228

Slide 228 text

Plan Cut Record Validate Refactor Verify ⚖ Compare Fallback

Slide 229

Slide 229 text

Plan Cut Record Validate Refactor Verify ⚖ Compare Fallback Delete ✂

Slide 230

Slide 230 text

Plan

Slide 231

Slide 231 text

Two Bug Fixes:

Slide 232

Slide 232 text

Calculator service doesn't add negative numbers correctly. #1

Slide 233

Slide 233 text

Pure function ❄

Slide 234

Slide 234 text

class Controller def show calc = Calculator.new @result = calc.add( params[:left], params[:right] ) end end

Slide 235

Slide 235 text

class Controller def show calc = Calculator.new @result = calc.add( params[:left], params[:right] ) end end

Slide 236

Slide 236 text

class Controller def show calc = Calculator.new @result = calc.add( params[:left], params[:right] ) end end

Slide 237

Slide 237 text

class Controller def show calc = Calculator.new @result = calc.add( params[:left], params[:right] ) end end

Slide 238

Slide 238 text

class Calculator def add(left, right) right.times do left += 1 end left end end

Slide 239

Slide 239 text

class Calculator def add(left, right) right.times do left += 1 end left end end

Slide 240

Slide 240 text

class Calculator def add(left, right) right.times do left += 1 end left end end

Slide 241

Slide 241 text

class Calculator def add(left, right) right.times do left += 1 end left end end

Slide 242

Slide 242 text

class Calculator def add(left, right) right.times do left += 1 end left end end

Slide 243

Slide 243 text

class Calculator def add(left, right) right.times do left += 1 end left end end

Slide 244

Slide 244 text

class Calculator def add(left, right) right.times do left += 1 end left end end

Slide 245

Slide 245 text

class Controller def show calc = Calculator.new @result = calc.add( params[:left], params[:right] ) end end

Slide 246

Slide 246 text

class Controller def show calc = Calculator.new @result = calc.add( params[:left], params[:right] ) end end We will create our "seam" here

Slide 247

Slide 247 text

Calculator tally service doesn't handle odd numbers correctly. #2

Slide 248

Slide 248 text

Mutation ☣

Slide 249

Slide 249 text

class Controller def index calc = Calculator.new params[:nums].each {|n| calc.tally(n) } @result = calc.total end end

Slide 250

Slide 250 text

class Controller def index calc = Calculator.new params[:nums].each {|n| calc.tally(n) } @result = calc.total end end

Slide 251

Slide 251 text

class Controller def index calc = Calculator.new params[:nums].each {|n| calc.tally(n) } @result = calc.total end end

Slide 252

Slide 252 text

class Controller def index calc = Calculator.new params[:nums].each {|n| calc.tally(n) } @result = calc.total end end

Slide 253

Slide 253 text

class Controller def index calc = Calculator.new params[:nums].each {|n| calc.tally(n) } @result = calc.total end end

Slide 254

Slide 254 text

class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0) do |i| if i * 2 == n @total += i * 2 end end return end end

Slide 255

Slide 255 text

class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0) do |i| if i * 2 == n @total += i * 2 end end return end end

Slide 256

Slide 256 text

class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0) do |i| if i * 2 == n @total += i * 2 end end return end end

Slide 257

Slide 257 text

class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0) do |i| if i * 2 == n @total += i * 2 end end return end end

Slide 258

Slide 258 text

class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0) do |i| if i * 2 == n @total += i * 2 end end return end end

Slide 259

Slide 259 text

class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0) do |i| if i * 2 == n @total += i * 2 end end return end end

Slide 260

Slide 260 text

class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0) do |i| if i * 2 == n @total += i * 2 end end return end end

Slide 261

Slide 261 text

class Controller def index calc = Calculator.new params[:nums].each {|n| calc.tally(n) } @result = calc.total end end

Slide 262

Slide 262 text

class Controller def index calc = Calculator.new params[:nums].each {|n| calc.tally(n) } @result = calc.total end end

Slide 263

Slide 263 text

class Controller def index calc = Calculator.new params[:nums].each {|n| calc.tally(n) } @result = calc.total end end This seam is more complex

Slide 264

Slide 264 text

Plan Cut Record Validate Refactor Verify ⚖ Compare Fallback Delete ✂

Slide 265

Slide 265 text

Cut

Slide 266

Slide 266 text

Pure function ❄

Slide 267

Slide 267 text

class Controller def show calc = Calculator.new @result = calc.add( params[:left], params[:right] ) end end

Slide 268

Slide 268 text

ss Controller ef show calc = Calculator.new @result = calc.add( params[:left], params[:right] ) nd

Slide 269

Slide 269 text

calc = Calculator.new @result = calc.add(eate :add, params[:left], params[:right] ) params[:left], params[:right] ]

Slide 270

Slide 270 text

calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args: [ params[:left], params[:right] ]

Slide 271

Slide 271 text

calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args: [ params[:left], params[:right] ]

Slide 272

Slide 272 text

calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args: [ params[:left], params[:right] ]

Slide 273

Slide 273 text

calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args: [ params[:left], params[:right] ] :old must respond_to?(:call)

Slide 274

Slide 274 text

calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args: [ params[:left], params[:right] ]

Slide 275

Slide 275 text

calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args: [ params[:left], params[:right] ] Initially a no-op; verify it still works

Slide 276

Slide 276 text

Mutation ☣

Slide 277

Slide 277 text

class Controller def index calc = Calculator.new params[:nums].each {|n| calc.tally(n) } @result = calc.total end end

Slide 278

Slide 278 text

ef index calc = Calculator.new params[:nums].each {|n| calc.tally(n) } @result = calc.total nd

Slide 279

Slide 279 text

calc = Calculator.new params[:nums].each {|n| calc.tally(n) :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total

Slide 280

Slide 280 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total

Slide 281

Slide 281 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total

Slide 282

Slide 282 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total Wait, calc isn't an arg!

Slide 283

Slide 283 text

How to design a seam

Slide 284

Slide 284 text

❄Pure functions are easy

Slide 285

Slide 285 text

❄Pure functions are easy Calculator#add(a,b)

Slide 286

Slide 286 text

❄Pure functions are easy Calculator#add(a,b) (2,8)

Slide 287

Slide 287 text

❄Pure functions are easy Calculator#add(a,b) (2,8) 10

Slide 288

Slide 288 text

❄Pure functions are easy Calculator#add(a,b) (2,8) 10 (2,8)

Slide 289

Slide 289 text

❄Pure functions are easy Calculator#add(a,b) (2,8) 10 (2,8) 10

Slide 290

Slide 290 text

❄Pure functions are easy Calculator#add(a,b) (2,8) 10 (2,8) 10 Repeatable input & output ✅

Slide 291

Slide 291 text

Mutation is hard ☣

Slide 292

Slide 292 text

Mutation is hard Calculator#tally(n) ☣

Slide 293

Slide 293 text

Mutation is hard Calculator#tally(n) (4) ☣

Slide 294

Slide 294 text

Mutation is hard Calculator#tally(n) (4) 4 ☣

Slide 295

Slide 295 text

Mutation is hard Calculator#tally(n) (4) 4 ☣ (4)

Slide 296

Slide 296 text

Mutation is hard Calculator#tally(n) (4) 4 ☣ (4) 8

Slide 297

Slide 297 text

Mutation is hard Calculator#tally(n) (4) 4 ☣ (4) 8 @total=

Slide 298

Slide 298 text

Mutation is hard Calculator#tally(n) (4) 4 ☣ (4) 8

Slide 299

Slide 299 text

Mutation is hard Calculator#tally(n) 4 ☣ (4) 8 (calc@0,4)

Slide 300

Slide 300 text

Mutation is hard Calculator#tally(n) 4 ☣ 8 (calc@0,4) (calc@0,4)

Slide 301

Slide 301 text

Mutation is hard Calculator#tally(n) 4 ☣ (calc@0,4) (calc@0,4) 4

Slide 302

Slide 302 text

Mutation is hard Calculator#tally(n) 4 ☣ (calc@0,4) (calc@0,4) 4 Repeatable input & output ✅

Slide 303

Slide 303 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total

Slide 304

Slide 304 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total Broaden the seam

Slide 305

Slide 305 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total

Slide 306

Slide 306 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total

Slide 307

Slide 307 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total

Slide 308

Slide 308 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total Return a value

Slide 309

Slide 309 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total

Slide 310

Slide 310 text

Plan Cut Record Validate Refactor Verify ⚖ Compare Fallback Delete ✂

Slide 311

Slide 311 text

Record

Slide 312

Slide 312 text

Pure function ❄

Slide 313

Slide 313 text

calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args: [ params[:left], params[:right] ]

Slide 314

Slide 314 text

calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args: [ params[:left], params[:right] ], record_calls: true

Slide 315

Slide 315 text

calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args: [ params[:left], params[:right] ], record_calls: true

Slide 316

Slide 316 text

calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args: [ params[:left], params[:right] ], record_calls: true Most options support ENV: SUTURE_RECORD_CALLS=true

Slide 317

Slide 317 text

Record some calls!

Slide 318

Slide 318 text

Record via CLI

Slide 319

Slide 319 text

controller = Controller.new controller.params = {:left => 5, :right => 6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show

Slide 320

Slide 320 text

controller = Controller.new controller.params = {:left => 5, :right => 6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show

Slide 321

Slide 321 text

controller = Controller.new controller.params = {:left => 5, :right => 6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show

Slide 322

Slide 322 text

controller = Controller.new controller.params = {:left => 5, :right => 6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show

Slide 323

Slide 323 text

controller = Controller.new controller.params = {:left => 5, :right => 6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show

Slide 324

Slide 324 text

controller = Controller.new controller.params = {:left => 5, :right => 6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show

Slide 325

Slide 325 text

controller = Controller.new controller.params = {:left => 5, :right => 6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show

Slide 326

Slide 326 text

controller = Controller.new controller.params = {:left => 5, :right => 6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show

Slide 327

Slide 327 text

Record via browser

Slide 328

Slide 328 text

Record via browser add(4,5)

Slide 329

Slide 329 text

Record via browser add(4,5)

Slide 330

Slide 330 text

Record in production!

Slide 331

Slide 331 text

Record in production!

Slide 332

Slide 332 text

Record in production!

Slide 333

Slide 333 text

Mutation ☣

Slide 334

Slide 334 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n], record_calls: true } @result = calc.total

Slide 335

Slide 335 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, args: [calc, n], record_calls: true } @result = calc.total

Slide 336

Slide 336 text

controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params = {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index

Slide 337

Slide 337 text

controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params = {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index

Slide 338

Slide 338 text

controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params = {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index

Slide 339

Slide 339 text

controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params = {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index

Slide 340

Slide 340 text

controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params = {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index

Slide 341

Slide 341 text

controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params = {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index

Slide 342

Slide 342 text

controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params = {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index

Slide 343

Slide 343 text

controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params = {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index

Slide 344

Slide 344 text

controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params = {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index

Slide 345

Slide 345 text

Where does it go?

Slide 346

Slide 346 text

Suture.config({ database_path: "db/suture.sqlite3" })

Slide 347

Slide 347 text

Suture.config({ database_path: "db/suture.sqlite3" })

Slide 348

Slide 348 text

Suture.config({ database_path: "db/suture.sqlite3" })

Slide 349

Slide 349 text

Suture.config({ database_path: "db/suture.sqlite3" }) I heard Ruby was getting a database!

Slide 350

Slide 350 text

Suture.config({ database_path: "db/suture.sqlite3" }) Marshal.dump

Slide 351

Slide 351 text

What about Rails?

Slide 352

Slide 352 text

Gilded Rose Kata

Slide 353

Slide 353 text

No content

Slide 354

Slide 354 text

require "suture" class ItemsController < ApplicationController def update_all Item.all.each do |item| Suture.create :gilded_rose, :old => lambda { |item| item.update_quality! item }, :args => [item], :record_calls => true end redirect_to items_path end end

Slide 355

Slide 355 text

No content

Slide 356

Slide 356 text

No content

Slide 357

Slide 357 text

No content

Slide 358

Slide 358 text

No content

Slide 359

Slide 359 text

No content

Slide 360

Slide 360 text

No content

Slide 361

Slide 361 text

No content

Slide 362

Slide 362 text

No content

Slide 363

Slide 363 text

No content

Slide 364

Slide 364 text

No content

Slide 365

Slide 365 text

No content

Slide 366

Slide 366 text

No content

Slide 367

Slide 367 text

No content

Slide 368

Slide 368 text

No content

Slide 369

Slide 369 text

No content

Slide 370

Slide 370 text

No content

Slide 371

Slide 371 text

It apparently works

Slide 372

Slide 372 text

Plan Cut Record Validate Refactor Verify ⚖ Compare Fallback Delete ✂

Slide 373

Slide 373 text

Validate

Slide 374

Slide 374 text

Pure function ❄

Slide 375

Slide 375 text

def test_validate_old_add calc = Calculator.new Suture.verify :add, subject: calc.method(:add) end

Slide 376

Slide 376 text

def test_validate_old_add calc = Calculator.new Suture.verify :add, subject: calc.method(:add) end

Slide 377

Slide 377 text

def test_validate_old_add calc = Calculator.new Suture.verify :add, subject: calc.method(:add) end

Slide 378

Slide 378 text

def test_validate_old_add calc = Calculator.new Suture.verify :add, subject: calc.method(:add) end

Slide 379

Slide 379 text

def test_validate_old_add calc = Calculator.new Suture.verify :add, subject: calc.method(:add) end

Slide 380

Slide 380 text

def test_validate_old_add calc = Calculator.new Suture.verify :add, subject: calc.method(:add) end Verifies each recorded args yield the recorded result

Slide 381

Slide 381 text

def test_validate_old_add calc = Calculator.new Suture.verify :add, subject: calc.method(:add) end

Slide 382

Slide 382 text

def test_validate_old_add calc = Calculator.new Suture.verify :add, subject: calc.method(:add) end

Slide 383

Slide 383 text

def test_validate_old_add calc = Calculator.new Suture.verify :add, subject: calc.method(:add) end

Slide 384

Slide 384 text

def test_validate_old_add calc = Calculator.new Suture.verify :add, subject: calc.method(:add) end Cheap tests!

Slide 385

Slide 385 text

Mutation ☣

Slide 386

Slide 386 text

def test_old_tally Suture.verify:tally, subject: ->(calc, n){ calc.tally(n) calc.total } end

Slide 387

Slide 387 text

def test_old_tally Suture.verify:tally, subject: ->(calc, n){ calc.tally(n) calc.total } end

Slide 388

Slide 388 text

def test_old_tally Suture.verify:tally, subject: ->(calc, n){ calc.tally(n) calc.total } end

Slide 389

Slide 389 text

def test_old_tally Suture.verify:tally, subject: ->(calc, n){ calc.tally(n) calc.total } end

Slide 390

Slide 390 text

def test_old_tally Suture.verify:tally, subject: ->(calc, n){ calc.tally(n) calc.total } end

Slide 391

Slide 391 text

def test_old_tally Suture.verify:tally, subject: ->(calc, n){ calc.tally(n) calc.total } end Duplicate the lambda exactly

Slide 392

Slide 392 text

Finally, a good use for code coverage!

Slide 393

Slide 393 text

Gilded Rose Kata

Slide 394

Slide 394 text

Trial # 1 Characterization tests

Slide 395

Slide 395 text

No content

Slide 396

Slide 396 text

No content

Slide 397

Slide 397 text

Trial # 2 Suture.verify

Slide 398

Slide 398 text

def test_gilded_rose_old Suture.verify :rose, subject: ->(items) { update_quality(items) items }, fail_fast: true end

Slide 399

Slide 399 text

def test_gilded_rose_old Suture.verify :rose, subject: ->(items) { update_quality(items) items }, fail_fast: true end

Slide 400

Slide 400 text

def test_gilded_rose_old Suture.verify :rose, subject: ->(items) { update_quality(items) items }, fail_fast: true end

Slide 401

Slide 401 text

def test_gilded_rose_old Suture.verify :rose, subject: ->(items) { update_quality(items) items }, fail_fast: true end

Slide 402

Slide 402 text

def test_gilded_rose_old Suture.verify :rose, subject: ->(items) { update_quality(items) items }, fail_fast: true end

Slide 403

Slide 403 text

def test_gilded_rose_old Suture.verify :rose, subject: ->(items) { update_quality(items) items }, fail_fast: true end Items in, mutated items out

Slide 404

Slide 404 text

def test_gilded_rose_old Suture.verify :rose, subject: ->(items) { update_quality(items) items }, fail_fast: true end

Slide 405

Slide 405 text

def test_gilded_rose_old Suture.verify :rose, subject: ->(items) { update_quality(items) items }, fail_fast: true end All recordings expected to pass

Slide 406

Slide 406 text

Check coverage before continuing

Slide 407

Slide 407 text

No content

Slide 408

Slide 408 text

100% Coverage and zero tests

Slide 409

Slide 409 text

Plan Cut Record Validate Refactor Verify ⚖ Compare Fallback Delete ✂

Slide 410

Slide 410 text

Refactor

Slide 411

Slide 411 text

I'm no refactoring expert

Slide 412

Slide 412 text

I'm no refactoring expert

Slide 413

Slide 413 text

I'm no refactoring expert That's why I needed this tool

Slide 414

Slide 414 text

No content

Slide 415

Slide 415 text

Unlike my book, this book exists

Slide 416

Slide 416 text

Pure function ❄

Slide 417

Slide 417 text

class Calculator def add(left, right) right.times do left += 1 end left end end

Slide 418

Slide 418 text

class Calculator def add(left, right) right.times do left += 1 end left end end Doesn't work for negative values!

Slide 419

Slide 419 text

class Calculator def new_add(left, right) return left if right < 0 # ^ FIXME later left + right end end

Slide 420

Slide 420 text

class Calculator def new_add(left, right) return left if right < 0 # ^ FIXME later left + right end end

Slide 421

Slide 421 text

class Calculator def new_add(left, right) return left if right < 0 # ^ FIXME later left + right end end

Slide 422

Slide 422 text

class Calculator def new_add(left, right) return left if right < 0 # ^ FIXME later left + right end end

Slide 423

Slide 423 text

class Calculator def new_add(left, right) return left if right < 0 # ^ FIXME later left + right end end

Slide 424

Slide 424 text

class Calculator def new_add(left, right) return left if right < 0 # ^ FIXME later left + right end end Retain current behavior exactly, bugs & all

Slide 425

Slide 425 text

class Calculator def new_add(left, right) return left if right < 0 # ^ FIXME later left + right end end We don't know what else depends on bad behavior

Slide 426

Slide 426 text

Mutation ☣

Slide 427

Slide 427 text

class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0) do |i| if i * 2 == n @total += i * 2 end end return end end

Slide 428

Slide 428 text

class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0) do |i| if i * 2 == n @total += i * 2 end end return end end Skips odd values!

Slide 429

Slide 429 text

class Calculator def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end

Slide 430

Slide 430 text

class Calculator def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end

Slide 431

Slide 431 text

class Calculator def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end

Slide 432

Slide 432 text

class Calculator def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end

Slide 433

Slide 433 text

class Calculator def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end Still returns nil

Slide 434

Slide 434 text

class Calculator def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end

Slide 435

Slide 435 text

class Calculator def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end

Slide 436

Slide 436 text

class Calculator def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end

Slide 437

Slide 437 text

class Calculator def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end "Make the change easy, then make the easy change" - Beck

Slide 438

Slide 438 text

Plan Cut Record Validate Refactor Verify ⚖ Compare Fallback Delete ✂

Slide 439

Slide 439 text

Verify

Slide 440

Slide 440 text

Pure function ❄

Slide 441

Slide 441 text

def test_new_add calc = Calculator.new Suture.verify :add, subject: calc.method(:new_add) end

Slide 442

Slide 442 text

def test_new_add calc = Calculator.new Suture.verify :add, subject: calc.method(:new_add) end

Slide 443

Slide 443 text

def test_new_add calc = Calculator.new Suture.verify :add, subject: calc.method(:new_add) end

Slide 444

Slide 444 text

def test_new_add calc = Calculator.new Suture.verify :add, subject: calc.method(:new_add) end

Slide 445

Slide 445 text

def test_new_add calc = Calculator.new Suture.verify :add, subject: calc.method(:new_add) end

Slide 446

Slide 446 text

def test_new_add calc = Calculator.new Suture.verify :add, subject: calc.method(:new_add) end ✅

Slide 447

Slide 447 text

Mutation ☣

Slide 448

Slide 448 text

def test_new_tally Suture.verify :tally, subject: ->(calc, n){ calc.new_tally(n) calc.total } end

Slide 449

Slide 449 text

def test_new_tally Suture.verify :tally, subject: ->(calc, n){ calc.new_tally(n) calc.total } end

Slide 450

Slide 450 text

def test_new_tally Suture.verify :tally, subject: ->(calc, n){ calc.new_tally(n) calc.total } end

Slide 451

Slide 451 text

def test_new_tally Suture.verify :tally, subject: ->(calc, n){ calc.new_tally(n) calc.total } end

Slide 452

Slide 452 text

def test_new_tally Suture.verify :tally, subject: ->(calc, n){ calc.new_tally(n) calc.total } end

Slide 453

Slide 453 text

def test_new_tally Suture.verify :tally, subject: ->(calc, n){ calc.new_tally(n) calc.total } end ❌

Slide 454

Slide 454 text

def test_new_tally Suture.verify :tally, subject: ->(calc, n){ calc.new_tally(n) calc.total } end ❌ ⁉

Slide 455

Slide 455 text

Judge a library by its messages

Slide 456

Slide 456 text

No content

Slide 457

Slide 457 text

No content

Slide 458

Slide 458 text

# Verification of your seam failed! Descriptions of each unsuccessful verification follows: ## Failures 1.) Recorded call for seam :tally (ID: 13) ran and failed comparison. Arguments: ``` [, 1] ``` Expected returned value: ``` 0 ``` Actual returned value: ``` nil ``` Ideas to fix this: * Focus on this test by setting ENV var `SUTURE_VERIFY_ONLY=13` * Is the recording wrong? Delete it! `Suture.delete!(13)`

Slide 459

Slide 459 text

1.) Recorded call for seam :tally (ID: 13) ran and failed comparison. Arguments: ``` [, 1] ``` Expected returned value: ``` 0 ``` Actual returned value: ``` nil ```

Slide 460

Slide 460 text

Ideas to fix this: * Focus on this test by setting ENV var `SUTURE_VERIFY_ONLY=13` * Is the recording wrong? Delete it! `Suture.delete!(13)`

Slide 461

Slide 461 text

Ideas to fix this: * Focus on this test by setting ENV var `SUTURE_VERIFY_ONLY=13` * Is the recording wrong? Delete it! `Suture.delete!(13)` Only run this failure

Slide 462

Slide 462 text

Ideas to fix this: * Focus on this test by setting ENV var `SUTURE_VERIFY_ONLY=13` * Is the recording wrong? Delete it! `Suture.delete!(13)` Delete bad recordings

Slide 463

Slide 463 text

Failure advice ☹

Slide 464

Slide 464 text

### Fixing these failures #### Custom comparator If any comparison is failing and you believe the results are equivalent, we suggest you look into creating a custom comparator. See more details here: https://github.com/testdouble/suture#creating-a-custom-comparator #### Random seed Suture runs all verifications in random order by default. If you're seeing an erratic failure, it's possibly due to order-dependent behavior somewhere in your subject's code. To re-run the tests with the same random seed as was used in this run, set the env var `SUTURE_RANDOM_SEED=74735` or the config entry `:random_seed => 74735`. To re-run the tests without added shuffling (that is, in the order the calls were recorded in), then set the random seed explicitly to nil with env var `SUTURE_RANDOM_SEED=nil` or the config entry `:random_seed => nil`.

Slide 465

Slide 465 text

### Fixing these failures #### Custom comparator If any comparison is failing and you believe the results are equivalent, we suggest you look into creating a custom comparator. See more details here: https://github.com/testdouble/ suture#creating-a-custom-comparator

Slide 466

Slide 466 text

Comparing Results

Slide 467

Slide 467 text

Default Comparator

Slide 468

Slide 468 text

==

Slide 469

Slide 469 text

or

Slide 470

Slide 470 text

) ( Marshall.dump Marshall.dump == ) (

Slide 471

Slide 471 text

What about ActiveRecord?!

Slide 472

Slide 472 text

.attributes == AR AR .attributes

Slide 473

Slide 473 text

No content

Slide 474

Slide 474 text

!=

Slide 475

Slide 475 text

!=

Slide 476

Slide 476 text

!=

Slide 477

Slide 477 text

Custom Comparators p q r s t

Slide 478

Slide 478 text

What if Calculator had many other fields? class Calculator attr_reader :created_at, :memory_val, :total # … end

Slide 479

Slide 479 text

Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: ->(recorded, actual) { recorded.total == actual.total }

Slide 480

Slide 480 text

Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: ->(recorded, actual) { recorded.total == actual.total }

Slide 481

Slide 481 text

Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: ->(recorded, actual) { recorded.total == actual.total }

Slide 482

Slide 482 text

Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: ->(recorded, actual) { recorded.total == actual.total }

Slide 483

Slide 483 text

Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: ->(recorded, actual) { recorded.total == actual.total }

Slide 484

Slide 484 text

Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: ->(recorded, actual) { recorded.total == actual.total }

Slide 485

Slide 485 text

Classes also exist!

Slide 486

Slide 486 text

class CalcPare < Suture::Comparator def call(left, right) if super then return true end left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new

Slide 487

Slide 487 text

class CalcPare < Suture::Comparator def call(left, right) if super then return true end left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new

Slide 488

Slide 488 text

class CalcPare < Suture::Comparator def call(left, right) return true if super left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new

Slide 489

Slide 489 text

class CalcPare < Suture::Comparator def call(left, right) return true if super left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new

Slide 490

Slide 490 text

class CalcPare < Suture::Comparator def call(left, right) return true if super left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new

Slide 491

Slide 491 text

class CalcPare < Suture::Comparator def call(left, right) return true if super left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new

Slide 492

Slide 492 text

Returning to the error message

Slide 493

Slide 493 text

#### Random seed Suture runs all verifications in random order by default. If you're seeing an erratic failure, it's possibly due to order-dependent behavior somewhere in your subject's code. To re-run the tests with the same random seed as was used in this run, set the env var `SUTURE_RANDOM_SEED=74735` or the config entry `:random_seed => 74735`. To re-run the tests without added shuffling (that is, in the order the calls were recorded in), then set the random seed explicitly to nil with env var `SUTURE_RANDOM_SEED=nil` or the config entry `:random_seed => nil`.

Slide 494

Slide 494 text

#### Random seed Suture runs all verifications in random order by default. If you're seeing an erratic failure, it's possibly due to order-dependent behavior somewhere in your subject's code. To re-run the tests with the same random seed as was used in this run, set the env var `SUTURE_RANDOM_SEED=74735` or the config entry `:random_seed => 74735`. To re-run the tests without added shuffling (that is, in the order the calls were recorded in), then set the random seed explicitly to nil with env var `SUTURE_RANDOM_SEED=nil` or the config entry `:random_seed => nil`.

Slide 495

Slide 495 text

#### Random seed Suture runs all verifications in random order by default. If you're seeing an erratic failure, it's possibly due to order-dependent behavior somewhere in your subject's code. To re-run the tests with the same random seed as was used in this run, set the env var `SUTURE_RANDOM_SEED=74735` or the config entry `:random_seed => 74735`. To re-run the tests without added shuffling (that is, in the order the calls were recorded in), then set the random seed explicitly to nil with env var `SUTURE_RANDOM_SEED=nil` or the config entry `:random_seed => nil`.

Slide 496

Slide 496 text

Discoverable configuration

Slide 497

Slide 497 text

# Configuration This is the configuration used by this test run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```

Slide 498

Slide 498 text

# Configuration This is the configuration used by this test run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```

Slide 499

Slide 499 text

# Configuration This is the configuration used by this test run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```

Slide 500

Slide 500 text

# Configuration This is the configuration used by this test run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```

Slide 501

Slide 501 text

# Configuration This is the configuration used by this test run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```

Slide 502

Slide 502 text

# Configuration This is the configuration used by this test run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```

Slide 503

Slide 503 text

# Configuration This is the configuration used by this test run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```

Slide 504

Slide 504 text

# Configuration This is the configuration used by this test run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```

Slide 505

Slide 505 text

A sense of progress

Slide 506

Slide 506 text

# Result Summary - Passed........12 - Failed........1 - with error..0 - Skipped.......0 - Total calls...13 ## Progress Here's what your progress to initial completion looks like so far: [●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●◍◌◌◌◌] Of 13 recorded interactions, 12 are currently passing. That's 92%!

Slide 507

Slide 507 text

# Result Summary - Passed........12 - Failed........1 - with error..0 - Skipped.......0 - Total calls...13 ## Progress Here's what your progress to initial completion looks like so far: [●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●◍◌◌◌◌] Of 13 recorded interactions, 12 are currently passing. That's 92%!

Slide 508

Slide 508 text

Judge a library by its messages

Slide 509

Slide 509 text

Wait, why did verification fail? ❌

Slide 510

Slide 510 text

1.) Recorded call for seam :tally (ID: 13) ran and failed comparison. Arguments: ``` [, 1] ``` Expected returned value: ``` 0 ``` Actual returned value: ``` nil ```

Slide 511

Slide 511 text

1.) Recorded call for seam :tally (ID: 13) ran and failed comparison. Arguments: ``` [, 1] ``` Expected returned value: ``` 0 ``` Actual returned value: ``` nil ```

Slide 512

Slide 512 text

1.) Recorded call for seam :tally (ID: 13) ran and failed comparison. Arguments: ``` [, 1] ``` Expected returned value: ``` 0 ``` Actual returned value: ``` nil ```

Slide 513

Slide 513 text

class Calculator attr_reader :total def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end

Slide 514

Slide 514 text

class Calculator attr_reader :total def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end

Slide 515

Slide 515 text

class Calculator attr_reader :total def new_tally(n) return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end

Slide 516

Slide 516 text

class Calculator attr_reader :total def new_tally(n) @total ||= 0 return if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end

Slide 517

Slide 517 text

def test_new_tally Suture.verify :tally, subject: ->(calc, n){ calc.new_tally(n) calc.total } end

Slide 518

Slide 518 text

def test_new_tally Suture.verify :tally, subject: ->(calc, n){ calc.new_tally(n) calc.total } end ✅

Slide 519

Slide 519 text

Plan Cut Record Validate Refactor Verify ⚖ Compare Fallback Delete ✂

Slide 520

Slide 520 text

⚖ Compare

Slide 521

Slide 521 text

Our mission

Slide 522

Slide 522 text

Our mission Development

Slide 523

Slide 523 text

Our mission Development Testing

Slide 524

Slide 524 text

Our mission Development Testing Staging

Slide 525

Slide 525 text

Our mission Development Testing Staging Production

Slide 526

Slide 526 text

Our progress

Slide 527

Slide 527 text

✅Development Our progress

Slide 528

Slide 528 text

✅Development ✅Testing Our progress

Slide 529

Slide 529 text

✅Development ✅Testing ❓Staging Our progress

Slide 530

Slide 530 text

✅Development ✅Testing ❓Staging ❓Production Our progress

Slide 531

Slide 531 text

Pure function ❄

Slide 532

Slide 532 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end

Slide 533

Slide 533 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end

Slide 534

Slide 534 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end

Slide 535

Slide 535 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end Calls :new & :old

Slide 536

Slide 536 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end Calls :new & :old ✅

Slide 537

Slide 537 text

You will find surprising inputs & outputs

Slide 538

Slide 538 text

Mutation ☣

Slide 539

Slide 539 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true } @result = calc.total

Slide 540

Slide 540 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true } @result = calc.total

Slide 541

Slide 541 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true } @result = calc.total

Slide 542

Slide 542 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true } @result = calc.total ❌

Slide 543

Slide 543 text

Another huge error message

Slide 544

Slide 544 text

The results from the old & new code paths did not match for the seam (Suture::Error::ResultMismatch) :tally and Suture is raising this error because the `:call_both` option is enabled, because both code paths are expected to return the same result. Arguments: ``` [, 2] ``` The new code path returned: ``` 2 ``` The old code path returned: ``` 4 ``` Here's what we recommend you do next: 1. Verify that this mismatch does not represent a missed requirement in the new code path. If it does, implement it! 2. If either (or both) code path has a side effect that impacts the return value of the other, consider passing an `:after_old` and/or `:after_new` hook to clean up your application's state well enough to run both paths one-after-the-other safely. 3. If the two return values above are sufficiently similar for the purpose of your application, consider writing your own custom comparator that relaxes the comparison (e.g. only checks equivalence of the attributes that matter). See the README for more info on custom comparators. 4. If the new code path is working as desired (i.e. the old code path had a bug for this argument and you don't want to reimplement it just to make them perfectly in sync with one another), consider writing a one-off comparator for this seam that will ignore the affected range of arguments. See the README for more info on custom comparators. By default, Suture's :call_both mode will log a warning and raise an error when the results of each code path don't match. It is intended for use in any pre-production environment to "try out" the new code path before pushing it to production. If, for whatever reason, this error is too disruptive and logging is sufficient for monitoring results, you may disable this error by setting `:raise_on_result_mismatch` to false.

Slide 545

Slide 545 text

Suture is raising this error because the `:call_both` option is enabled, because both code paths are expected to return the same result. Arguments: ``` [, 2] ``` The new code path returned: ``` 2 ``` The old code path returned: ``` 4 ```

Slide 546

Slide 546 text

Suture is raising this error because the `:call_both` option is enabled, because both code paths are expected to return the same result. Arguments: ``` [, 2] ``` The new code path returned: ``` 2 ``` The old code path returned: ``` 4 ```

Slide 547

Slide 547 text

Suture is raising this error because the `:call_both` option is enabled, because both code paths are expected to return the same result. Arguments: ``` [, 2] ``` The new code path returned: ``` 2 ``` The old code path returned: ``` 4 ```

Slide 548

Slide 548 text

Suture is raising this error because the `:call_both` option is enabled, because both code paths are expected to return the same result. Arguments: ``` [, 2] ``` The new code path returned: ``` 2 ``` The old code path returned: ``` 4 ```

Slide 549

Slide 549 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 550

Slide 550 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 551

Slide 551 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 552

Slide 552 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total protect from arg mutation

Slide 553

Slide 553 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total protect from arg mutation ☺

Slide 554

Slide 554 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total protect from arg mutation ❌

Slide 555

Slide 555 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 556

Slide 556 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total calc never changes now!

Slide 557

Slide 557 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 558

Slide 558 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total total is always nil

Slide 559

Slide 559 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 560

Slide 560 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 561

Slide 561 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 562

Slide 562 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 563

Slide 563 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total ✅

Slide 564

Slide 564 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 565

Slide 565 text

Remember: every mode is optional!

Slide 566

Slide 566 text

Plan Cut Record Validate Refactor Verify ⚖ Compare Fallback Delete ✂

Slide 567

Slide 567 text

Fallback

Slide 568

Slide 568 text

Make change safe for users

Slide 569

Slide 569 text

New path errored? Try the old one! ♻

Slide 570

Slide 570 text

Pure function ❄

Slide 571

Slide 571 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end

Slide 572

Slide 572 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end

Slide 573

Slide 573 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end

Slide 574

Slide 574 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end

Slide 575

Slide 575 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end Rescues :new with :old

Slide 576

Slide 576 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end Rescues :new with :old ✅

Slide 577

Slide 577 text

Mutation ☣

Slide 578

Slide 578 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 579

Slide 579 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total

Slide 580

Slide 580 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 581

Slide 581 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 582

Slide 582 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total ✅

Slide 583

Slide 583 text

Faster than call_both

Slide 584

Slide 584 text

Fewer side effects

Slide 585

Slide 585 text

All errors are logged ✍

Slide 586

Slide 586 text

Allow certain errors via expected_error_types

Slide 587

Slide 587 text

Plan Cut Record Validate Refactor Verify ⚖ Compare Fallback Delete ✂

Slide 588

Slide 588 text

Delete ✂

Slide 589

Slide 589 text

Like stitches, remove once the wound heals ✂

Slide 590

Slide 590 text

Pure function ❄

Slide 591

Slide 591 text

def test_new_add calc = Calculator.new Suture.verify :add, subject: calc.method(:new_add) end

Slide 592

Slide 592 text

No content

Slide 593

Slide 593 text

Suture.delete_all!(:add)

Slide 594

Slide 594 text

No content

Slide 595

Slide 595 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end

Slide 596

Slide 596 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end

Slide 597

Slide 597 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end

Slide 598

Slide 598 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end

Slide 599

Slide 599 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end

Slide 600

Slide 600 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end

Slide 601

Slide 601 text

class Controller def show calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], ) fallback_on_error: true end end

Slide 602

Slide 602 text

class Controller def show calc = Calculator.new @result = calc.new_add( old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], ) fallback_on_error: true end end

Slide 603

Slide 603 text

class Controller def show calc = Calculator.new @result = calc.new_add( params[:left], params[:right] ) end end

Slide 604

Slide 604 text

class Controller def show calc = Calculator.new @result = calc.new_add( params[:left], params[:right] ) end end

Slide 605

Slide 605 text

Mutation ☣

Slide 606

Slide 606 text

def test_new_tally Suture.verify :tally, subject: ->(calc, n){ calc.new_tally(n) calc.total } end

Slide 607

Slide 607 text

No content

Slide 608

Slide 608 text

Suture.delete_all!(:tally)

Slide 609

Slide 609 text

No content

Slide 610

Slide 610 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 611

Slide 611 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 612

Slide 612 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 613

Slide 613 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 614

Slide 614 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 615

Slide 615 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 616

Slide 616 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 617

Slide 617 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 618

Slide 618 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 619

Slide 619 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(n) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 620

Slide 620 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(n) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 621

Slide 621 text

calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m) { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(n) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total

Slide 622

Slide 622 text

calc = Calculator.new params[:nums].each {|n| calc.new_tally(n) } @result = calc.total

Slide 623

Slide 623 text

calc = Calculator.new params[:nums].each {|n| calc.new_tally(n) } @result = calc.total

Slide 624

Slide 624 text

We did it!

Slide 625

Slide 625 text

Suture is ready to use!

Slide 626

Slide 626 text

Suture is ready to use! github.com/testdouble/suture

Slide 627

Slide 627 text

Suture is ready to use! 1.0.0

Slide 628

Slide 628 text

Together, let's make refactors less scary

Slide 629

Slide 629 text

One last thing…

Slide 630

Slide 630 text

No content

Slide 631

Slide 631 text

No content

Slide 632

Slide 632 text

No content

Slide 633

Slide 633 text

No content

Slide 634

Slide 634 text

No content

Slide 635

Slide 635 text

No content

Slide 636

Slide 636 text

My homestay brother was also a programmer

Slide 637

Slide 637 text

No content

Slide 638

Slide 638 text

No content

Slide 639

Slide 639 text

No content

Slide 640

Slide 640 text

No content

Slide 641

Slide 641 text

No content

Slide 642

Slide 642 text

No content

Slide 643

Slide 643 text

No content

Slide 644

Slide 644 text

No content

Slide 645

Slide 645 text

No content

Slide 646

Slide 646 text

No content

Slide 647

Slide 647 text

No content

Slide 648

Slide 648 text

No content

Slide 649

Slide 649 text

Slide 650

Slide 650 text

օ͞Μ΁ɺ

Slide 651

Slide 651 text

օ͞Μ΁ɺ ͓࿩͢ΔػձΛ ͍͖ͨͩ͋Γ͕ͱ͏ʂ

Slide 652

Slide 652 text

օ͞Μ΁ɺ ͓࿩͢ΔػձΛ ͍͖ͨͩ͋Γ͕ͱ͏ʂ ײँͷؾ࣋ͪͰ ͍ͬͺ͍Ͱ͢ʂ

Slide 653

Slide 653 text

օ͞Μ΁ɺ ͓࿩͢ΔػձΛ ͍͖ͨͩ͋Γ͕ͱ͏ʂ ײँͷؾ࣋ͪͰ ͍ͬͺ͍Ͱ͢ʂ

Slide 654

Slide 654 text

I'm @searls—tell me what you think !

Slide 655

Slide 655 text

I'm in Kansai all month! [email protected]

Slide 656

Slide 656 text

ɹ ΋͏Ұճ͋Γ͕ͱ͏ʂ