Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Little Leaks Sink Your Tests

Little Leaks Sink Your Tests

"The tests pass on my machine." "Wait, it was working a minute ago." "Oh, that test is flaky sometimes." Unpredictable tests are toxic for our productivity. They undermine confidence in our code. They encourage us to wallpaper over the immediate problem, rather than fixing the underlying cause.

In this presentation, we'll talk about a chief cause of flaky tests: leaky global state.

Erin Dees

June 22, 2016
Tweet

More Decks by Erin Dees

Other Decks in Programming

Transcript

  1. RSpec.describe 'Dear Diary' do it 'starts empty' do expect(Dir['diary/*.txt']).to be_empty

    end it 'lets me add deep thoughts' do FileUtils.touch('diary/deep_thoughts.txt') expect(Dir['diary/*.txt']).not_to be_empty end end
  2. RSpec.describe 'Dear Diary' do it 'starts empty' do expect(Dir['diary/*.txt']).to be_empty

    end it 'lets me add deep thoughts' do FileUtils.touch('diary/deep_thoughts.txt') expect(Dir['diary/*.txt']).not_to be_empty end end
  3. RSpec.describe 'Dear Diary' do it 'starts empty' do expect(Dir['diary/*.txt']).to be_empty

    end it 'lets me add deep thoughts' do FileUtils.touch('diary/deep_thoughts.txt') expect(Dir['diary/*.txt']).not_to be_empty end end
  4. $ rspec before_spec.rb .. Finished in 0.00942 seconds (files took

    1.03 seconds to load) 2 examples, 0 failures
  5. $ rspec before_spec.rb .F Failures: 1) Dear Diary starts empty

    Failure/Error:
 expect(Dir['diary/*.txt']).to be_empty
  6. $ rspec before_spec.rb .F Failures: 1) Dear Diary starts empty

    Failure/Error:
 expect(Dir['diary/*.txt']).to be_empty
  7. RSpec.describe 'Cloud tracker' do context 'on a clear day' do

    it 'reports that there are no clouds' do expect(DB[:clouds].count).to eq(0) end end # ... end
  8. RSpec.describe 'Cloud tracker' do context 'on a clear day' do

    it 'reports that there are no clouds' do expect(DB[:clouds].count).to eq(0) end end # ... end
  9. RSpec.describe 'Cloud tracker' do # ... context 'with a cirrus

    cloud' do before do DB[:clouds].insert(type: 'cirrus',
 spotted_at: Time.now) end it 'reports the cloud' do expect(DB[:clouds].map(:type)).to eq(['cirrus']) end end end
  10. RSpec.describe 'Cloud tracker' do # ... context 'with a cirrus

    cloud' do before do DB[:clouds].insert(type: 'cirrus',
 spotted_at: Time.now) end it 'reports the cloud' do expect(DB[:clouds].map(:type)).to eq(['cirrus']) end end end
  11. RSpec.describe 'Cloud tracker' do # ... context 'with a cirrus

    cloud' do before do DB[:clouds].insert(type: 'cirrus',
 spotted_at: Time.now) end it 'reports the cloud' do expect(DB[:clouds].map(:type)).to eq(['cirrus']) end end end
  12. RSpec.describe 'Cloud tracker' do context 'on a clear day' do

    # ... end context 'with a cirrus cloud' do # ... end end
  13. $ rspec before_spec.rb .. Finished in 0.01159 seconds (files took

    0.65775 seconds to load) 2 examples, 0 failures
  14. $ rspec before_spec.rb .F Failures: 1) Cloud tracker on a

    clear day reports that there are no clouds Failure/Error:
 expect(DB[:clouds].count).to eq(0) expected: 0 got: 1
  15. $ rspec before_spec.rb .F Failures: 1) Cloud tracker on a

    clear day reports that there are no clouds Failure/Error:
 expect(DB[:clouds].count).to eq(0) expected: 0 got: 1
  16. RSpec.configure do |config| config.before(:suite) do # ... DatabaseCleaner.clean_with :truncation DatabaseCleaner.strategy

    = :transaction end config.around(:example) do |example| DatabaseCleaner.cleaning { example.run } end end
  17. RSpec.configure do |config| config.before(:suite) do # ... DatabaseCleaner.clean_with :truncation DatabaseCleaner.strategy

    = :transaction end config.around(:example) do |example| DatabaseCleaner.cleaning { example.run } end end
  18. RSpec.configure do |config| config.before(:suite) do # ... DatabaseCleaner.clean_with :truncation DatabaseCleaner.strategy

    = :transaction end config.around(:example) do |example| DatabaseCleaner.cleaning { example.run } end end
  19. NOT COVERED TODAY (BUT WATCH OUT FOR THESE) ➤Global variables

    / singletons ➤Class variables ➤Mutating constants
  20. “ …let us prepare to grapple with the ineffable itself,

    and see if we may not eff it after all. -Douglas Adams, Dirk Gently’s Holistic Detective Agency
  21. class FootTest < ActiveSupport::TestCase test 'quadruped detection' do Foot.expects(:count).returns(4) assert

    Foot.belongs_to_quadruped? end test 'biped default' do assert_equal 2, Foot.count end end
  22. class FootTest < ActiveSupport::TestCase test 'quadruped detection' do Foot.expects(:count).returns(4) assert

    Foot.belongs_to_quadruped? end test 'biped default' do assert_equal 2, Foot.count end end
  23. class FootTest < ActiveSupport::TestCase test 'quadruped detection' do Foot.expects(:count).returns(4) assert

    Foot.belongs_to_quadruped? end test 'biped default' do assert_equal 2, Foot.count end end
  24. class FootTest < ActiveSupport::TestCase test 'quadruped detection' do Foot.expects(:count).returns(4) assert

    Foot.belongs_to_quadruped? end test 'biped default' do assert_equal 2, Foot.count end end
  25. $ bundle exec rake test test/models/before_test.rb # Running: .. Finished

    in 0.012140s, 164.7389 runs/s, 164.7389 assertions/s. 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
  26. $ bundle exec rake test test/models/before_test.rb # Running: .E Finished

    in 0.011270s, 177.4576 runs/s, 88.7288 assertions/s. 1) Error: FootTest#test_biped_default: Mocha::ExpectationError: unexpected invocation: 
 Foot(...).count() unsatisfied expectations: - expected exactly once, invoked twice:
 Foot(...).count(any_parameters)
  27. $ bundle exec rake test test/models/before_test.rb # Running: .E Finished

    in 0.011270s, 177.4576 runs/s, 88.7288 assertions/s. 1) Error: FootTest#test_biped_default: Mocha::ExpectationError: unexpected invocation: 
 Foot(...).count() unsatisfied expectations: - expected exactly once, invoked twice:
 Foot(...).count(any_parameters)
  28. RSpec.describe 'The information superhighway' do before do FakeWeb.register_uri( :get, 'http://google.com',

    body: 'Welcome online!') end it 'gets me online' do response = Net::HTTP.get('google.com', '/') expect(response).to eq('Welcome online!') end end
  29. RSpec.describe 'The information superhighway' do before do FakeWeb.register_uri( :get, 'http://google.com',

    body: 'Welcome online!') end it 'gets me online' do response = Net::HTTP.get('google.com', '/') expect(response).to eq('Welcome online!') end end
  30. RSpec.describe 'The information superhighway' do before do FakeWeb.register_uri( :get, 'http://google.com',

    body: 'Welcome online!') end it 'gets me online' do response = Net::HTTP.get('google.com', '/') expect(response).to eq('Welcome online!') end end
  31. RSpec.describe 'The information superhighway' do before do FakeWeb.register_uri( :get, 'http://google.com',

    body: 'Welcome online!') end it 'gets me online' do response = Net::HTTP.get('google.com', '/') expect(response).to eq('Welcome online!') end end
  32. RSpec.describe 'The information superhighway' do before do FakeWeb.register_uri( :get, 'http://google.com',

    body: 'Welcome online!') end it 'gets me online' do response = Net::HTTP.get('google.com', '/') expect(response).to eq('Welcome online!') end end
  33. RSpec.describe 'My awesome browser' do it 'handles redirects' do response

    = Net::HTTP.get('google.com', '/') expect(response).to include('Moved') end end
  34. RSpec.describe 'My awesome browser' do it 'handles redirects' do response

    = Net::HTTP.get('google.com', '/') expect(response).to include('Moved') end end
  35. RSpec.describe 'My awesome browser' do it 'handles redirects' do response

    = Net::HTTP.get('google.com', '/') expect(response).to include('Moved') end end
  36. $ rspec before_spec.rb .. Finished in 1.01 seconds (files took

    0.21161 seconds to load) 2 examples, 0 failures
  37. $ rspec before_spec.rb .F Failures: 1) My awesome browser handles

    redirects Failure/Error: expect(response).to include('Moved') expected "Welcome online!" to include "Moved"
  38. $ rspec before_spec.rb .F Failures: 1) My awesome browser handles

    redirects Failure/Error: expect(response).to include('Moved') expected "Welcome online!" to include "Moved"
  39. class FavoriteThing class Raindrops < FavoriteThing; end class Whiskers <

    FavoriteThing; end ALL = [ Raindrops.new, Whiskers.new ] def better_than_a_bee_sting? true end end
  40. class FavoriteThing class Raindrops < FavoriteThing; end class Whiskers <

    FavoriteThing; end ALL = [ Raindrops.new, Whiskers.new ] def better_than_a_bee_sting? true end end
  41. class FavoriteThing class Raindrops < FavoriteThing; end class Whiskers <

    FavoriteThing; end ALL = [ Raindrops.new, Whiskers.new ] def better_than_a_bee_sting? true end end
  42. class FavoriteThing class Raindrops < FavoriteThing; end class Whiskers <

    FavoriteThing; end ALL = [ Raindrops.new, Whiskers.new ] def better_than_a_bee_sting? true end end
  43. class FavoriteThing class Raindrops < FavoriteThing; end class Whiskers <

    FavoriteThing; end ALL = [ Raindrops.new, Whiskers.new ] def better_than_a_bee_sting? true end end
  44. RSpec.describe FavoriteThing do it 'always ends in S for some

    reason' do ObjectSpace.each_object(FavoriteThing) do
 |thing|
 last_letter = thing.class.to_s[-1] expect(last_letter).to eq('s') end end 
 # ... end
  45. RSpec.describe FavoriteThing do it 'always ends in S for some

    reason' do ObjectSpace.each_object(FavoriteThing) do
 |thing|
 last_letter = thing.class.to_s[-1] expect(last_letter).to eq('s') end end 
 # ... end
  46. RSpec.describe FavoriteThing do it 'always ends in S for some

    reason' do ObjectSpace.each_object(FavoriteThing) do
 |thing|
 last_letter = thing.class.to_s[-1] expect(last_letter).to eq('s') end end 
 # ... end
  47. RSpec.describe FavoriteThing do it 'always ends in S for some

    reason' do ObjectSpace.each_object(FavoriteThing) do
 |thing|
 last_letter = thing.class.to_s[-1] expect(last_letter).to eq('s') end end 
 # ... end
  48. RSpec.describe FavoriteThing do it 'always ends in S for some

    reason' do ObjectSpace.each_object(FavoriteThing) do
 |thing|
 last_letter = thing.class.to_s[-1] expect(last_letter).to eq('s') end end 
 # ... end
  49. RSpec.describe FavoriteThing do # ... it 'helps me get over

    bee stings' do Wine = Class.new(FavoriteThing) expect(Wine.new).to be_better_than_a_bee_sting end end
  50. RSpec.describe FavoriteThing do # ... it 'helps me get over

    bee stings' do Wine = Class.new(FavoriteThing) expect(Wine.new).to be_better_than_a_bee_sting end end
  51. RSpec.describe FavoriteThing do # ... it 'helps me get over

    bee stings' do Wine = Class.new(FavoriteThing) expect(Wine.new).to be_better_than_a_bee_sting end end
  52. $ rspec before_spec.rb .. Finished in 0.00803 seconds (files took

    0.22942 seconds to load) 2 examples, 0 failures
  53. $ rspec before_spec.rb .F Failures: 1) FavoriteThing always ends in

    S for some reason Failure/Error:
 expect(last_letter).to eq('s') expected: "s" got: "e"
  54. $ rspec before_spec.rb .F Failures: 1) FavoriteThing always ends in

    S for some reason Failure/Error:
 expect(last_letter).to eq('s') expected: "s" got: "e"
  55. RSpec.describe FavoriteThing do it 'always ends in S for some

    reason' do ObjectSpace.each_object(FavoriteThing) do
 |thing|
 last_letter = thing.class.to_s[-1] expect(last_letter).to eq('s') end end 
 # ... end
  56. DoItLive.after_delay(7) do puts "**** it, we'll do it live!" end

    # 7-second delay to work the bleep button, # then prints the message
  57. def become_impatient! DoItLive.module_eval do class << self alias_method :old_after_delay, :after_delay

    def after_delay(delay, &block)
 # no sleep! block.call end end end end
  58. RSpec.describe DoItLive do
 # ... it 'preserves the return value'

    do become_impatient! result = DoItLive.after_delay(5) { 'ALL DONE' } expect(result).to eq('ALL DONE')
 chillax! end end
  59. RSpec.describe DoItLive do
 # ... it 'preserves the return value'

    do become_impatient! result = DoItLive.after_delay(5) { 'ALL DONE' } expect(result).to eq('ALL DONE')
 chillax! end end
  60. RSpec.describe DoItLive do
 # ... it 'preserves the return value'

    do become_impatient! result = DoItLive.after_delay(5) { 'ALL DONE' } expect(result).to eq('ALL DONE')
 chillax! end end
  61. RSpec.describe DoItLive do
 # ... it 'preserves the return value'

    do become_impatient! result = DoItLive.after_delay(5) { 'ALL DONE' } expect(result).to eq('ALL DONE')
 # HAHA KIDDING OF COURSE WE FORGOT TO CHILLAX end end
  62. $ rspec before_spec.rb .. Finished in 0.00848 seconds (files took

    0.1619 seconds to load) 2 examples, 0 failures
  63. $ rspec before_spec.rb .F Failures: 1) DoItLive waits before acting

    Failure/Error: expect(DoItLive).to receive(:sleep).with(5) (DoItLive).sleep(5) expected: 1 time with arguments: (5) received: 0 times
  64. $ rspec before_spec.rb .F Failures: 1) DoItLive waits before acting

    Failure/Error: expect(DoItLive).to receive(:sleep).with(5) (DoItLive).sleep(5) expected: 1 time with arguments: (5) received: 0 times
  65. RSpec.describe DoItLive do # ... context 'unit spec', :monkey_patches do

    it 'preserves the return value' do result = DoItLive.after_delay(1) { 'ALL DONE' } expect(result).to eq('ALL DONE') end end end
  66. RSpec.describe DoItLive do # ... context 'unit spec', :monkey_patches do

    it 'preserves the return value' do result = DoItLive.after_delay(1) { 'ALL DONE' } expect(result).to eq('ALL DONE') end end end
  67. RSpec.describe DoItLive do # ... context 'unit spec', :monkey_patches do

    it 'preserves the return value' do result = DoItLive.after_delay(1) { 'ALL DONE' } expect(result).to eq('ALL DONE') end end end
  68. 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.