Slide 1

Slide 1 text

Testing Just
 What You Need @undees Open Source Bridge June 22, 2017

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Public domain: https://commons.wikimedia.org/wiki/File:Vancouver_Tornado_-_RTX_loop.gif

Slide 4

Slide 4 text

Testing Just
 What You Need

Slide 5

Slide 5 text

The problem

Slide 6

Slide 6 text

Tests are slow

Slide 7

Slide 7 text

Test output is huge

Slide 8

Slide 8 text

It’s tough to find errors

Slide 9

Slide 9 text

The real problem

Slide 10

Slide 10 text

Feedback cycle is too slow

Slide 11

Slide 11 text

The solution:

Slide 12

Slide 12 text

Run just what you need

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Let’s look at MiniTest first

Slide 15

Slide 15 text

Use the files, Luke!

Slide 16

Slide 16 text

test └── unit ├── coffee_test.rb └── test_helper.rb
 
 
 
 


Slide 17

Slide 17 text

test └── unit ├── coffee_test.rb └── test_helper.rb
 
 $ ruby -I.:./lib:./test test/unit/coffee_test.rb

Slide 18

Slide 18 text

test └── unit ├── coffee_test.rb └── test_helper.rb
 $ export RUBYOPT=-I.:./lib:./test 
 $ ruby -I.:./lib:./test test/unit/coffee_test.rb

Slide 19

Slide 19 text

test └── unit ├── coffee_test.rb └── test_helper.rb
 
 $ export RUBYOPT=-I.:./lib:./test 
 $ ruby test/unit/coffee_test.rb

Slide 20

Slide 20 text

test └── unit ├── coffee_test.rb └── test_helper.rb
 
 
 $ ruby test/unit/coffee_test.rb

Slide 21

Slide 21 text

test └── unit ├── coffee_test.rb └── test_helper.rb
 
 $ ruby test/unit/*_test.rb

Slide 22

Slide 22 text

test └── unit ├── coffee_test.rb └── test_helper.rb
 
 $ ruby test/**/*_test.rb


Slide 23

Slide 23 text

• Organize tests by directory • Wildcards (globs) are your friend

Slide 24

Slide 24 text

Run tests by name

Slide 25

Slide 25 text

class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end # ... end

Slide 26

Slide 26 text

class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end # Using macros from `shoulda` context 'coffee' do should 'cost $1' {} end end

Slide 27

Slide 27 text

Exact match

Slide 28

Slide 28 text

class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end context 'coffee' do should 'cost $1' {} end end
 
 $ ruby test/unit/coffee_test.rb -n test_coffee_should_be_awesome

Slide 29

Slide 29 text

class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end context 'coffee' do should 'cost $1' {} end end
 
 $ ruby test/unit/coffee_test.rb -n test_coffee_should_be_awesome

Slide 30

Slide 30 text

What about
 generated test names?

Slide 31

Slide 31 text

Shoulda

Slide 32

Slide 32 text

class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end context 'coffee' do should 'cost $1' {} end end
 $ ruby test/unit/coffee_test.rb -n 'coffee should cost $1'

Slide 33

Slide 33 text

class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end context 'coffee' do should 'cost $1' {} end end
 $ ruby test/unit/coffee_test.rb -n 'coffee should cost $1'

Slide 34

Slide 34 text

class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end context 'coffee' do should 'cost $1' {} end end
 $ ruby test/unit/coffee_test.rb -n 'coffee should cost $1' HAHA NOPE

Slide 35

Slide 35 text


 class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end context 'coffee' do should 'cost $1' {} end end
 $ ruby test/unit/coffee_test.rb -n 'test_: coffee should cost $1. '
 ^

Slide 36

Slide 36 text

MiniTest::Spec

Slide 37

Slide 37 text


 describe Coffee do it 'costs $1' {} end
 $ ruby test/unit/coffee_spec.rb -n 'test_0001_costs $1'

Slide 38

Slide 38 text

Regular expression

Slide 39

Slide 39 text

class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end context 'coffee' do should 'cost $1' {} context 'with milk' do should 'cost $1.25' {} end end end
 $ ruby test/unit/coffee_test.rb -n '/coffee/'

Slide 40

Slide 40 text

class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end context 'coffee' do should 'cost $1' {} context 'with milk' do should 'cost $1.25' {} end end end
 $ ruby test/unit/coffee_test.rb -n '/coffee/'

Slide 41

Slide 41 text

class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end context 'coffee' do should 'cost $1' {} context 'with milk' do should 'cost $1.25' {} end end end
 $ ruby test/unit/coffee_test.rb -n '/milk/'

Slide 42

Slide 42 text

• Pass the -n option to run all matching tests • Use a regular expression inside single quotes:
 '/test case to run/' • Be mindful of names generated by Shoulda,
 MiniTest::Spec, etc.

Slide 43

Slide 43 text

Integrate with your editor

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

Whoa

Slide 46

Slide 46 text

You can go so far
 with what we’ve covered so far

Slide 47

Slide 47 text

But now…

Slide 48

Slide 48 text

RSpec

Slide 49

Slide 49 text

You can do all the stuff we just did!

Slide 50

Slide 50 text

spec ├── spec_helper.rb └── unit └── coffee_spec.rb
 


Slide 51

Slide 51 text

spec ├── spec_helper.rb └── unit └── coffee_spec.rb
 
 $ rspec spec/unit/coffee_spec.rb

Slide 52

Slide 52 text

spec ├── spec_helper.rb └── unit └── coffee_spec.rb
 
 $ rspec spec/unit/*_spec.rb

Slide 53

Slide 53 text

spec ├── spec_helper.rb └── unit └── coffee_spec.rb
 
 $ rspec spec/**/*_spec.rb

Slide 54

Slide 54 text

spec ├── spec_helper.rb └── unit └── coffee_spec.rb
 $ rspec

Slide 55

Slide 55 text

• You can still separate specs by directory • RSpec can run a directory for you • Wildcards are still your friend • Run specs from your text editor

Slide 56

Slide 56 text

RSpec.describe 'Coffee' do it 'costs $1' context 'with milk' do it 'costs $1.25' it 'probably tastes a little milder' end
 end 
 $ rspec -e 'Coffee costs $1'

Slide 57

Slide 57 text

RSpec.describe 'Coffee' do it 'costs $1' context 'with milk' do it 'costs $1.25' it 'probably tastes a little milder' end
 end 
 $ rspec -e 'Coffee costs $1'

Slide 58

Slide 58 text

RSpec.describe 'Coffee' do it 'costs $1' context 'with milk' do it 'costs $1.25' it 'probably tastes a little milder' end
 end 
 $ rspec -e 'milk'

Slide 59

Slide 59 text

• Pass the -e option to run all matching specs • No need for regex-style slashes; just use:
 'test case to run'

Slide 60

Slide 60 text

Failing line number is right there in the output!

Slide 61

Slide 61 text

$ rspec
 
 A cup of coffee costs $1 with milk probably tastes a little milder costs $1.25 (FAILED - 1)
 3 examples, 1 failure Failed examples: rspec ./spec/unit/coffee_spec.rb:13
 # A cup of coffee with milk costs $1.25

Slide 62

Slide 62 text

$ rspec
 
 A cup of coffee costs $1 with milk probably tastes a little milder costs $1.25 (FAILED - 1)
 3 examples, 1 failure Failed examples: rspec ./spec/unit/coffee_spec.rb:13
 # A cup of coffee with milk costs $1.25

Slide 63

Slide 63 text

Paste and run

Slide 64

Slide 64 text

$ rspec ./spec/unit/coffee_spec.rb:13
 
 A cup of coffee with milk costs $1.25 (FAILED - 1) 
 
 1 example, 1 failure
 
 Failed examples: rspec ./spec/unit/coffee_spec.rb:15
 # A cup of coffee with milk costs $1.25

Slide 65

Slide 65 text

$ rspec ./spec/unit/coffee_spec.rb:13
 
 A cup of coffee with milk costs $1.25 (FAILED - 1) 
 
 1 example, 1 failure
 
 Failed examples: rspec ./spec/unit/coffee_spec.rb:15
 # A cup of coffee with milk costs $1.25

Slide 66

Slide 66 text

$ rspec ./spec/unit/coffee_spec.rb:13
 
 A cup of coffee with milk costs $1.25 (FAILED - 1) 
 
 1 example, 1 failure
 
 Failed examples: rspec ./spec/unit/coffee_spec.rb:15
 # A cup of coffee with milk costs $1.25

Slide 67

Slide 67 text

• Run the spec on a specific line number:
 some_spec.rb:42 • Paste and run from test failure output

Slide 68

Slide 68 text

RSpec offers even more shortcuts
 for the studiously lazy

Slide 69

Slide 69 text

Focus on specific specs

Slide 70

Slide 70 text

RSpec.describe 'Coffee' do it 'costs $1' context 'with milk' do it 'costs $1.25' it 'probably tastes a little milder' end
 end

Slide 71

Slide 71 text

$ rspec A cup of coffee costs $1 with milk probably tastes a little milder costs $1.25 3 examples, 0 failures

Slide 72

Slide 72 text

RSpec.describe 'Coffee' do it 'costs $1' context 'with milk' do it 'costs $1.25' it 'probably tastes a little milder' end
 end

Slide 73

Slide 73 text

RSpec.describe 'Coffee' do it 'costs $1' context 'with milk' do it 'costs $1.25' fit 'probably tastes a little milder' end
 end

Slide 74

Slide 74 text

$ rspec . 1 example, 0 failures

Slide 75

Slide 75 text

RSpec.describe 'Coffee' do it 'costs $1' context 'with milk' do it 'costs $1.25' it 'probably tastes a little milder' end
 end

Slide 76

Slide 76 text

RSpec.describe 'Coffee' do it 'costs $1' fcontext 'with milk' do it 'costs $1.25' it 'probably tastes a little milder' end
 end

Slide 77

Slide 77 text

RSpec.fdescribe 'Coffee' do it 'costs $1' context 'with milk' do it 'costs $1.25' it 'probably tastes a little milder' end
 end

Slide 78

Slide 78 text

A little configuration
 (automatic if you use rspec --init)

Slide 79

Slide 79 text

# spec_helper.rb RSpec.configure do |config| config.filter_run_when_matching :focus end

Slide 80

Slide 80 text

RSpec.describe 'Coffee' do it 'costs $1' context 'with milk' do it 'costs $1.25' fit 'probably tastes a little milder' end
 end

Slide 81

Slide 81 text

RSpec.describe 'Coffee' do it 'costs $1' context 'with milk' do it 'costs $1.25' it 'probably tastes a little milder', focus: true end
 end

Slide 82

Slide 82 text

Tag your examples!

Slide 83

Slide 83 text

RSpec.describe 'Coffee' do it 'costs $1' context 'with milk' do it 'costs $1.25' it 'probably tastes a little milder' end
 end

Slide 84

Slide 84 text

RSpec.describe 'Coffee' do it 'costs $1', :payment context 'with milk' do it 'costs $1.25', :payment it 'probably tastes a little milder' end
 end

Slide 85

Slide 85 text

RSpec.describe 'Coffee' do it 'costs $1', :payment context 'with milk' do it 'costs $1.25', :payment it 'probably tastes a little milder' end
 end

Slide 86

Slide 86 text

# Run just the payment specs $ rspec -t payment
 
 # Run just the non-payment specs $ rspec -t ~payment

Slide 87

Slide 87 text

# Run just the specs related to one bug $ rspec -t bug_id:123


Slide 88

Slide 88 text

Run just what failed

Slide 89

Slide 89 text

$ rspec 
 
 
 A cup of coffee costs $1 (FAILED - 1) with milk costs $1.25 (FAILED - 2) probably tastes a little milder
 
 3 examples, 2 failures

Slide 90

Slide 90 text

$ rspec 
 
 
 A cup of coffee costs $1 (FAILED - 1) with milk costs $1.25 (FAILED - 2) probably tastes a little milder
 
 3 examples, 2 failures

Slide 91

Slide 91 text

$ rspec 
 
 
 A cup of coffee costs $1 (FAILED - 1) with milk costs $1.25 (FAILED - 2) probably tastes a little milder
 
 3 examples, 2 failures

Slide 92

Slide 92 text

$ rspec 
 
 
 A cup of coffee costs $1 (FAILED - 1) with milk costs $1.25 (FAILED - 2) probably tastes a little milder
 
 3 examples, 2 failures

Slide 93

Slide 93 text

$ rspec --only-failures
 Run options: include {:last_run_status=>"failed"} A cup of coffee costs $1 (FAILED - 1) with milk costs $1.25 (FAILED - 2)
 
 2 examples, 2 failures

Slide 94

Slide 94 text

$ rspec --only-failures
 Run options: include {:last_run_status=>"failed"} A cup of coffee costs $1 (FAILED - 1) with milk costs $1.25 (FAILED - 2)
 
 2 examples, 2 failures

Slide 95

Slide 95 text

$ rspec --only-failures
 Run options: include {:last_run_status=>"failed"} A cup of coffee costs $1 (FAILED - 1) with milk costs $1.25 (FAILED - 2)
 
 2 examples, 2 failures

Slide 96

Slide 96 text

$ rspec --only-failures
 Run options: include {:last_run_status=>"failed"} A cup of coffee costs $1 (FAILED - 1) with milk costs $1.25 (FAILED - 2)
 
 2 examples, 2 failures

Slide 97

Slide 97 text

…or just one failure at a time

Slide 98

Slide 98 text

$ rspec --next-failure
 Run options: include {:last_run_status=>"failed"} A cup of coffee costs $1 (FAILED - 1) 1 example, 1 failure

Slide 99

Slide 99 text

$ rspec --next-failure
 Run options: include {:last_run_status=>"failed"} A cup of coffee costs $1 (FAILED - 1) 1 example, 1 failure

Slide 100

Slide 100 text

How to set this up:

Slide 101

Slide 101 text

# spec_helper.rb RSpec.configure do |config| config.example_status_persistence_file_path =
 'spec/examples.txt' end

Slide 102

Slide 102 text

No content

Slide 103

Slide 103 text

Reproduce errors, minimally

Slide 104

Slide 104 text

$ rspec .........* 10 examples, 0 failures, 1 pending

Slide 105

Slide 105 text

RSpec.describe Ledger, :db do
 describe '#record' do context 'when the expense lacks a payee' do it 'rejects the expense as invalid' end end end

Slide 106

Slide 106 text

$ rspec FF........* 11 examples, 2 failures, 1 pending

Slide 107

Slide 107 text

Two failures?!?

Slide 108

Slide 108 text

$ rspec --bisect
 Bisect started using options: "" Running suite to find failures... (0.56287 seconds) Starting bisect with 2 failing examples
 and 9 non-failing examples. Checking that failure(s) are order-dependent...
 failure appears to be order-dependent 
 # ...

Slide 109

Slide 109 text

$ rspec --bisect
 Bisect started using options: "" Running suite to find failures... (0.56287 seconds) Starting bisect with 2 failing examples
 and 9 non-failing examples. Checking that failure(s) are order-dependent...
 failure appears to be order-dependent 
 # ...

Slide 110

Slide 110 text

$ rspec --bisect
 Bisect started using options: "" Running suite to find failures... (0.56287 seconds) Starting bisect with 2 failing examples
 and 9 non-failing examples. Checking that failure(s) are order-dependent...
 failure appears to be order-dependent 
 # ...

Slide 111

Slide 111 text

$ rspec --bisect
 Bisect started using options: "" Running suite to find failures... (0.56287 seconds) Starting bisect with 2 failing examples
 and 9 non-failing examples. Checking that failure(s) are order-dependent...
 failure appears to be order-dependent 
 # ...

Slide 112

Slide 112 text

$ rspec --bisect
 Bisect started using options: "" Running suite to find failures... (0.56287 seconds) Starting bisect with 2 failing examples
 and 9 non-failing examples. Checking that failure(s) are order-dependent...
 failure appears to be order-dependent 
 # ...

Slide 113

Slide 113 text

$ rspec --bisect 
 # ... Round 1: bisecting over non-failing examples 1-9 . ignoring examples 1-5 (0.45315 seconds) Round 2: bisecting over non-failing examples 6-9 .. ignoring examples 8-9 (0.94028 seconds) Round 3: bisecting over non-failing examples 6-7 . ignoring example 6 (0.5078 seconds) Bisect complete! Reduced necessary non-failing examples from 9 to 1 in 2.31 seconds.

Slide 114

Slide 114 text

$ rspec --bisect 
 # ... Round 1: bisecting over non-failing examples 1-9 . ignoring examples 1-5 (0.45315 seconds) Round 2: bisecting over non-failing examples 6-9 .. ignoring examples 8-9 (0.94028 seconds) Round 3: bisecting over non-failing examples 6-7 . ignoring example 6 (0.5078 seconds) Bisect complete! Reduced necessary non-failing examples from 9 to 1 in 2.31 seconds.

Slide 115

Slide 115 text

$ rspec --bisect 
 # ... Round 1: bisecting over non-failing examples 1-9 . ignoring examples 1-5 (0.45315 seconds) Round 2: bisecting over non-failing examples 6-9 .. ignoring examples 8-9 (0.94028 seconds) Round 3: bisecting over non-failing examples 6-7 . ignoring example 6 (0.5078 seconds) Bisect complete! Reduced necessary non-failing examples from 9 to 1 in 2.31 seconds.

Slide 116

Slide 116 text

$ rspec --bisect 
 # ... Round 1: bisecting over non-failing examples 1-9 . ignoring examples 1-5 (0.45315 seconds) Round 2: bisecting over non-failing examples 6-9 .. ignoring examples 8-9 (0.94028 seconds) Round 3: bisecting over non-failing examples 6-7 . ignoring example 6 (0.5078 seconds) Bisect complete! Reduced necessary non-failing examples from 9 to 1 in 2.31 seconds.

Slide 117

Slide 117 text

$ rspec --bisect 
 # ... The minimal reproduction command is: rspec ./spec/integration/app/ ledger_spec.rb[1:1:1:1,1:1:2:1] ./spec/unit/app/ api_spec.rb[1:2:2:2]

Slide 118

Slide 118 text

Recap:

Slide 119

Slide 119 text

• Copy/paste to run the spec at a given line • Focus on specific specs:
 fdescribe / fit • Run specs with specific tags:
 -t tag:value • Run just what failed:
 --only-failures, --next-failure • Bisect to track down errors

Slide 120

Slide 120 text

Back to MiniTest

Slide 121

Slide 121 text

You can get some of these benefits with add-ons

Slide 122

Slide 122 text

Run tests by line (with minitest-line gem)

Slide 123

Slide 123 text

1: class CoffeeTest < Minitest::Test 2: def test_coffee_should_be_awesome; end 3: 4: context 'coffee' do 5: should 'cost $1' {} 6: 7: context 'with milk' do 8: should 'cost $1.25' {} 9: end 10: end 11: end
 
 


Slide 124

Slide 124 text

1: class CoffeeTest < Minitest::Test 2: def test_coffee_should_be_awesome; end 3: 4: context 'coffee' do 5: should 'cost $1' {} 6: 7: context 'with milk' do 8: should 'cost $1.25' {} 9: end 10: end 11: end
 
 


Slide 125

Slide 125 text

1: class CoffeeTest < Minitest::Test 2: def test_coffee_should_be_awesome; end 3: 4: context 'coffee' do 5: should 'cost $1' {} 6: 7: context 'with milk' do 8: should 'cost $1.25' {} 9: end 10: end 11: end
 
 
 $ ruby test/unit/coffee_test.rb -l 8

Slide 126

Slide 126 text

Helps especially with failing tests

Slide 127

Slide 127 text

$ ruby test/unit/coffee_test.rb 
 3 tests, 3 assertions, 1 failures, 0 errors, 0 skips Focus on failing tests: ruby test/unit/coffee_test.rb -l 20

Slide 128

Slide 128 text

$ ruby test/unit/coffee_test.rb 
 3 tests, 3 assertions, 1 failures, 0 errors, 0 skips Focus on failing tests: ruby test/unit/coffee_test.rb -l 20

Slide 129

Slide 129 text

Focus on specific tests (with minitest-focus gem)

Slide 130

Slide 130 text

require 'minitest/focus'
 
 class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end context 'coffee' do
 focus should 'cost $1' {} end
 end
 $ ruby test/unit/coffee_test.rb

Slide 131

Slide 131 text

require 'minitest/focus'
 
 class CoffeeTest < Minitest::Test def test_coffee_should_be_awesome; end context 'coffee' do
 focus should 'cost $1' {} end
 end
 $ ruby test/unit/coffee_test.rb

Slide 132

Slide 132 text

Reproduce errors minimally (with minitest-bisect gem)

Slide 133

Slide 133 text

$ minitest_bisect test/**/*_test.rb

Slide 134

Slide 134 text

• You can do a lot with MiniTest plugins • You get a lot for free with RSpec

Slide 135

Slide 135 text

No content

Slide 136

Slide 136 text

No content

Slide 137

Slide 137 text

http://pragprog.com/titles/rspec3
 
 Coupon code: Open_Source_Bridge_2017

Slide 138

Slide 138 text

Closing thoughts

Slide 139

Slide 139 text

Don’t worry about getting the perfect automation setup

Slide 140

Slide 140 text

Even a ball of duct tape can save you time

Slide 141

Slide 141 text

Make the time to try this stuff out

Slide 142

Slide 142 text

Automate what you can

Slide 143

Slide 143 text

Faster feedback = qualitative improvement

Slide 144

Slide 144 text

No content

Slide 145

Slide 145 text

This document and the information herein (including any information that may be incorporated by reference) is provided for informational purposes only and should not be construed as an offer, commitment, promise or obligation on behalf of New Relic, Inc. (“New Relic”) to sell securities or deliver any product, material, code, functionality, or other feature. Any information provided hereby is proprietary to New Relic and may not be replicated or disclosed without New Relic’s express written permission. Such information may contain forward-looking statements within the meaning of federal securities laws. Any statement that is not a historical fact or refers to expectations, projections, future plans, objectives, estimates, goals, or other characterizations of future events is a forward-looking statement. These forward-looking statements can often be identified as such because the context of the statement will include words such as “believes,” “anticipates,” “expects” or words of similar import. Actual results may differ materially from those expressed in these forward-looking statements, which speak only as of the date hereof, and are subject to change at any time without notice. Existing and prospective investors, customers and other third parties transacting business with New Relic are cautioned not to place undue reliance on this forward-looking information. The achievement or success of the matters covered by such forward-looking statements are based on New Relic’s current assumptions, expectations, and beliefs and are subject to substantial risks, uncertainties, assumptions, and changes in circumstances that may cause the actual results, performance, or achievements to differ materially from those expressed or implied in any forward-looking statement. Further information on factors that could affect such forward-looking statements is included in the filings we make with the SEC from time to time. Copies of these documents may be obtained by visiting New Relic’s Investor Relations website at ir.newrelic.com or the SEC’s website at www.sec.gov. New Relic assumes no obligation and does not intend to update these forward-looking statements, except as required by law. New Relic makes no warranties, expressed or implied, in this document or otherwise, with respect to the information provided.