Slide 1

Slide 1 text

better code through boring(er) tests BETSY HAIBEL @betsythemuffin

Slide 2

Slide 2 text

everyone values testing @betsythemuffin

Slide 3

Slide 3 text

TYPE A QUOTE HERE. @betsythemuffin

Slide 4

Slide 4 text

literally no one loves testing all the time ‣not even me ‣it’s ok to admit it ‣the testing police are not coming for you @betsythemuffin

Slide 5

Slide 5 text

GROUNDWORK WHAT WE’RE GONNA DO ABOUT THAT TODAY ▸ learn why we hate our tests :( ▸ learn why we fight about tests :( ▸ learn about ways we accidentally make our tests worse :( ▸ learn how to fix those! ▸ learn how to make our app code better at the same time! @betsythemuffin

Slide 6

Slide 6 text

GROUNDWORK WHAT WE’RE GONNA DO ABOUT THAT TODAY UNDERLYING REASONS @betsythemuffin

Slide 7

Slide 7 text

GROUNDWORK WHAT WE’RE GONNA DO ABOUT THAT TODAY UNDERLYING REASONS SPECIFIC EXAMPLES @betsythemuffin

Slide 8

Slide 8 text

GROUNDWORK WHAT WE’RE GONNA DO ABOUT THAT TODAY UNDERLYING REASONS SPECIFIC EXAMPLES @betsythemuffin

Slide 9

Slide 9 text

GROUNDWORK WHAT WE’RE GONNA DO ABOUT THAT TODAY UNDERLYING REASONS SPECIFIC EXAMPLES @betsythemuffin

Slide 10

Slide 10 text

GROUNDWORK WHAT WE’RE GONNA DO ABOUT THAT TODAY UNDERLYING REASONS SPECIFIC EXAMPLES @betsythemuffin

Slide 11

Slide 11 text

tests are code and are therefore terrible UNDERLYING REASONS @betsythemuffin

Slide 12

Slide 12 text

you write your tests with the same imperfect knowledge as you write all your code UNDERLYING REASONS @betsythemuffin

Slide 13

Slide 13 text

WHY DON’T YOU TRY LISTENING TO YOUR TESTS? some jerk who likes TDD too much THIS IS THE THING THAT ALWAYS HAPPENS. CRIKEY. UNDERLYING REASONS @betsythemuffin

Slide 14

Slide 14 text

1. TESTS YOU HATE 2. ??? 3. CODE YOU DON’T HATE some jerk who likes TDD too much WE TRANSLATE, WITHOUT EXCESSIVE KINDNESS UNDERLYING REASONS @betsythemuffin

Slide 15

Slide 15 text

UGH I THINK WE CAN FIX THIS IF WE JUST TRY. I PROMISE. a TDDer who is not communicating well yet WE TRANSLATE, WITH SOMEWHAT MORE EMPATHY @betsythemuffin

Slide 16

Slide 16 text

some notes before we get started properly ‣bad code happens ‣bad tests happen ‣this is fine UNDERLYING REASONS @betsythemuffin

Slide 17

Slide 17 text

code is terrible because the world is complex UNDERLYING REASONS @betsythemuffin

Slide 18

Slide 18 text

we invented esoteric testing techniques for good reasons UNDERLYING REASONS @betsythemuffin

Slide 19

Slide 19 text

everything I am about to show you I have done in production especially the parts I say are bad UNDERLYING REASONS @betsythemuffin

Slide 20

Slide 20 text

TESTING PRIVATE METHODS @betsythemuffin

Slide 21

Slide 21 text

@book.send(:dogear) # a private method! TESTING PRIVATE METHODS - THE PROBLEM @betsythemuffin

Slide 22

Slide 22 text

WHY DO PEOPLE TEST PRIVATE METHODS? ▸ indirect results ▸ expensive to test via public interface ▸ weird side effects elsewhere TESTING PRIVATE METHODS - THE PROBLEM @betsythemuffin

Slide 23

Slide 23 text

ALTERNATIVES TO TESTING PRIVATE METHODS ▸ make the method public ▸ build more introspection logic ▸ extract a simple class TESTING PRIVATE METHODS - ALTERNATIVES @betsythemuffin

Slide 24

Slide 24 text

def dogear DogearMaker.new(self).call end class DogearMaker def initialize(target) @target = target end def call # stuff end end TESTING PRIVATE METHODS - EXTRACT SIMPLE CLASS @betsythemuffin

Slide 25

Slide 25 text

it’s ok to fix things later TESTING PRIVATE METHODS - DEFER THE DECISION @betsythemuffin

Slide 26

Slide 26 text

DUPLICATE TESTS @betsythemuffin

Slide 27

Slide 27 text

class ClientTest < Minitest::Test def test_mobile_phones @client.phone_number = MOBILE_PHONE assert @client.mobile_phone? end def test_landline_phones @client.phone_number = LANDLINE_PHONE assert [email protected]_phone? end end DUPLICATE TESTS - THE PROBLEM @betsythemuffin

Slide 28

Slide 28 text

def test_mobile_phones @client.phone_number = MOBILE_PHONE assert @client.mobile_phone? end it ‘can have a mobile phone‘ do subject.phone_number = MOBILE_PHONE expect(subject.mobile_phone?).to be(true) end DUPLICATE TESTS - THE PROBLEM @betsythemuffin

Slide 29

Slide 29 text

class ClientTest < Minitest::Test def test_mobile_phones @client.phone_number = MOBILE_PHONE assert @client.mobile_phone? end def test_landline_phones @client.phone_number = LANDLINE_PHONE assert [email protected]_phone? end end DUPLICATE TESTS - THE PROBLEM @betsythemuffin

Slide 30

Slide 30 text

class DonorrTest < Minitest::Test def test_mobile_phones @donorr.phone_number = MOBILE_PHONE assert @donorr.mobile_phone? end def test_landline_phones @donorr.phone_number = LANDLINE_PHONE assert [email protected]_phone? end end DUPLICATE TESTS - THE PROBLEM @betsythemuffin

Slide 31

Slide 31 text

module HasPhoneNumberTest def test_mobile_phones @subjec.phone_number = MOBILE_PHONE assert @subjec.mobile_phone? end def test_landline_phones @subjec.phone_number = LANDLINE_PHONE assert [email protected]_phone? end end DUPLICATE TESTS - THE SHARED EXAMPLE GROUP SMELL @betsythemuffin

Slide 32

Slide 32 text

class ClientTest < Minitest::Test include HasPhoneNumberTest end class CustomerTest < Minitest::Test include HasPhoneNumberTest end DUPLICATE TESTS - THE SHARED EXAMPLE GROUP SMELL @betsythemuffin

Slide 33

Slide 33 text

class Client include HasPhoneNumber end class Customer include HasPhoneNumber end DUPLICATE TESTS - THE SHARED EXAMPLE GROUP SMELL @betsythemuffin

Slide 34

Slide 34 text

this locks us into assumptions DUPLICATE TESTS - THE SHARED EXAMPLE GROUP SMELL @betsythemuffin

Slide 35

Slide 35 text

class HasPhoneNumberTest < Minitest::Test class HasPhone include HasPhoneNumber end def test_landline_phones @has_phone = HasPhone.new @has_phone.phone_number = LANDLINE_PHONE assert !@has_phone.mobile_phone? end end DUPLICATE TESTS - THE DIRECT MODULE TEST SMELL @betsythemuffin

Slide 36

Slide 36 text

class PhoneNumber attr_accessor :value def mobile_phone? # complicated lookup logic end end DUPLICATE TESTS - THE EXTRACT CLASS FIX @betsythemuffin

Slide 37

Slide 37 text

test duplication sometimes means functional duplication DUPLICATE TESTS - THE KEY IDEA @betsythemuffin

Slide 38

Slide 38 text

INVENTING THE UNIVERSE @betsythemuffin

Slide 39

Slide 39 text

class PieTest < Minitest::Test def test_pie_tastes_good @universe.invent @apple_seed = AppleSeed.new @orchard = Orchard.new @orchard.plant(@apple_seed) @orchard.water! let_much_time_pass @apple_tree = Orchard.find_tree_from(@apple_seed) # etc assert @pie.filling_set_properly? assert @pie.cinnamon_level > 6 assert @pie.super_flaky_crust? end end INVENTING THE UNIVERSE - THE PROBLEM @betsythemuffin

Slide 40

Slide 40 text

class PieTest < Minitest::Test def test_pie_tastes_good # many other lines assert @pie.filling_set_properly? assert @pie.cinnamon_level > 6 assert @pie.super_flaky_crust? end end INVENTING THE UNIVERSE - THE PROBLEM @betsythemuffin

Slide 41

Slide 41 text

first, pretend this isn’t terrible. INVENTING THE UNIVERSE - THE PREMATURE FIX SMELL @betsythemuffin

Slide 42

Slide 42 text

class PieTest < Minitest::Test def test_pie_tastes_good @universe.invent @apple_seed = AppleSeed.new @orchard = Orchard.new @orchard.plant(@apple_seed) @orchard.water! let_much_time_pass @apple_tree = Orchard.find_tree_from(@apple_seed) # etc assert @pie.filling_set_properly? assert @pie.cinnamon_level > 6 assert @pie.super_flaky_crust? end end INVENTING THE UNIVERSE - THE PREMATURE FIX SMELL @betsythemuffin

Slide 43

Slide 43 text

your tests use your application’s assumptions (unless you try very hard) INVENTING THE UNIVERSE - THE PREMATURE FIX SMELL @betsythemuffin

Slide 44

Slide 44 text

wait until you have multiple tests INVENTING THE UNIVERSE - THE PREMATURE FIX SMELL @betsythemuffin

Slide 45

Slide 45 text

shared contexts INVENTING THE UNIVERSE - SHARED CONTEXT SMELL @betsythemuffin

Slide 46

Slide 46 text

class PieTest < Minitest::Test include ReliesOnUniverseExisting def setup invent_universe end end INVENTING THE UNIVERSE - SHARED CONTEXT SMELL @betsythemuffin

Slide 47

Slide 47 text

WHAT ARE SHARED CONTEXTS GOOD FOR? ▸ database setup ▸ inventing the universe INVENTING THE UNIVERSE - SHARED CONTEXT SMELL @betsythemuffin

Slide 48

Slide 48 text

WHAT DO SHARED CONTEXTS HIDE FROM US? ▸ dependencies ▸ (but that’s not the whole story) INVENTING THE UNIVERSE - SHARED CONTEXT SMELL @betsythemuffin

Slide 49

Slide 49 text

factory_girl INVENTING THE UNIVERSE - FACTORY_GIRL SMELL @betsythemuffin

Slide 50

Slide 50 text

FactoryGirl.create(:pie) FactoryGirl.create(:filling) # omg they look the same! INVENTING THE UNIVERSE - FACTORY_GIRL SMELL @betsythemuffin

Slide 51

Slide 51 text

saying “dependencies” is cheating INVENTING THE UNIVERSE - FACTORY_GIRL SMELL @betsythemuffin

Slide 52

Slide 52 text

saying “dependencies” is cheating INVENTING THE UNIVERSE - FACTORY_GIRL SMELL @betsythemuffin

Slide 53

Slide 53 text

“dependencies” just means “code you’re not looking at” INVENTING THE UNIVERSE - FACTORY_GIRL SMELL @betsythemuffin

Slide 54

Slide 54 text

just don’t hide domain logic INVENTING THE UNIVERSE - FACTORY_GIRL SMELL @betsythemuffin

Slide 55

Slide 55 text

# constructor: Pie.new(filling: PumpkinPieFilling.new) INVENTING THE UNIVERSE - THE PLAIN FACTORY FIX @betsythemuffin

Slide 56

Slide 56 text

# constructor: Pie.new(filling: PumpkinPieFilling.new) # factory method on class: Pie.bake_with(PumpkinPieFilling.new) INVENTING THE UNIVERSE - THE PLAIN FACTORY FIX @betsythemuffin

Slide 57

Slide 57 text

# constructor: Pie.new(filling: PumpkinPieFilling.new) # factory method on class: Pie.bake_with(PumpkinPieFilling.new) # java-style factory class factory = PieFactory.new factory.add_filling(PumpkinPieFilling.new) @pie = factory.bake INVENTING THE UNIVERSE - THE PLAIN FACTORY FIX @betsythemuffin

Slide 58

Slide 58 text

Business logic is too important to delegate to framework magic INVENTING THE UNIVERSE - THE PLAIN FACTORY FIX @betsythemuffin

Slide 59

Slide 59 text

class PieTest < Minitest::Test def test_pie_tastes_good @universe.invent @apple_seed = AppleSeed.new @orchard = Orchard.new @orchard.plant(@apple_seed) @orchard.water! let_much_time_pass @apple_tree = Orchard.find_tree_from(@apple_seed) # etc assert @pie.filling_set_properly? assert @pie.cinnamon_level > 6 assert @pie.super_flaky_crust? end end INVENTING THE UNIVERSE - THE PLAIN FACTORY FIX @betsythemuffin

Slide 60

Slide 60 text

@universe.invent @apple_seed = AppleSeed.new @orchard = Orchard.new @orchard.plant(@apple_seed) @orchard.water! let_much_time_pass @apple_tree = Orchard.find_tree_from(@apple_seed) # etc assert @pie.filling_set_properly? assert @pie.cinnamon_level > 6 assert @pie.super_flaky_crust? INVENTING THE UNIVERSE - THE PLAIN FACTORY FIX @orchard = Universe.with_orchard(Apple) @pie = Pie.from(@orchard.fruit) assert @pie.filling_set_properly? assert @pie.cinnamon_level > 6 assert @pie.super_flaky_crust?

Slide 61

Slide 61 text

INVENTING THE UNIVERSE - THE PLAIN FACTORY FIX class PieTest < Minitest::Test def test_pie_tastes_good @orchard = Universe.with_orchard(Apple) @pie = Pie.from(@orchard.fruit) assert @pie.filling_set_properly? assert @pie.cinnamon_level > 6 assert @pie.super_flaky_crust? end end @betsythemuffin

Slide 62

Slide 62 text

A MAGIC TRICK @betsythemuffin

Slide 63

Slide 63 text

A MAGIC TRICK @betsythemuffin

Slide 64

Slide 64 text

this is subjective A MAGIC TRICK @betsythemuffin

Slide 65

Slide 65 text

how do we define boring? A MAGIC TRICK @betsythemuffin

Slide 66

Slide 66 text

i think: code that minimizes assumptions is boring A MAGIC TRICK @betsythemuffin

Slide 67

Slide 67 text

application code influences test code test code influences application code A MAGIC TRICK @betsythemuffin

Slide 68

Slide 68 text

1. TESTS YOU DON’T HATE 2. REFACTORING 3. CODE YOU DON’T HATE all “listening to your tests” means A CONCLUSION @betsythemuffin

Slide 69

Slide 69 text

SHAMELESS PROMOTION TIME BETSY HAIBEL ▸ @betsythemuffin ▸ betsyhaibel.com ▸ github.com/bhaibel ▸ roostify.com/jobs @betsythemuffin

Slide 70

Slide 70 text

QUESTIONS? you, maybe TEXT @betsythemuffin