Advanced Developer Testing

Preview:

DESCRIPTION

Developers have unique opportunities to influence software quality. We explore three advanced testing techniques that belong in every developers toolbox: (1) characterization, (2) approval, and (3) mutation testing.

Citation preview

Three AdvancedDeveloper Testing Techniques

Alistair McKinnell@amckinnell

How do we change code safely?

Changing Code

Existing Behaviour New Behaviour

CharacterizationTesting

ApprovalTesting

CodeCoverage

MutationTesting

Gilded Rose

1 class GildedRose 2 attr_reader :items 3 4 def initialize(items) 5 @items = items 6 end 7 8 def update_quality 9 @items.each do |item| 10 if item.name != 'Aged Brie' and 11 item.name != 'Backstage passes to a TAFKAL80ETC concert' 12 if item.quality > 0 13 if item.name != 'Sulfuras, Hand of Ragnaros' 14 item.quality = item.quality - 1 15 end 16 end 17 else 18 if item.quality < 50 19 item.quality = item.quality + 1 20 if item.name == 'Backstage passes to a TAFKAL80ETC concert' 21 if item.sell_in < 11 22 if item.quality < 50 23 item.quality = item.quality + 1 24 end 25 end 26 if item.sell_in < 6 27 if item.quality < 50 28 item.quality = item.quality + 1 29 end 30 end 31 end 32 end 33 end 34 if item.name != 'Sulfuras, Hand of Ragnaros' 35 item.sell_in = item.sell_in - 1 36 end 37 if item.sell_in < 0 38 if item.name != 'Aged Brie' 39 if item.name != 'Backstage passes to a TAFKAL80ETC concert' 40 if item.quality > 0 41 if item.name != 'Sulfuras, Hand of Ragnaros' 42 item.quality = item.quality - 1 43 end 44 end 45 else 46 item.quality = item.quality - item.quality 47 end 48 else 49 if item.quality < 50 50 item.quality = item.quality + 1 51 end 52 end 53 end 54 end 55 end 56 57 end !

“Conjured” items degrade twice as fast as normal items.

New Behaviour

Existing Behaviour(Refactored)

New Behaviour(Conjured Item)

Changing Code

Are we ready to change code safely?

Nope

Refactoring safelyrequires tests.

CharacterizationTesting

ApprovalTesting

CodeCoverage

MutationTesting

describe GildedRose do

it 'knows how to update quality for items' do items = [Item.new('Mail Armour', 10, 20)] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq(nil) end

end

describe GildedRose do

it 'knows how to update quality for items' do items = [Item.new('Mail Armour', 10, 20)] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq(nil) end

end

expected: nil got: "Mail Armour, 9, 19"

describe GildedRose do

it 'knows how to update quality for items' do items = [Item.new('Mail Armour', 10, 20)] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq(nil) end

end

expected: nil got: "Mail Armour, 9, 19"

describe GildedRose do

it 'knows how to update quality for items' do items = [Item.new('Mail Armour', 10, 20)] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 9, 19') end

end

describe GildedRose do

it 'knows how to update quality for items' do items = [Item.new(‘Mail Armour', 10, 20)] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 9, 19')

subject.update_quality expect(subject.items[0].to_s).to eq(nil) end

end

describe GildedRose do

it 'knows how to update quality for items' do items = [Item.new('Mail Armour', 10, 20)] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 9, 19')

subject.update_quality expect(subject.items[0].to_s).to eq(nil) end

end

expected: nil got: "Mail Armour, 8, 18"

describe GildedRose do

it 'knows how to update quality for items' do items = [Item.new('Mail Armour', 10, 20)] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 9, 19')

subject.update_quality expect(subject.items[0].to_s).to eq(nil) end

end

expected: nil got: "Mail Armour, 8, 18"

describe GildedRose do

it 'knows how to update quality for items' do items = [Item.new('Mail Armour', 10, 20)] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 9, 19')

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 8, 18') end

end

describe GildedRose do

it 'knows how to update quality for items' do items = [ Item.new('Mail Armour', 10, 20), Item.new('Aged Brie', 4, 9) ] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 9, 19')

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 8, 18') end

end

describe GildedRose do

it 'knows how to update quality for items' do items = [ Item.new('Mail Armour', 10, 20), Item.new('Aged Brie', 4, 9) ] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 9, 19')

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 8, 18') end

end

describe GildedRose do

it 'knows how to update quality for items' do items = [ Item.new('Mail Armour', 10, 20), Item.new(‘Aged Brie', 4, 9) ] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 9, 19') expect(subject.items[1].to_s).to eq(nil)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 8, 18') expect(subject.items[1].to_s).to eq(nil) end

end

describe GildedRose do

it 'knows how to update quality for items' do items = [ Item.new('Mail Armour', 10, 20), Item.new(‘Aged Brie', 4, 9) ] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 9, 19') expect(subject.items[1].to_s).to eq(nil)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 8, 18') expect(subject.items[1].to_s).to eq(nil) end

end

describe GildedRose do

it 'knows how to update quality for items' do items = [ Item.new('Mail Armour', 10, 20), Item.new(‘Aged Brie', 4, 9) ] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 9, 19') expect(subject.items[1].to_s).to eq(‘Aged Brie, 3, 10')

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 8, 18') expect(subject.items[1].to_s).to eq(‘Aged Brie, 2, 11') end

end

describe GildedRose do

it 'knows how to update quality for items' do items = [ Item.new('Mail Armour', 10, 20), Item.new(‘Aged Brie', 4, 9) ] subject = GildedRose.new(items)

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 9, 19') expect(subject.items[1].to_s).to eq(‘Aged Brie, 3, 10')

subject.update_quality expect(subject.items[0].to_s).to eq('Mail Armour, 8, 18') expect(subject.items[1].to_s).to eq(‘Aged Brie, 2, 11') end

end

Let’s organize our test

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def items end

def characterize(subject, days) end

def expected end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Aged Brie', 4, 9] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def characterize(subject, days) characterization = []

(1..days).each do subject.update_quality

subject.items.each { |item| characterization << item.to_s } end

characterization end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def expected [ 'Mail Armour, 9, 19', 'Aged Brie, 3, 10', 'Mail Armour, 8, 18', 'Aged Brie, 2, 11' ] end

Are we ready to change code safely?

Nope

Characterizationis incomplete.

CharacterizationTesting

ApprovalTesting

CodeCoverage

MutationTesting

53%

if item.name == 'Backstage passes to a TAFKAL80ETC concert' if item.sell_in < 11 if item.quality < 50 item.quality = item.quality + 1 end end if item.sell_in < 6 if item.quality < 50 item.quality = item.quality + 1 end end end

if item.name == 'Backstage passes to a TAFKAL80ETC concert' if item.sell_in < 11 if item.quality < 50 item.quality = item.quality + 1 end end if item.sell_in < 6 if item.quality < 50 item.quality = item.quality + 1 end end end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Aged Brie', 4, 9] ]

item_attributes.map { |args| Item.new(*args) } end

if item.name == 'Backstage passes to a TAFKAL80ETC concert' if item.sell_in < 11 if item.quality < 50 item.quality = item.quality + 1 end end if item.sell_in < 6 if item.quality < 50 item.quality = item.quality + 1 end end end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Aged Brie', 4, 9] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Aged Brie', 4, 9], ['Backstage passes to a TAFKAL80ETC concert', 15, 17] ]

item_attributes.map { |args| Item.new(*args) } end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Aged Brie', 4, 9], ['Backstage passes to a TAFKAL80ETC concert', 15, 17] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def expected [ 'Mail Armour, 9, 19', 'Aged Brie, 3, 10', 'Backstage passes to a TAFKAL80ETC concert, 14, 18', 'Mail Armour, 8, 18', 'Aged Brie, 2, 11', 'Backstage passes to a TAFKAL80ETC concert, 13, 19', ] end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def expected [ 'Mail Armour, 9, 19', 'Aged Brie, 3, 10', 'Backstage passes to a TAFKAL80ETC concert, 14, 18', 'Mail Armour, 8, 18', 'Aged Brie, 2, 11', 'Backstage passes to a TAFKAL80ETC concert, 13, 19', ] end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

60%

if item.name == 'Backstage passes to a TAFKAL80ETC concert' if item.sell_in < 11 if item.quality < 50 item.quality = item.quality + 1 end end if item.sell_in < 6 if item.quality < 50 item.quality = item.quality + 1 end end end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

if item.name == 'Backstage passes to a TAFKAL80ETC concert' if item.sell_in < 11 if item.quality < 50 item.quality = item.quality + 1 end end if item.sell_in < 6 if item.quality < 50 item.quality = item.quality + 1 end end end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

if item.name == 'Backstage passes to a TAFKAL80ETC concert' if item.sell_in < 11 if item.quality < 50 item.quality = item.quality + 1 end end if item.sell_in < 6 if item.quality < 50 item.quality = item.quality + 1 end end end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 20)

expect(characterization).to eq(expected) end

end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 20)

expect(characterization).to eq(expected) end

end

This is what we want to do

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 20)

expect(characterization).to eq(expected) end

end

def expected [ 'Mail Armour, 9, 19', 'Aged Brie, 3, 10', 'Backstage passes to a TAFKAL80ETC concert, 14, 18', 'Mail Armour, 8, 18', 'Aged Brie, 2, 11', 'Backstage passes to a TAFKAL80ETC concert, 13, 19’,

and many more expected results ] end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def characterize(subject, days) characterization = []

(1..days).each do subject.update_quality

characterization.concat(subject.items.map(&:to_s)) end

Digest::SHA2.hexdigest(characterization.join) end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def characterize(subject, days) characterization = []

(1..days).each do subject.update_quality

characterization.concat(subject.items.map(&:to_s)) end

Digest::SHA2.hexdigest(characterization.join) end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def expected [ 'Mail Armour, 9, 19', 'Aged Brie, 3, 10', 'Backstage passes to a TAFKAL80ETC concert, 14, 18', 'Mail Armour, 8, 18', 'Aged Brie, 2, 11', 'Backstage passes to a TAFKAL80ETC concert, 13, 19’, ] end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def expected nil end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def expected nil end

expected: nil got: “c869cb44cd36e1553d18 … d958277a43b1adc8e8e5”

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def expected nil end

expected: nil got: “c869cb44cd36e1553d18 … d958277a43b1adc8e8e5”

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def expected ‘c869cb44cd36e1553d18 … d958277a43b1adc8e8e5’ end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 20)

expect(characterization).to eq(expected) end

end

def expected ‘c869cb44cd36e1553d18 … d958277a43b1adc8e8e5’ end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 20)

expect(characterization).to eq(expected) end

end

expected: “c869cb44cd36e1553d18 … d958277a43b1adc8e8e5” got: “fb61e7fdfffcba653fec … 9ae0512470a974b1561e"

def expected ‘c869cb44cd36e1553d18 … d958277a43b1adc8e8e5’ end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 20)

expect(characterization).to eq(expected) end

end

def expected ‘fb61e7fdfffcba653fec … 9ae0512470a974b1561e’ end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Mail Armour', 10, 1], ['Aged Brie', 4, 9], ['Aged Brie', 1, 49], ['Backstage passes to a TAFKAL80ETC concert', 15, 17], ['Backstage passes to a TAFKAL80ETC concert', 5, 49], ['Sulfuras, Hand of Ragnaros', -1, 80] ]

item_attributes.map { |args| Item.new(*args) } end

100%

Are we ready to change code safely?

Nope

Characterizationis incomplete.

CharacterizationTesting

ApprovalTesting

CodeCoverage

MutationTesting

class GildedRose attr_reader :items

def initialize(items) @items = items end

def update_quality @items.each do |item|

if item.name != 'Sulfuras, Hand of Ragnaros' item.sell_in = item.sell_in - 1 end

end end end

class GildedRose attr_reader :items

def initialize(items) @items = items end

def update_quality @items.each do |item|

# if item.name != 'Sulfuras, Hand of Ragnaros' item.sell_in = item.sell_in - 1 # end

end end end

class GildedRose attr_reader :items

def initialize(items) @items = items end

def update_quality @items.each do |item|

# if item.name != 'Sulfuras, Hand of Ragnaros' item.sell_in = item.sell_in - 1 # end

end end end

Comment out two lines

Test still passes?

class GildedRose attr_reader :items

def initialize(items) @items = items end

def update_quality @items.each do |item|

# if item.name != 'Sulfuras, Hand of Ragnaros' item.sell_in = item.sell_in - 1 # end

end end end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Aged Brie', 4, 9], ['Backstage passes to a TAFKAL80ETC concert', 15, 17] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Aged Brie', 4, 9], ['Backstage passes to a TAFKAL80ETC concert', 15, 17], ['Sulfuras, Hand of Ragnaros', -1, 80] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Aged Brie', 4, 9], ['Backstage passes to a TAFKAL80ETC concert', 15, 17], ['Sulfuras, Hand of Ragnaros', -1, 80] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

Are we ready to change code safely?

Nope

Characterizationis incomplete.

github.com/mbj/mutant

Mutant configuration:

Subjects: 2Mutations: 476 Kills: 454 Alive: 22 Coverage: 95.38% Expected: 100.00%

- if (item.quality > 0) + if (item.quality > 1)

- if (item.quality < 50) + if (item.quality < 49)

- if (item.quality < 50) + if (item.quality < 51)

- if (item.quality < 50) + if true

- if (item.quality < 50) + if 50

- if (item.name == "Backstage passes to a TAFKAL80ETC concert") + if item.name.eql?("Backstage passes to a TAFKAL80ETC concert")

def items item_attributes = [ ['Mail Armour', 10, 20], ['Aged Brie', 4, 9], ['Backstage passes to a TAFKAL80ETC concert', 15, 17], ['Sulfuras, Hand of Ragnaros', -1, 80] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Mail Armour', 10, 1], ['Aged Brie', 4, 9], ['Aged Brie', 1, 49], ['Backstage passes to a TAFKAL80ETC concert', 15, 17], ['Backstage passes to a TAFKAL80ETC concert', 5, 49], ['Sulfuras, Hand of Ragnaros', -1, 80] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Mail Armour', 10, 1], ['Aged Brie', 4, 9], ['Aged Brie', 1, 49], ['Backstage passes to a TAFKAL80ETC concert', 15, 17], ['Backstage passes to a TAFKAL80ETC concert', 5, 49], ['Sulfuras, Hand of Ragnaros', -1, 80] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

Subjects: 2 Mutations: 476 Kills: 454 Alive: 22 Coverage: 95.38% Expected: 100.00%

Before AfterSubjects: 2 Mutations: 476 Kills: 474 Alive: 4 Coverage: 99.16% Expected: 100.00%

Are we ready to change code safely?

Yep

New BehaviourExisting Behaviour

Changing Code

CharacterizationTesting

ApprovalTesting

CodeCoverage

MutationTesting

Refactoring

1 class GildedRose 2 attr_reader :items 3 4 def initialize(items) 5 @items = items 6 end 7 8 def update_quality 9 @items.each { |item| update_item_quality(item) } 10 end 11 12 private 13 14 def update_item_quality(item) 15 return if item.name == 'Sulfuras, Hand of Ragnaros' 16 17 perform_inventory_rollover(item) 18 perform_inventory_expiration(item) 19 end 20 21 def perform_inventory_rollover(item) 22 item.sell_in -= 1 23 24 case item.name 25 when 'Aged Brie' 26 increase_quality(item) 27 when 'Backstage passes to a TAFKAL80ETC concert' 28 increase_quality(item) 29 increase_quality(item) if item.sell_in < 10 30 increase_quality(item) if item.sell_in < 5 31 else 32 decrease_quality(item) 33 end 34 end 35 36 def perform_inventory_expiration(item) 37 return unless expired?(item) 38 39 case item.name 40 when 'Aged Brie' 41 increase_quality(item) 42 when 'Backstage passes to a TAFKAL80ETC concert' 43 writeoff(item) 44 else 45 decrease_quality(item) 46 end 47 end 48 49 def expired?(item) 50 item.sell_in < 0 51 end 52 53 def decrease_quality(item) 54 item.quality -= 1 if 0 < item.quality 55 end 56 57 def increase_quality(item) 58 item.quality += 1 if item.quality < 50 59 end 60 61 def writeoff(item) 62 item.quality = 0 63 end 64 65 end

“Conjured” items degrade twice as fast as normal items.

New Behaviour

def perform_inventory_rollover(item)

case item.name when 'Conjured Mana' decrease_quality(item) decrease_quality(item) end

end

def perform_inventory_expiration(item)

case item.name

when 'Conjured Mana' decrease_quality(item) decrease_quality(item) end

end

def perform_inventory_rollover(item)

case item.name when 'Conjured Mana' decrease_quality(item) decrease_quality(item) end

end

def perform_inventory_expiration(item)

case item.name

when 'Conjured Mana' decrease_quality(item) decrease_quality(item) end

end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Mail Armour', 10, 1], ['Aged Brie', 4, 9], ['Aged Brie', 1, 49], ['Backstage passes to a TAFKAL80ETC concert', 15, 17], ['Backstage passes to a TAFKAL80ETC concert', 5, 49], ['Sulfuras, Hand of Ragnaros', -1, 80], [‘Conjured Mana', 13, 50] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Mail Armour', 10, 1], ['Aged Brie', 4, 9], ['Aged Brie', 1, 49], ['Backstage passes to a TAFKAL80ETC concert', 15, 17], ['Backstage passes to a TAFKAL80ETC concert', 5, 49], ['Sulfuras, Hand of Ragnaros', -1, 80], [‘Conjured Mana', 13, 50] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 20)

expect(characterization).to eq(expected) end

end

def expected ‘1768fa473f323772588a … 13eab4018717ea78ea0c’ end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 20)

expect(characterization).to eq(expected) end

end

expected: “1768fa473f323772588a … 13eab4018717ea78ea0c” got: “6cf1111d5865232381f1 … 9d00ff8e831395de5420"

def expected ‘1768fa473f323772588a … 13eab4018717ea78ea0c’ end

New BehaviourExisting Behaviour

Changing Code

Are we ready to change code safely?

Nope

Can’t distinguish existing and new behaviour.

CharacterizationTesting

ApprovalTesting

CodeCoverage

MutationTesting

Whatever the code does.

Existing Behaviour

“Conjured” items degradetwice as fast as normal items.

New Behaviour

github.com/kytrinyx/approvals

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items)

verify { characterize(subject, 20) } end

end

def characterize(subject, days) characterization = []

(1..days).each_with_index do |day| subject.update_quality

characterization << "Day #{day} of #{days}" subject.items.each { |item| characterization << " #{item.to_s}" } end

characterization.join("\n") end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items)

verify { characterize(subject, 20) } end

end

def characterize(subject, days) characterization = []

(1..days).each_with_index do |day| subject.update_quality

characterization << "Day #{day} of #{days}" subject.items.each { |item| characterization << " #{item.to_s}" } end

characterization.join("\n") end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items)

verify { characterize(subject, 20) } end

end

def characterize(subject, days) characterization = []

(1..days).each_with_index do |day| subject.update_quality

characterization << "Day #{day} of #{days}" subject.items.each { |item| characterization << " #{item.to_s}" } end

characterization.join("\n") end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items)

verify { characterize(subject, 20) } end

end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items)

verify { characterize(subject, 20) } end

end

Approvals::ApprovalError: Approval Error: Approval file “spec/fixtures/approvals/gildedrose/ knows_how_to_update_quality_for_items.approved.txt"not found.

knows_how_to_update_quality_for_items.received.txt

1 Day 1 of 20 2 Mail Armour, 9, 19 3 Mail Armour, 9, 0 4 Aged Brie, 3, 10 5 Aged Brie, 0, 50 6 Backstage passes to a TAFKAL80ETC concert, 14, 18 7 Backstage passes to a TAFKAL80ETC concert, 4, 50 8 Sulfuras, Hand of Ragnaros, -1, 80 9 Day 2 of 20 10 Mail Armour, 8, 18

153 Day 20 of 20 154 Mail Armour, -10, 0 155 Mail Armour, -10, 0 156 Aged Brie, -16, 45 157 Aged Brie, -19, 50 158 Backstage passes to a TAFKAL80ETC concert, -5, 0 159 Backstage passes to a TAFKAL80ETC concert, -15, 0 160 Sulfuras, Hand of Ragnaros, -1, 80

knows_how_to_update_quality_for_items.approved.txt

1 Day 1 of 20 2 Mail Armour, 9, 19 3 Mail Armour, 9, 0 4 Aged Brie, 3, 10 5 Aged Brie, 0, 50 6 Backstage passes to a TAFKAL80ETC concert, 14, 18 7 Backstage passes to a TAFKAL80ETC concert, 4, 50 8 Sulfuras, Hand of Ragnaros, -1, 80 9 Day 2 of 20 10 Mail Armour, 8, 18

153 Day 20 of 20 154 Mail Armour, -10, 0 155 Mail Armour, -10, 0 156 Aged Brie, -16, 45 157 Aged Brie, -19, 50 158 Backstage passes to a TAFKAL80ETC concert, -5, 0 159 Backstage passes to a TAFKAL80ETC concert, -15, 0 160 Sulfuras, Hand of Ragnaros, -1, 80

knows_how_to_update_quality_for_items.approved.txt

1 Day 1 of 20 2 Mail Armour, 9, 19 3 Mail Armour, 9, 0 4 Aged Brie, 3, 10 5 Aged Brie, 0, 50 6 Backstage passes to a TAFKAL80ETC concert, 14, 18 7 Backstage passes to a TAFKAL80ETC concert, 4, 50 8 Sulfuras, Hand of Ragnaros, -1, 80 9 Day 2 of 20 10 Mail Armour, 8, 18

153 Day 20 of 20 154 Mail Armour, -10, 0 155 Mail Armour, -10, 0 156 Aged Brie, -16, 45 157 Aged Brie, -19, 50 158 Backstage passes to a TAFKAL80ETC concert, -5, 0 159 Backstage passes to a TAFKAL80ETC concert, -15, 0 160 Sulfuras, Hand of Ragnaros, -1, 80

New BehaviourExisting Behaviour

Changing Code

def perform_inventory_rollover(item)

case item.name when 'Conjured Mana' decrease_quality(item) decrease_quality(item) end

end

def perform_inventory_expiration(item)

case item.name

when 'Conjured Mana' decrease_quality(item) decrease_quality(item) end

end

def perform_inventory_rollover(item

case item.name when 'Conjured Mana' decrease_quality(item) decrease_quality(item) end

end

def perform_inventory_expiration(item)

case item.name

when 'Conjured Mana' decrease_quality(item) decrease_quality(item) end

end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Mail Armour', 10, 1], ['Aged Brie', 4, 9], ['Aged Brie', 1, 49], ['Backstage passes to a TAFKAL80ETC concert', 15, 17], ['Backstage passes to a TAFKAL80ETC concert', 5, 49], ['Sulfuras, Hand of Ragnaros', -1, 80], [‘Conjured Mana', 13, 50] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

def items item_attributes = [ ['Mail Armour', 10, 20], ['Mail Armour', 10, 1], ['Aged Brie', 4, 9], ['Aged Brie', 1, 49], ['Backstage passes to a TAFKAL80ETC concert', 15, 17], ['Backstage passes to a TAFKAL80ETC concert', 5, 49], ['Sulfuras, Hand of Ragnaros', -1, 80], [‘Conjured Mana', 13, 50] ]

item_attributes.map { |args| Item.new(*args) } end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items) characterization = characterize(subject, 2)

expect(characterization).to eq(expected) end

end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items)

verify { characterize(subject, 20) } end

end

describe GildedRose do

it 'knows how to update quality for items' do subject = GildedRose.new(items)

verify { characterize(subject, 20) } end

end

Approvals::ApprovalError: Approval Error: Received file “spec/fixtures/approvals/gildedrose/ knows_how_to_update_quality_for_items.received.txt” does not match approved “spec/fixtures/approvals/gildedrose/ knows_how_to_update_quality_for_items.approved.txt".

knows_how_to_update_quality_for_items.received.txt

4 Aged Brie, 3, 10 5 Aged Brie, 0, 50 6 Backstage passes to a TAFKAL80ETC concert, 14, 18 7 Backstage passes to a TAFKAL80ETC concert, 4, 50 8 Sulfuras, Hand of Ragnaros, -1, 80 9 Conjured Mana, 12, 48 10 Day 2 of 20 11 Mail Armour, 8, 18 12 …

knows_how_to_update_quality_for_items.approved.txt

4 Aged Brie, 3, 10 5 Aged Brie, 0, 50 6 Backstage passes to a TAFKAL80ETC concert, 14, 18 7 Backstage passes to a TAFKAL80ETC concert, 4, 50 8 Sulfuras, Hand of Ragnaros, -1, 80 9 Day 2 of 20 10 Mail Armour, 8, 18 11 …

knows_how_to_update_quality_for_items.received.txt

8 Sulfuras, Hand of Ragnaros, -1, 80 9 Conjured Mana, 12, 48 10 Day 2 of 20 11 Mail Armour, 8, 18 12 Mail Armour, 8, 0 13 Aged Brie, 2, 11 14 Aged Brie, -1, 50 15 Backstage passes to a TAFKAL80ETC concert, 13, 19 16 Backstage passes to a TAFKAL80ETC concert, 3, 50 17 Sulfuras, Hand of Ragnaros, -1, 80 18 Conjured Mana, 11, 46 19 Day 3 of 20 20 Mail Armour, 7, 17 21 Mail Armour, 7, 0 22 Aged Brie, 1, 12 23 Aged Brie, -2, 50 24 Backstage passes to a TAFKAL80ETC concert, 12, 20 25 Backstage passes to a TAFKAL80ETC concert, 2, 50 26 Sulfuras, Hand of Ragnaros, -1, 80 27 Conjured Mana, 10, 44 28 Day 4 of 20 29 Mail Armour, 6, 16 30 …

knows_how_to_update_quality_for_items.approved.txt

8 Sulfuras, Hand of Ragnaros, -1, 80 9 Conjured Mana, 12, 48 10 Day 2 of 20 11 Mail Armour, 8, 18 12 Mail Armour, 8, 0 13 Aged Brie, 2, 11 14 Aged Brie, -1, 50 15 Backstage passes to a TAFKAL80ETC concert, 13, 19 16 Backstage passes to a TAFKAL80ETC concert, 3, 50 17 Sulfuras, Hand of Ragnaros, -1, 80 18 Conjured Mana, 11, 46 19 Day 3 of 20 20 Mail Armour, 7, 17 21 Mail Armour, 7, 0 22 Aged Brie, 1, 12 23 Aged Brie, -2, 50 24 Backstage passes to a TAFKAL80ETC concert, 12, 20 25 Backstage passes to a TAFKAL80ETC concert, 2, 50 26 Sulfuras, Hand of Ragnaros, -1, 80 27 Conjured Mana, 10, 44 28 Day 4 of 20 29 Mail Armour, 6, 16 30 …

knows_how_to_update_quality_for_items.approved.txt

8 Sulfuras, Hand of Ragnaros, -1, 80 9 Conjured Mana, 12, 48 10 Day 2 of 20 11 Mail Armour, 8, 18 12 Mail Armour, 8, 0 13 Aged Brie, 2, 11 14 Aged Brie, -1, 50 15 Backstage passes to a TAFKAL80ETC concert, 13, 19 16 Backstage passes to a TAFKAL80ETC concert, 3, 50 17 Sulfuras, Hand of Ragnaros, -1, 80 18 Conjured Mana, 11, 46 19 Day 3 of 20 20 Mail Armour, 7, 17 21 Mail Armour, 7, 0 22 Aged Brie, 1, 12 23 Aged Brie, -2, 50 24 Backstage passes to a TAFKAL80ETC concert, 12, 20 25 Backstage passes to a TAFKAL80ETC concert, 2, 50 26 Sulfuras, Hand of Ragnaros, -1, 80 27 Conjured Mana, 10, 44 28 Day 4 of 20 29 Mail Armour, 6, 16 30 …

We win.

We preservedexisting behaviour and added new behaviour.

CharacterizationTesting

ApprovalTesting

CodeCoverage

MutationTesting

Are we ready to change code safely?

Changing Code

Existing Behaviour New Behaviour

Changing Code

New Behaviour(Approval Tests)

Existing Behaviour(Characterization Tests)

Be amazing atchanging code safely.

github.com/amckinnell/developer-testing

Recommended Reading

Recommended