Slide 1

Slide 1 text

TESTS SHOULD TELL A STORY LARACON US 2016

Slide 2

Slide 2 text

STORIES

Slide 3

Slide 3 text

WHO KNOWS THIS MAN?

Slide 4

Slide 4 text

FRED ROGERS

Slide 5

Slide 5 text

THE NEIGHBORHOOD LIVES ON

Slide 6

Slide 6 text

We live in a world in which we need to share responsibility. It's easy to say, “It's not my child, not my community, not my world, not my problem.” Then there are those who see the need and respond. I consider those people my heroes. Fred Rogers

Slide 7

Slide 7 text

TAYLOR OTWELL

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

YOUR CODE TELLS A STORY

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

TESTS SHOULD TELL A STORY DISTILLED SHARED KNOWLEDGE ▸ Good naming ▸ Good abstractions ▸ Details pushed down ▸ Intent clearly expressed ▸ Test driven (mostly)

Slide 15

Slide 15 text

TESTS SHOULD TELL A STORY IN STORY FORM ▸ Who are the characters? (Subject & Collaborators) ▸ What is the setting? (Environment) ▸ What is the plot? (Goal of our system) ▸ What is the conflict? (Task at hand) ▸ What is the resolution? (Solution)

Slide 16

Slide 16 text

STORIES ARE NOT JUST TOLD THEY ARE INTERPRETED

Slide 17

Slide 17 text

I NEED MAYO Coheed and Cambria HEADSHOT KID DISASTER FROM SECOND STAGE TURBINE BLADE

Slide 18

Slide 18 text

WHAT DO YOU SEE?

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

EVEN FUTURE-YOU WILL INTERPRET CODE DIFFERENTLY

Slide 21

Slide 21 text

TESTS SHOULD TELL A STORY COMMUNICATIVE CONCERNS TO CONSIDER ▸ Skill level ▸ Ambiguous intent ▸ Cultural differences ▸ Language differences ▸ Contextual differences ▸ Mood at the time of reading ▸ Knowledge of area/domain of code

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

TESTS SHOULD TELL A STORY EXPECTATIONS ▸ Ex: A method named read in a CSV parsing library ▸ Load the entire file into memory into an array ▸ Stream the file, each line as an array ▸ Load the file into a lazily evaluated collection - each row lazily loaded in an array

Slide 24

Slide 24 text

AMBIGUITY LEAVES ROOM FOR INTERPRETATION

Slide 25

Slide 25 text

We don’t make mistakes, we have happy accidents. Bob Ross

Slide 26

Slide 26 text

WE ARE NOT BOB ROSS

Slide 27

Slide 27 text

WE MAKE MISTAKES

Slide 28

Slide 28 text

OUR MISTAKES HAVE CONSEQUENCES

Slide 29

Slide 29 text

WRITE TESTS TO PREVENT HAPPY ACCIDENTS

Slide 30

Slide 30 text

YOU DO WRITE TESTS, DON’T YOU?

Slide 31

Slide 31 text

ಠ_ಠ

Slide 32

Slide 32 text

ಠ_ಠ

Slide 33

Slide 33 text

ಠ_ಠ

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

KEYNOTE !"

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

TESTS

Slide 38

Slide 38 text

ANATOMY OF A TEST

Slide 39

Slide 39 text

TESTS SHOULD TELL A STORY ANATOMY OF A TEST - FOUR PHASE ▸ Setup environment and preconditions ▸ Exercise the method/system under test ▸ Verify the result ▸ Tear down the environment

Slide 40

Slide 40 text

TESTS SHOULD TELL A STORY ANATOMY OF A TEST - AAA ▸ Arrange preconditions and input ▸ Act on the object/method/system under test ▸ Assert what you expect the result to be

Slide 41

Slide 41 text

TESTS SHOULD TELL A STORY ANATOMY OF A TEST - GIVEN-WHEN-THEN ▸ Arrange - Given ▸ Act - When ▸ Assert - Then

Slide 42

Slide 42 text

SETTING A BASELINE

Slide 43

Slide 43 text

TESTS SHOULD TELL A STORY PHPUNIT subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }

Slide 44

Slide 44 text

XUNIT

Slide 45

Slide 45 text

TESTS SHOULD TELL A STORY XUNIT - PHPUNIT subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }

Slide 46

Slide 46 text

TESTS SHOULD TELL A STORY XUNIT - PHPUNIT subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }

Slide 47

Slide 47 text

TESTS SHOULD TELL A STORY XUNIT - PHPUNIT subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }

Slide 48

Slide 48 text

TESTS SHOULD TELL A STORY XUNIT - PHPUNIT subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }

Slide 49

Slide 49 text

TESTS SHOULD TELL A STORY XUNIT - PHPUNIT subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }

Slide 50

Slide 50 text

TESTS SHOULD TELL A STORY XUNIT - PHPUNIT subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }

Slide 51

Slide 51 text

TESTS SHOULD TELL A STORY XUNIT - PHPUNIT subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }

Slide 52

Slide 52 text

TESTS SHOULD TELL A STORY XUNIT - PHPUNIT subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }

Slide 53

Slide 53 text

SPEC/BDD STYLE

Slide 54

Slide 54 text

AT A BASIC LEVEL VERY SIMILAR

Slide 55

Slide 55 text

TESTS SHOULD TELL A STORY XUNIT - PHPUNIT subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }

Slide 56

Slide 56 text

TESTS SHOULD TELL A STORY SPEC/BDD - PHO subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); }); afterEach(function() { $this->subject->clear(); }); it(‘can find a user by name’, function() { $user = $this->subject->findByName($this->name); expect($user->name)->toEqual($this->name); }); });

Slide 57

Slide 57 text

TESTS SHOULD TELL A STORY XUNIT - PHPUNIT subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }

Slide 58

Slide 58 text

TESTS SHOULD TELL A STORY SPEC/BDD - PHO subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); }); afterEach(function() { $this->subject->clear(); }); it(‘can find a user by name’, function() { $user = $this->subject->findByName($this->name); expect($user->name)->toEqual($this->name); }); });

Slide 59

Slide 59 text

TESTS SHOULD TELL A STORY SPEC/BDD - PHO subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); }); afterEach(function() { $this->subject->clear(); }); it(‘can find a user by name’, function() { $user = $this->subject->findByName($this->name); expect($user->name)->toEqual($this->name); }); });

Slide 60

Slide 60 text

TESTS SHOULD TELL A STORY SPEC/BDD - PHO subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); }); afterEach(function() { $this->subject->clear(); }); it(‘can find a user by name’, function() { $user = $this->subject->findByName($this->name); expect($user->name)->toEqual($this->name); }); });

Slide 61

Slide 61 text

TESTS SHOULD TELL A STORY SPEC/BDD - PHO subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); }); afterEach(function() { $this->subject->clear(); }); it(‘can find a user by name’, function() { $user = $this->subject->findByName($this->name); expect($user->name)->toEqual($this->name); }); });

Slide 62

Slide 62 text

TESTS SHOULD TELL A STORY SPEC/BDD - PHO subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); }); afterEach(function() { $this->subject->clear(); }); it(‘can find a user by name’, function() { $user = $this->subject->findByName($this->name); expect($user->name)->toEqual($this->name); }); });

Slide 63

Slide 63 text

TESTS SHOULD TELL A STORY SPEC/BDD - PHO subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); }); afterEach(function() { $this->subject->clear(); }); it(‘can find a user by name’, function() { $user = $this->subject->findByName($this->name); expect($user->name)->toEqual($this->name); }); });

Slide 64

Slide 64 text

TESTS SHOULD TELL A STORY SPEC/BDD - PHO subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); }); afterEach(function() { $this->subject->clear(); }); it(‘can find a user by name’, function() { $user = $this->subject->findByName($this->name); expect($user->name)->toEqual($this->name); }); });

Slide 65

Slide 65 text

TESTS SHOULD TELL A STORY SPEC/BDD - PHO subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); }); afterEach(function() { $this->subject->clear(); }); it(‘can find a user by name’, function() { $user = $this->subject->findByName($this->name); expect($user->name)->toEqual($this->name); }); });

Slide 66

Slide 66 text

TESTS SHOULD TELL A STORY SPEC/BDD - PHO subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); }); afterEach(function() { $this->subject->clear(); }); it(‘can find a user by name’, function() { $user = $this->subject->findByName($this->name); expect($user->name)->toEqual($this->name); }); });

Slide 67

Slide 67 text

TESTS SHOULD TELL A STORY NESTED CONTEXTS subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); }); afterEach(function() { $this->subject->clear(); }); context(‘when finding a user’, function() { it(‘can find an existing user by name’, function() { $user = $this->subject->findByName($this->name); expect($user->name)->toEqual($this->name); }); it(‘returns null when given an invalid name’, function() { $user = $this->subject->findByName(‘Santa Claus’); expect($user)->toEqual(null); }); }); });

Slide 68

Slide 68 text

TESTS SHOULD TELL A STORY SPEC OR DOCUMENTATION FORMATTER PRINTS AS A User Repository when finding a user can find an existing user by name returns null when given an invalid username

Slide 69

Slide 69 text

TESTS SHOULD TELL A STORY SUBJECTIVE PREFERENCE TO BDD SPECS OVER XUNIT ▸ Generally written out in sentences ▸ Easily mapped to user stories ▸ Easily mapped to acceptance criteria ▸ Easily readable by stakeholders ▸ Allow for contexts to be described easily ▸ Allow for nested contexts ▸ Handles multiple cases well ▸ I work with them day-to-day

Slide 70

Slide 70 text

HOW ARE YOU FEELING ABOUT THE SPEC DSL?

Slide 71

Slide 71 text

HOW ARE YOU FEELING ABOUT THE SPEC DSL?

Slide 72

Slide 72 text

SPEC LIBRARIES WE’LL BE USING

Slide 73

Slide 73 text

SPEC LIBRARIES WE’LL BE USING

Slide 74

Slide 74 text

JASMINE & RSPEC

Slide 75

Slide 75 text

TESTS SHOULD TELL A STORY JASMINE describe(‘A User Repository’, function() { const subject = new UserRepository(); const name = ‘Machuga’; beforeEach(function() { subject.save(new User(name)); }); afterEach(function() { subject.clear(); }); it(‘can find a user by name’, function() { const user = subject.findByName(name); expect(user.name).toEqual(name); }); });

Slide 76

Slide 76 text

TESTS SHOULD TELL A STORY JASMINE describe(‘A User Repository’, function() { const subject = new UserRepository(); const name = ‘Machuga’; beforeEach(function() { subject.save(new User(name)); }); afterEach(function() { subject.clear(); }); it(‘can find a user by name’, function() { const user = subject.findByName(name); expect(user.name).toEqual(name); }); }); const defines a variable that cannot be redefined

Slide 77

Slide 77 text

TESTS SHOULD TELL A STORY JASMINE describe(‘A User Repository’, function() { const subject = new UserRepository(); const name = ‘Machuga’; beforeEach(function() { subject.save(new User(name)); }); afterEach(function() { subject.clear(); }); it(‘can find a user by name’, function() { const user = subject.findByName(name); expect(user.name).toEqual(name); }); }); dot syntax rather than ->

Slide 78

Slide 78 text

TESTS SHOULD TELL A STORY JASMINE describe(‘A User Repository’, function() { const subject = new UserRepository(); const name = ‘Machuga’; beforeEach(function() { subject.save(new User(name)); }); afterEach(function() { subject.clear(); }); it(‘can find a user by name’, function() { const user = subject.findByName(name); expect(user.name).toEqual(name); }); }); lexical scope for variables

Slide 79

Slide 79 text

TESTS SHOULD TELL A STORY RSPEC describe ‘A User Repository’ do subject { UserRepository.new } let(:name) { ‘Machuga’ } before do subject.save(User.new(name)) end after do subject.clear! end it ‘can find a user by name’ do user = subject.find_by_name(name) expect(user.name).to eq(name) end end

Slide 80

Slide 80 text

TESTS SHOULD TELL A STORY RSPEC describe ‘A User Repository’ do subject { UserRepository.new } let(:name) { ‘Machuga’ } before do subject.save(User.new(name)) end after do subject.clear! end it ‘can find a user by name’ do user = subject.find_by_name(name) expect(user.name).to eq(name) end end blocks denoted by { } or do..end

Slide 81

Slide 81 text

TESTS SHOULD TELL A STORY RSPEC describe ‘A User Repository’ do subject { UserRepository.new } let(:name) { ‘Machuga’ } before do subject.save(User.new(name)) end after do subject.clear! end it ‘can find a user by name’ do user = subject.find_by_name(name) expect(user.name).to eq(name) end end optional syntax

Slide 82

Slide 82 text

TESTS SHOULD TELL A STORY RSPEC describe ‘A User Repository’ do subject { UserRepository.new } let(:name) { ‘Machuga’ } before do subject.save(User.new(name)) end after do subject.clear! end it ‘can find a user by name’ do user = subject.find_by_name(name) expect(user.name).to eq(name) end end new is a method on the class

Slide 83

Slide 83 text

TESTS SHOULD TELL A STORY RSPEC describe ‘A User Repository’ do subject { UserRepository.new } let(:name) { ‘Machuga’ } before do subject.save(User.new(name)) end after do subject.clear! end it ‘can find a user by name’ do user = subject.find_by_name(name) expect(user.name).to eq(name) end end can use ! and ? in method names

Slide 84

Slide 84 text

5 TIPS FOR SPEC STORYTELLING OR: I TRIED THESE 5 WEIRD TRICKS ON MY TESTS; YOU’LL NEVER GUESS WHAT HAPPENED NEXT!

Slide 85

Slide 85 text

1. BE EXPRESSIVE

Slide 86

Slide 86 text

CUSTOM ACTIONS FOR COMPLEX BEHAVIOR

Slide 87

Slide 87 text

TESTS SHOULD TELL A STORY RSPEC ACCEPTANCE TEST describe 'items' do
 it 'should be able to be dragged into categories' do
 drag_item_to_category_with_text 'apple', 'a-f' expect('apple').to be_in_category_with_text(‘a-f')
 end
 end

Slide 88

Slide 88 text

TESTS SHOULD TELL A STORY LAYOUT CHECK NEXT

Slide 89

Slide 89 text

TESTS SHOULD TELL A STORY LAYOUT CHECK NEXT

Slide 90

Slide 90 text

TESTS SHOULD TELL A STORY RSPEC CUSTOM ACTION # Perform drag and drop on item by name to category by name
 def drag_item_to_category_with_text(item_text, category_text)
 item(item_text).drag_to category_with_text(category_text)
 end

Slide 91

Slide 91 text

TESTS SHOULD TELL A STORY RSPEC CUSTOM ACTION # Perform drag and drop on item by name to category by name
 def drag_item_to_category_with_text(item_text, category_text)
 item(item_text).drag_to category_with_text(category_text)
 end 
 def item(text)
 sel = “#{item_prefix}_categorizable_selector”
 page.find(send(sel), text: text)
 end 
 def category_with_text(text)
 sel = “#{category_prefix}_category_selector”
 page.find(send(sel), text: text)
 end

Slide 92

Slide 92 text

TESTS SHOULD TELL A STORY RSPEC ACCEPTANCE TEST REPRISE describe 'items' do
 it 'should be able to be dragged into categories' do
 drag_item_to_category_with_text 'apple', 'a-f' expect('apple').to be_in_category_with_text(‘a-f')
 end
 end

Slide 93

Slide 93 text

TESTS SHOULD TELL A STORY RSPEC ACCEPTANCE TEST UNFOLDED describe 'items' do
 it 'should be able to be dragged into categories' do
 item = page.find(
 send(#{item_prefix}_categorizable_selector”),
 text: ‘apple’)
 category = page.find(
 send(“#{category_prefix}_category_selector”),
 text: ‘a-f’)
 
 item.drag_to category expect('apple').to be_in_category_with_text(‘a-f')
 end
 end

Slide 94

Slide 94 text

TESTS SHOULD TELL A STORY RSPEC ACCEPTANCE TEST DOUBLED DOWN describe 'items' do it 'should be able to be dragged into categories' do
 item1 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'apple') item2 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'oranges') item3 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'bananas') item4 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'kiwis') category = page.find(
 send('#{category_prefix}_category_selector'),
 text: 'a-f')
 item1.drag_to category item2.drag_to category item3.drag_to category item4.drag_to category expect(uncategorized_items).to be_empty
 end
 end

Slide 95

Slide 95 text

TESTS SHOULD TELL A STORY RSPEC ACCEPTANCE TEST DOUBLED DOWN describe 'items' do it 'should be able to be dragged into categories' do
 item1 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'apple') item2 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'oranges') item3 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'bananas') item4 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'kiwis') category = page.find(
 send('#{category_prefix}_category_selector'),
 text: 'a-f')
 item1.drag_to category item2.drag_to category item3.drag_to category item4.drag_to category expect(uncategorized_items).to be_empty
 end
 end

Slide 96

Slide 96 text

TESTS SHOULD TELL A STORY RSPEC ACCEPTANCE TEST NOPE describe 'items' do it 'should be able to be dragged into categories' do
 item1 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'apple') item2 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'oranges') item3 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'bananas') item4 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'kiwis') category1 = page.find(
 send('#{category_prefix}_category_selector'),
 text: 'a-f')
 category2 = page.find(
 send('#{category_prefix}_category_selector'),
 text: 'g-n')
 category3 = page.find(
 send('#{category_prefix}_category_selector'),
 text: 'o-w')
 category4 = page.find(
 send('#{category_prefix}_category_selector'),
 text: 'x-z')
 item1.drag_to category1 item2.drag_to category2 item3.drag_to category3 item4.drag_to category4 check_button.click expect(next_button).to be_enabled
 end
 end

Slide 97

Slide 97 text

TESTS SHOULD TELL A STORY RSPEC ACCEPTANCE TEST NOPE describe 'items' do it 'should be able to be dragged into categories' do
 item1 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'apple') item2 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'oranges') item3 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'bananas') item4 = page.find(
 send("#{item_prefix}_categorizable_selector"),
 text: 'kiwis') category1 = page.find(
 send('#{category_prefix}_category_selector'),
 text: 'a-f')
 category2 = page.find(
 send('#{category_prefix}_category_selector'),
 text: 'g-n')
 category3 = page.find(
 send('#{category_prefix}_category_selector'),
 text: 'o-w')
 category4 = page.find(
 send('#{category_prefix}_category_selector'),
 text: 'x-z')
 item1.drag_to category1 item2.drag_to category2 item3.drag_to category3 item4.drag_to category4 check_button.click expect(next_button).to be_enabled
 end
 end

Slide 98

Slide 98 text

TESTS ARE CODE REFACTOR!

Slide 99

Slide 99 text

USE THE BEST MATCHER / ASSERTION

Slide 100

Slide 100 text

expect((2+2).to_s == 4).to eq true

Slide 101

Slide 101 text

Failures: 1) Hi, Laracon! # Failure/Error: expect((2+2).to_s == 4).to eq true expected true got false (compared using ==) # ./testing_spec.rb:3:in `block (2 levels) in ' Finished in 0.02861 seconds (files took 0.16021 seconds to load) 1 example, 1 failure Failed examples: rspec ./testing_spec.rb:2 # My awesome example group works

Slide 102

Slide 102 text

expect((2+2).to_s).to eq 4

Slide 103

Slide 103 text

Failures: 1) Hi, Laracon! # Failure/Error: expect((2+2).to_s).to eq 4 expected: 4 got: "4" (compared using ==) # ./testing_spec.rb:3:in `block (2 levels) in ' Finished in 0.01195 seconds (files took 0.13384 seconds to load) 1 example, 1 failure Failed examples: rspec ./testing_spec.rb:2 # My awesome example group works

Slide 104

Slide 104 text

CUSTOM MATCHERS / ASSERTIONS

Slide 105

Slide 105 text

TESTS SHOULD TELL A STORY LARAVEL CUSTOM ASSERTION /** * Asserts that the response contains the given header * and equals the optional value. * * @param string $headerName * @param mixed $value * @return $this */ protected function seeHeader($headerName, $value = null) { $headers = $this->response->headers; $this->assertTrue($headers->has($headerName), "Header [{$headerName}] not present on response."); if (! is_null($value)) { $this->assertEquals( $headers->get($headerName), $value, "Header [{$headerName}] was found, but value [{$headers->get($headerName)}] does not match [{$value}]." ); } return $this; }

Slide 106

Slide 106 text

TESTS SHOULD TELL A STORY PHPUNIT CUSTOM ASSERTIONS BLESSED METHOD

Slide 107

Slide 107 text

TESTS SHOULD TELL A STORY RSPEC CUSTOM MATCHER require 'rspec/expectations' RSpec::Matchers.define :be_a_multiple_of do |expected| match do |actual| actual % expected == 0 end failure_message do |actual| "expected that #{actual} would be a multiple of #{expected}" end end RSpec.describe 9 do it ‘should blow up and be helpful’ do expect(9).to be_a_multiple_of(4) end end Failures: 1) 9 should blow up and be helpful Failure/Error: expect(9).to be_a_multiple_of(4) expected that 9 would be a multiple of 4

Slide 108

Slide 108 text

2. RESPECT THE CUKES

Slide 109

Slide 109 text

TESTS SHOULD TELL A STORY CUCUMBER

Slide 110

Slide 110 text

TESTS SHOULD TELL A STORY USER STORIES AS GHERKIN SPECS Feature: Getting Laracon attendees test their code In order to convince Laracon attendees to test their code
 As a speaker
 I want to provide helpful information and entertainment Scenario: Adam Wathan gives a TDD talk prior to my talk
 Given that attendees recently saw Adam’s talk
 And I was in the audience
 When I go to give my talk
 Then I can make some assumptions on what attendees know
 And I can weep as a rearrange my slide deck

Slide 111

Slide 111 text

TESTS SHOULD TELL A STORY USER STORIES AS RSPEC SPECS describe ‘Convincing Laracon attendees to test their code’ do describe ‘Giving a talk as a speaker’ do context ‘Adam Wathan gives a TDD talk the day before’ do it ‘can make some assumptions on what attendees know’ it ‘can weep while rearranging slide deck’ end end end

Slide 112

Slide 112 text

TESTS SHOULD TELL A STORY BEHAVIOR-DRIVEN DEVELOPMENT ‣ Describing/designing an application as how it appears to the stakeholder ‣ Feature-driven workflow ‣ Embraces concepts of the Agile Manifesto ‣ Individuals and interactions over processes and tools ‣ Working software over comprehensive documentation ‣ Customer collaboration over contract negotiation ‣ Responding to change over following a plan

Slide 113

Slide 113 text

3. BAD STORIES ARE SIGNALS

Slide 114

Slide 114 text

TESTS SHOULD TELL A STORY SMELLS OF BAD STORIES ▸ Plot holes ▸ Lack of Character Development ▸ No Descriptions ▸ Switching narrative

Slide 115

Slide 115 text

TESTS SHOULD TELL A STORY PLOT HOLES ▸ Mocking the object under test ▸ Global state mutations out of view ▸ Implicit Behavior

Slide 116

Slide 116 text

TESTS SHOULD TELL A STORY NO DESCRIPTIONS ▸ Creating abstractions that provide no benefit ▸ No explanation why the environment is in current state ▸ The world is bland and empty

Slide 117

Slide 117 text

TESTS SHOULD TELL A STORY LACK OF CHARACTER DEVELOPMENT ▸ Performing transformations on characters out of view ▸ Mocking collaborators without clear reason

Slide 118

Slide 118 text

TESTS SHOULD TELL A STORY SWITCHING NARRATIVES ▸ Setting expectations on collaborators ▸ Blurring lines between suites

Slide 119

Slide 119 text

4. UNIT TEST IN ISOLATION

Slide 120

Slide 120 text

MORE ON THIS SOON

Slide 121

Slide 121 text

5. TRY TDD/BDD

Slide 122

Slide 122 text

TWO WEEKS

Slide 123

Slide 123 text

TESTS SHOULD TELL A STORY FEATURE-FIRST ▸ Build only what your client needs at that moment ▸ Supports slice plans ▸ Waste few resources to test an idea ▸ Provides readable, agreed upon specs for stakeholders ▸ Encourages collaboration

Slide 124

Slide 124 text

TESTS SHOULD TELL A STORY DRIVING YOUR DESIGN & CODE

Slide 125

Slide 125 text

TESTS SHOULD TELL A STORY DRIVING YOUR DESIGN & CODE ▸ Write failing test first ▸ Evidence feature is incomplete ▸ Evidence feature is not coincidently passing

Slide 126

Slide 126 text

TESTS SHOULD TELL A STORY DRIVING YOUR DESIGN & CODE ▸ Write failing test first ▸ Evidence feature is incomplete ▸ Evidence feature is not coincidently passing ▸ Write smallest amount of code ▸ Evidence necessary code to solve that use-case is implemented

Slide 127

Slide 127 text

TESTS SHOULD TELL A STORY DRIVING YOUR DESIGN & CODE ▸ Write failing test first ▸ Evidence feature is incomplete ▸ Evidence feature is not coincidently passing ▸ Write smallest amount of code ▸ Evidence necessary code to solve that use-case is implemented ▸ Listen to feedback & refactor code & test in pieces ▸ Confidence nothing is broken ▸ Aids in better design

Slide 128

Slide 128 text

TESTS SHOULD TELL A STORY DRIVING YOUR DESIGN & CODE ▸ Write failing test first ▸ Evidence feature is incomplete ▸ Evidence feature is not coincidently passing ▸ Write smallest amount of code ▸ Evidence necessary code to solve that use-case is implemented ▸ Listen to feedback & refactor code & test in pieces ▸ Confidence nothing is broken ▸ Aids in better design ▸ Taking small steps ▸ Ensures frequent save points

Slide 129

Slide 129 text

TIME FOR A STORY WARNING: MATH AHEAD

Slide 130

Slide 130 text

No content

Slide 131

Slide 131 text

TESTS SHOULD TELL A STORY LMS ▸ Per School Year ▸ ~ 3 Million Students ▸ ~ 2 Million Live Help Sessions ▸ ~ 1 Billion Math Problems ▸ Math Content Quantity Comparable to Khan Academy ▸ A gigantic Rails monolith + two SPA’s

Slide 132

Slide 132 text

No content

Slide 133

Slide 133 text

TESTS SHOULD TELL A STORY LIVE HELP ▸ Teachers available to help students ▸ Voice ▸ Text ▸ Whiteboard

Slide 134

Slide 134 text

No content

Slide 135

Slide 135 text

No content

Slide 136

Slide 136 text

TESTS SHOULD TELL A STORY LIVE HELP ▸ Originally in Flash ▸ Reimagined & Rewritten in 2013 ▸ Goals of rewrite ▸ Decrease wait time of over 2 minutes ▸ Work on iPad ▸ Improve performance and maintainability ▸ Better pairing of teachers to students

Slide 137

Slide 137 text

TESTS SHOULD TELL A STORY LIVE HELP ▸ Rewrote in CoffeeScript with Node & Angular ▸ Great Success ▸ Throughput bumped to the current level ▸ Wait times decreased to typically < 15 seconds ▸ Compatible with iPad ▸ Improved pairing students to teachers

Slide 138

Slide 138 text

TESTS SHOULD TELL A STORY LIVE HELP ▸ Left largely untouched until big update in late 2015 ▸ Compiled all CoffeeScript to JS, switched to ES2015 ▸ Simplified server-side to boost performance + scaling

Slide 139

Slide 139 text

THE PAIRING ALGORITHM

Slide 140

Slide 140 text

No content

Slide 141

Slide 141 text

TESTS SHOULD TELL A STORY LIVE HELP PAIRING ALGORITHM ▸ Time-based weighting queue ▸ Grows linearly over time ▸ Those who are waiting longer get selected first ▸ Bonus weighting for teachers/students speaking same language ▸ Adds large offset that tapers over time ▸ Offset becomes 0 at threshold ▸ Fastpass match weight - Guaranteed win

Slide 142

Slide 142 text

TESTS SHOULD TELL A STORY GRAPHS! Weight Points 0 5000 10000 15000 20000 Seconds 0 5 10 15 20 Student w/ Language Mismatch Student w/ Language Match

Slide 143

Slide 143 text

TESTS SHOULD TELL A STORY LIVE HELP PAIRING ALGORITHM - WITHIN THRESHOLD Given a language boost threshold of 15000
 And EnglishStudent1 has waited 11s
 And SpanishStudent1 has waited 6s (13.5s)
 When a Spanish Teacher pulls in another student
 Then that student will be SpanishStudent1 Weight Points 0 15000 30000 Seconds 0 5 10 15 20 25 30 English Student Spanish Student

Slide 144

Slide 144 text

TESTS SHOULD TELL A STORY LIVE HELP PAIRING ALGORITHM - WITHIN THRESHOLD Given a language boost threshold of 15000
 And EnglishStudent1 has waited 11s
 And SpanishStudent1 has waited 6s (13.5s)
 When a Spanish Teacher pulls in another student
 Then that student will be SpanishStudent1 Weight Points 0 15000 30000 Seconds 0 5 10 15 20 25 30 English Student Spanish Student

Slide 145

Slide 145 text

TESTS SHOULD TELL A STORY LIVE HELP PAIRING ALGORITHM - PAST THRESHOLD Given a language boost threshold of 15000
 And EnglishStudent1 has waited 20s
 And SpanishStudent1 has waited 15s (15s)
 When a Spanish Teacher pulls in another student
 Then that student will be EnglishStudent1 Weight Points 0 15000 30000 Seconds 0 5 10 15 20 25 30 English Student Spanish Student

Slide 146

Slide 146 text

REFACTORING SPECS

Slide 147

Slide 147 text

TESTS SHOULD TELL A STORY PREVIOUS SPEC (COMPILED FROM COFFEESCRIPT) describe("PairMatcher", function() { describe("Pair scoring order", function() { describe("Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { var match1, match2; this.offsetStudentTimes(); match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe('es'); expect(match2.getLanguage()).toBe('es'); }); }); }); });

Slide 148

Slide 148 text

TESTS SHOULD TELL A STORY EXISTING SPEC describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); });

Slide 149

Slide 149 text

TESTS SHOULD TELL A STORY EXISTING SPEC describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); });

Slide 150

Slide 150 text

TESTS SHOULD TELL A STORY EXISTING SPEC describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); });

Slide 151

Slide 151 text

TESTS SHOULD TELL A STORY EXISTING SPEC describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); WHAT LANGUAGE DOES STUDENT SPEAK?

Slide 152

Slide 152 text

TESTS SHOULD TELL A STORY EXISTING SPEC describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); });

Slide 153

Slide 153 text

TESTS SHOULD TELL A STORY EXISTING SPEC describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); });

Slide 154

Slide 154 text

TESTS SHOULD TELL A STORY EXISTING SPEC describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); WTF IS THIS?

Slide 155

Slide 155 text

TESTS SHOULD TELL A STORY EXISTING SPEC describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); WTF IS THIS? ADD OPTIONAL OFFSET
 STAGGER WAIT TIMES
 REVERSE CHRONOLOGICALLY

Slide 156

Slide 156 text

TESTS SHOULD TELL A STORY EXISTING SPEC describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); });

Slide 157

Slide 157 text

TESTS SHOULD TELL A STORY EXISTING SPEC describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); WUT?

Slide 158

Slide 158 text

TESTS SHOULD TELL A STORY EXISTING SPEC describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); });

Slide 159

Slide 159 text

TESTS SHOULD TELL A STORY THE OLD STORY Describe the pair order for Spanish speaking teachers Given this list of four students [es, en, es, en]
 And all have wait times within the language threshold
 And each is waiting longer than the previous
 When the teacher pulls two new students
 Then the teacher should receive the Spanish speaking students

Slide 160

Slide 160 text

TESTS SHOULD TELL A STORY THE OLD STORY describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); THE PROLOGUE

Slide 161

Slide 161 text

TESTS SHOULD TELL A STORY THE OLD STORY describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); THE SUPPORTING CAST

Slide 162

Slide 162 text

TESTS SHOULD TELL A STORY THE OLD STORY describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); THE PROTAGONIST

Slide 163

Slide 163 text

TESTS SHOULD TELL A STORY THE OLD STORY describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); THE PLOT

Slide 164

Slide 164 text

TESTS SHOULD TELL A STORY THE OLD STORY describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); M. NIGHT SHYMALAN TWIST &
 NO CHARACTER DEVELOPMENT

Slide 165

Slide 165 text

TESTS SHOULD TELL A STORY THE OLD STORY describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); THE CONFLICT

Slide 166

Slide 166 text

TESTS SHOULD TELL A STORY THE OLD STORY describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); PLOT HOLE

Slide 167

Slide 167 text

TESTS SHOULD TELL A STORY THE OLD STORY describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); BREAKING DOWN THE 4TH WALL

Slide 168

Slide 168 text

TESTS SHOULD TELL A STORY THE OLD STORY describe(“PairMatcher Pair scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); THE RESOLUTION

Slide 169

Slide 169 text

TESTS SHOULD TELL A STORY GOOD THINGS ABOUT THIS SPEC

Slide 170

Slide 170 text

TESTS SHOULD TELL A STORY GOOD THINGS ABOUT THIS SPEC ▸ Mostly clear name for example group and test description

Slide 171

Slide 171 text

TESTS SHOULD TELL A STORY GOOD THINGS ABOUT THIS SPEC ▸ Mostly clear name for example group and test description ▸ Using this (disposable context in Jasmine)

Slide 172

Slide 172 text

TESTS SHOULD TELL A STORY GOOD THINGS ABOUT THIS SPEC ▸ Mostly clear name for example group and test description ▸ Using this (disposable context in Jasmine) ▸ Assertions match description

Slide 173

Slide 173 text

TESTS SHOULD TELL A STORY NOT-SO-GOOD THINGS ABOUT THIS SPEC

Slide 174

Slide 174 text

TESTS SHOULD TELL A STORY NOT-SO-GOOD THINGS ABOUT THIS SPEC ▸ Helpers cause side-effects (offsetTimes, removeStudent)

Slide 175

Slide 175 text

TESTS SHOULD TELL A STORY NOT-SO-GOOD THINGS ABOUT THIS SPEC ▸ Helpers cause side-effects (offsetTimes, removeStudent) ▸ Object under test has dependency mutated after use

Slide 176

Slide 176 text

TESTS SHOULD TELL A STORY NOT-SO-GOOD THINGS ABOUT THIS SPEC ▸ Helpers cause side-effects (offsetTimes, removeStudent) ▸ Object under test has dependency mutated after use ▸ Character development happens off-screen

Slide 177

Slide 177 text

TESTS SHOULD TELL A STORY NOT-SO-GOOD THINGS ABOUT THIS SPEC ▸ Helpers cause side-effects (offsetTimes, removeStudent) ▸ Object under test has dependency mutated after use ▸ Character development happens off-screen ▸ Expectations are dependent on result of side-effects

Slide 178

Slide 178 text

TESTS SHOULD TELL A STORY NOT-SO-GOOD THINGS ABOUT THIS SPEC ▸ Helpers cause side-effects (offsetTimes, removeStudent) ▸ Object under test has dependency mutated after use ▸ Character development happens off-screen ▸ Expectations are dependent on result of side-effects ▸ Three methods of creating a student are used

Slide 179

Slide 179 text

TESTS PASS SPANISH STUDENTS ARE PULLED FIRST

Slide 180

Slide 180 text

TESTS SHOULD TELL A STORY THE NEW STORY Describe A Spanish Speaking teacher pulling students with the same language Given all wait times are within the threshold
 And englishStudent1 has been waiting 3s
 And spanishStudent2 has been waiting 2s
 And spanishStudent1 has been waiting 1s
 When the teacher pulls the next student
 Then the teacher should receive spanishStudent2

Slide 181

Slide 181 text

TESTS SHOULD TELL A STORY THE NEW STORY describe('StudentTeacherScorer', function() { var sessions = []; //… describe('A teacher speaking same language as a student', function() { beforeEach(function() { spanishStudent1.waitTime = 1000; spanishStudent2.waitTime = 2000; englishStudent1.waitTime = 3000; sessions.push(englishStudent1); sessions.push(spanishStudent2); sessions.push(spanishStudent1); }); it('will pick the student waiting longest', function() { const chosenStudent = scorer.findBestMatchFor(sessions, spanishTeacher); expect(chosenStudent).toEqual(spanishStudent2); }); }); });

Slide 182

Slide 182 text

TESTS SHOULD TELL A STORY THE NEW STORY describe('StudentTeacherScorer', function() { var sessions = []; //… describe('A teacher speaking same language as a student', function() { beforeEach(function() { spanishStudent1.waitTime = 1000; spanishStudent2.waitTime = 2000; englishStudent1.waitTime = 3000; sessions.push(englishStudent1); sessions.push(spanishStudent2); sessions.push(spanishStudent1); }); it('will pick the student waiting longest', function() { const chosenStudent = scorer.findBestMatchFor(sessions, spanishTeacher); expect(chosenStudent).toEqual(spanishStudent2); }); }); }); THE PROLOGUE

Slide 183

Slide 183 text

TESTS SHOULD TELL A STORY THE NEW STORY describe('StudentTeacherScorer', function() { var sessions = []; //… describe('A teacher speaking same language as a student', function() { beforeEach(function() { spanishStudent1.waitTime = 1000; spanishStudent2.waitTime = 2000; englishStudent1.waitTime = 3000; sessions.push(englishStudent1); sessions.push(spanishStudent2); sessions.push(spanishStudent1); }); it('will pick the student waiting longest', function() { const chosenStudent = scorer.findBestMatchFor(sessions, spanishTeacher); expect(chosenStudent).toEqual(spanishStudent2); }); }); }); CHARACTER DEVELOPMENT

Slide 184

Slide 184 text

TESTS SHOULD TELL A STORY THE NEW STORY describe('StudentTeacherScorer', function() { var sessions = []; //… describe('A teacher speaking same language as a student', function() { beforeEach(function() { spanishStudent1.waitTime = 1000; spanishStudent2.waitTime = 2000; englishStudent1.waitTime = 3000; sessions.push(englishStudent1); sessions.push(spanishStudent2); sessions.push(spanishStudent1); }); it('will pick the student waiting longest', function() { const chosenStudent = scorer.findBestMatchFor(sessions, spanishTeacher); expect(chosenStudent).toEqual(spanishStudent2); }); }); }); THE CHOSEN ONE

Slide 185

Slide 185 text

TESTS SHOULD TELL A STORY THE NEW STORY describe('StudentTeacherScorer', function() { var sessions = []; //… describe('A teacher speaking same language as a student', function() { beforeEach(function() { spanishStudent1.waitTime = 1000; spanishStudent2.waitTime = 2000; englishStudent1.waitTime = 3000; sessions.push(englishStudent1); sessions.push(spanishStudent2); sessions.push(spanishStudent1); }); it('will pick the student waiting longest', function() { const chosenStudent = scorer.findBestMatchFor(sessions, spanishTeacher); expect(chosenStudent).toEqual(spanishStudent2); }); }); }); THE PLOT

Slide 186

Slide 186 text

TESTS SHOULD TELL A STORY THE NEW STORY describe('StudentTeacherScorer', function() { var sessions = []; //… describe('A teacher speaking same language as a student', function() { beforeEach(function() { spanishStudent1.waitTime = 1000; spanishStudent2.waitTime = 2000; englishStudent1.waitTime = 3000; sessions.push(englishStudent1); sessions.push(spanishStudent2); sessions.push(spanishStudent1); }); it('will pick the student waiting longest', function() { const chosenStudent = scorer.findBestMatchFor(sessions, spanishTeacher); expect(chosenStudent).toEqual(spanishStudent2); }); }); }); THE CONFLICT

Slide 187

Slide 187 text

TESTS SHOULD TELL A STORY THE NEW STORY describe('StudentTeacherScorer', function() { var sessions = []; //… describe('A teacher speaking same language as a student', function() { beforeEach(function() { spanishStudent1.waitTime = 1000; spanishStudent2.waitTime = 2000; englishStudent1.waitTime = 3000; sessions.push(englishStudent1); sessions.push(spanishStudent2); sessions.push(spanishStudent1); }); it('will pick the student waiting longest', function() { const chosenStudent = scorer.findBestMatchFor(sessions, spanishTeacher); expect(chosenStudent).toEqual(spanishStudent2); }); }); }); THE RESOLUTION

Slide 188

Slide 188 text

TESTS FAIL

Slide 189

Slide 189 text

TESTS FAIL Expected: spanishStudent2 Actual: spanishStudent1

Slide 190

Slide 190 text

No content

Slide 191

Slide 191 text

spanishStudent1.waitTime = 1000; spanishStudent2.waitTime = 2000;

Slide 192

Slide 192 text

1000 > 2000? spanishStudent1.waitTime = 1000; spanishStudent2.waitTime = 2000;

Slide 193

Slide 193 text

1000 > 2000? spanishStudent1.waitTime = 1000; spanishStudent2.waitTime = 2000;

Slide 194

Slide 194 text

EXISTING BEHAVIOR VERIFIED BY RUNNING THE OLD SPEC WITH NEW ASSERTIONS

Slide 195

Slide 195 text

A WILD BUG APPEARS MATH TO THE RESCUE!

Slide 196

Slide 196 text

TESTS SHOULD TELL A STORY I AM AWFUL AT SPREADSHEETS Score w/ Language Language Weight Normal Score X-axis: Time Y-axis: Score

Slide 197

Slide 197 text

TESTS SHOULD TELL A STORY I AM AWFUL AT SPREADSHEETS Lang score decreased over time till negated Score w/ Language Language Weight Normal Score X-axis: Time Y-axis: Score

Slide 198

Slide 198 text

TESTS SHOULD TELL A STORY I AM AWFUL AT SPREADSHEETS Score should always increase Lang score decreased over time till negated Score w/ Language Language Weight Normal Score X-axis: Time Y-axis: Score

Slide 199

Slide 199 text

WHAT DID WE DO ABOUT IT?

Slide 200

Slide 200 text

TESTS SHOULD TELL A STORY HACK THE PLANET it('will pick the student waiting longest', function() { const chosenStudent = scorer.findBestMatchFor(sessions, spanishTeacher); expect(chosenStudent).toEqual(spanishStudent2); });

Slide 201

Slide 201 text

TESTS SHOULD TELL A STORY HACK THE PLANET it('will pick the student waiting shortestlongest', function() { const chosenStudent = scorer.findBestMatchFor(sessions, spanishTeacher); expect(chosenStudent).toEqual(spanishStudent2); });

Slide 202

Slide 202 text

TESTS SHOULD TELL A STORY HACK THE PLANET it('will pick the student waiting shortestlongest', function() { const chosenStudent = scorer.findBestMatchFor(sessions, spanishTeacher); expect(chosenStudent).toEqual(spanishStudent2); }); // TODO: This behavior is probably unintended, but the algorithm has not been changed. // It appears as though students who have waited *less* than the ELL threshold are actually // processed in the opposite order than we intend. Once they get beyond the ELL threshold // then wait time correctly influences the student scoring.

Slide 203

Slide 203 text

TALK TO STAKEHOLDERS

Slide 204

Slide 204 text

THEY SAID TO PUNT ON IT FIX EVENTUALLY BUT BEEN LIVING WITH IT FOR YEARS

Slide 205

Slide 205 text

No content

Slide 206

Slide 206 text

WE ARE A PITTSBURGH COMPANY

Slide 207

Slide 207 text

SO ON THE PUNT RETURN…

Slide 208

Slide 208 text

No content

Slide 209

Slide 209 text

FOUND THE BUG!

Slide 210

Slide 210 text

PAST-SELF FUDGED SOME MATH I WORK FOR A MATH COMPANY i·ro·ny /ˈīrənē/ noun

Slide 211

Slide 211 text

FIXED THE CALCULATION REVERTED THE SPEC

Slide 212

Slide 212 text

TESTS PASS

Slide 213

Slide 213 text

HOWEVER… AN ACCEPTANCE TEST BROKE ELSEWHERE UNRELATED TO LANGUAGE

Slide 214

Slide 214 text

CURSE JAVASCRIPT

Slide 215

Slide 215 text

No content

Slide 216

Slide 216 text

No content

Slide 217

Slide 217 text

TESTS SHOULD TELL A STORY IN THIS ACCEPTANCE TEST

Slide 218

Slide 218 text

TESTS SHOULD TELL A STORY IN THIS ACCEPTANCE TEST ▸ Had an English Speaking teacher accepting fast pass

Slide 219

Slide 219 text

TESTS SHOULD TELL A STORY IN THIS ACCEPTANCE TEST ▸ Had an English Speaking teacher accepting fast pass ▸ Enqueued a English speaking student

Slide 220

Slide 220 text

TESTS SHOULD TELL A STORY IN THIS ACCEPTANCE TEST ▸ Had an English Speaking teacher accepting fast pass ▸ Enqueued a English speaking student ▸ Waited a while

Slide 221

Slide 221 text

TESTS SHOULD TELL A STORY IN THIS ACCEPTANCE TEST ▸ Had an English Speaking teacher accepting fast pass ▸ Enqueued a English speaking student ▸ Waited a while ▸ Enqueued another English speaking student w/ fast pass

Slide 222

Slide 222 text

TESTS SHOULD TELL A STORY IN THIS ACCEPTANCE TEST ▸ Had an English Speaking teacher accepting fast pass ▸ Enqueued a English speaking student ▸ Waited a while ▸ Enqueued another English speaking student w/ fast pass ▸ Fast pass ensures second student gets max score, no way to lose

Slide 223

Slide 223 text

WE LOST

Slide 224

Slide 224 text

TEST FIXTURE WASN’T BEING SET CORRECTLY

Slide 225

Slide 225 text

PATCHED, STILL FAILED MEANING THIS WAS A PRODUCTION BUG

Slide 226

Slide 226 text

PATCHED CODE DESERIALIZATION AND CONSTRUCTION DIFFERED

Slide 227

Slide 227 text

TESTS PASS

Slide 228

Slide 228 text

TESTS SHOULD TELL A STORY WHAT HAPPENED?

Slide 229

Slide 229 text

TESTS SHOULD TELL A STORY WHAT HAPPENED? ▸ Earlier bug hid this ▸ Forced shorter times to be favored on language match ▸ Was documented as working the other way

Slide 230

Slide 230 text

TESTS SHOULD TELL A STORY WHAT HAPPENED? ▸ Earlier bug hid this ▸ Forced shorter times to be favored on language match ▸ Was documented as working the other way ▸ Our acceptance test worked because of order dependency ▸ Only two students, last student was under test and won favorability ▸ Putting another student closer could have prevented failure

Slide 231

Slide 231 text

MORALS OF THE STORY

Slide 232

Slide 232 text

TESTS SHOULD TELL A STORY MORALS OF THE STORY

Slide 233

Slide 233 text

TESTS SHOULD TELL A STORY MORALS OF THE STORY ▸ Favor explicit assertions over potential coincidence

Slide 234

Slide 234 text

TESTS SHOULD TELL A STORY MORALS OF THE STORY ▸ Favor explicit assertions over potential coincidence ▸ Favor specs that tell the story you need to know

Slide 235

Slide 235 text

TESTS SHOULD TELL A STORY MORALS OF THE STORY ▸ Favor explicit assertions over potential coincidence ▸ Favor specs that tell the story you need to know ▸ Beware of coincidental truths

Slide 236

Slide 236 text

TESTS SHOULD TELL A STORY MORALS OF THE STORY ▸ Favor explicit assertions over potential coincidence ▸ Favor specs that tell the story you need to know ▸ Beware of coincidental truths ▸ Don’t be overly clever with fixtures/fake data

Slide 237

Slide 237 text

TESTS SHOULD TELL A STORY MORALS OF THE STORY ▸ Favor explicit assertions over potential coincidence ▸ Favor specs that tell the story you need to know ▸ Beware of coincidental truths ▸ Don’t be overly clever with fixtures/fake data ▸ Our careers are founded on math - learn more math

Slide 238

Slide 238 text

QUESTIONS?

Slide 239

Slide 239 text

I’M MATT MACHUGA Twitter, GitHub, Freenode: machuga

Slide 240

Slide 240 text

ERIE, PA

Slide 241

Slide 241 text

ERIE, PA

Slide 242

Slide 242 text

Hiring Software Developers! TTM IS HIRING! CONSULTING & TEACHING Small to medium projects