26
Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 1 Introduction to Test Driven Development and Mock Objects Loh Siu Yin ([email protected]) Beyond Broadcast LLP

Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

  • Upload
    others

  • View
    21

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 1

Introduction to Test Driven Development and Mock Objects

Loh Siu Yin ([email protected])

Beyond Broadcast LLP

Page 2: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 2

Test Driven Development (TDD)

● Writing tests: – Ensures correct program behaviour at

deployment.

– Verifies bugs have been fixed.

● These tests:– Are automated.

– Run quickly.

– Can be run independently (no dependence on external systems).

Page 3: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 3

Benefits of TDD

● Allows you to bravely change code to encourage agile development.

– Make the change, run the tests, fix tests that failed.

● Tests verify correct performance.● Tests document the desired behaviour of the

system.

Page 4: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 4

Challenges with TDD

● Writing tests takes time and effort.– Personal observation (60% of effort on tests,

40% on code).

– Re-writing (refactoring) legacy code to be properly testable takes even more time and effort.

● External system dependencies (eg. FTP server or sensor input) need to be mocked-up

– Mocking an object is not easy.

Page 5: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 5

Temperature Logger

● Sensor outputs temperature in Kelvin as a floating point number.

– Eg. 273.15 Kelvin

● Logger must present in Celcius and Fahrenheit.– Eg. 0.0 Celcius, 32.0 Fahrenheit.

Page 6: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 6

Temperature Converter

Page 7: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 7

TDD Process

● Start with “empty” stubbed-out implementation.

● Write tests.● Ensure tests run without

errors.● Flesh out

implementation.● Re-run tests.

Page 8: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 8

TDD Worked Example Roadmap

● Slide 9: Stub-out temp_conv.rb and temp_conv_test.rb

● Slide 10: Initial test_to_c . Run test, check for errors.

● Slide 11: Initial test_to_f .

● Slide 12: Initial implementation for TempConv.to_c .

● Slide 13: More comprehensive test_to_c .

● Slide 14: While writing test_to_f, realised we are working with floats not integers. Also refactored test_to_c .

● Slide 15: Implement TempConv.to_f .

Page 9: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 9

Stub Implementation and Test# temp_conv_test.rbrequire 'test/unit'require 'temp_conv'

class TempConvTest < Test::Unit::TestCase # Setup. # This is run before each test. def setup end

# Tear down test and clean up. # This is run after each unit test. def teardown endend

# temp_conv.rb

class TempConv def to_c( inp ) end

def to_f( inp ) endend

Page 10: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 10

Test TempConv.to_cclass TempConvTest < Test::Unit::TestCase # Setup. # This is run before each test. def setup end

# Tear down test and clean up. # This is run after each unit test. def teardown end

def test_to_c temp = TempConv.new assert temp.to_c(273.15) endend

./temp_conv_test.rb Loaded suite ./temp_conv_testStartedFFinished in 0.008432 seconds.

1) Failure:test_to_c(TempConvTest) [./temp_conv_test.rb:21]:<nil> is not true.

1 tests, 1 assertions, 1 failures, 0 errors

Page 11: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 11

Test TempConv.to_f

def test_to_c temp = TempConv.new assert temp.to_c(273.15) end

def test_to_f temp = TempConv.new assert temp.to_f(273.15) end

./temp_conv_test.rb Loaded suite ./temp_conv_testStartedFFFinished in 0.009581 seconds.

1) Failure:test_to_c(TempConvTest) [./temp_conv_test.rb:21]:<nil> is not true.

2) Failure:test_to_f(TempConvTest) [./temp_conv_test.rb:26]:<nil> is not true.

2 tests, 2 assertions, 2 failures, 0 errors

Page 12: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 12

Flesh-out TempConv.to_c

#!/usr/bin/ruby# temp_conv.rb

class TempConv def to_c( inp ) inp - 273.15 end

def to_f( inp ) endend

def test_to_c temp = TempConv.new assert temp.to_c(273.15) end

./temp_conv_test.rb Loaded suite ./temp_conv_testStarted.FFinished in 0.009757 seconds.

1) Failure:test_to_f(TempConvTest) [./temp_conv_test.rb:26]:<nil> is not true.

2 tests, 2 assertions, 1 failures, 0 errors

Page 13: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 13

Refining test to_c● 271.15 K = 0 deg Celcius

def test_to_c temp = TempConv.new deg_c = temp.to_c(273.15) assert deg_c assert_equal 0, deg_c end

def test_to_f temp = TempConv.new assert temp.to_f(273.15) end

./temp_conv_test.rb Loaded suite ./temp_conv_testStarted.FFinished in 0.008483 seconds.

1) Failure:test_to_f(TempConvTest) [./temp_conv_test.rb:28]:<nil> is not true.

2 tests, 3 assertions, 1 failures, 0 errors

Page 14: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 14

Refining Test to_f def test_to_c temp = TempConv.new deg_c = temp.to_c(273.15) assert deg_c assert_in_delta 0.0, deg_c, 0.001 end

def test_to_f temp = TempConv.new deg_f = temp.to_f(273.15) assert deg_f assert_in_delta 32.0, deg_f, 0.001 end

1) Failure:test_to_f(TempConvTest) [./temp_conv_test.rb:29]:<nil> is not true.

2 tests, 3 assertions, 1 failures, 0 errors

def to_c( inp ) inp - 273.15 end

def to_f( inp ) end

Page 15: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 15

Implementing TempConv.to_f#!/usr/bin/ruby# temp_conv.rb

class TempConv def to_c( inp ) inp - 273.15 end

def to_f( inp ) tc = TempConv.new tc.to_c(inp) * 100.0/180.0 + 32.0 endend

./temp_conv_testStarted..Finished in 0.000924 seconds.

2 tests, 4 assertions, 0 failures, 0 errors

Page 16: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 16

End of Part 1

Questions / Comments ?

Page 17: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 17

Mock Objects

● There is a temperature sensor in the real world.● Testing with a real sensor is cumbersome.● We need a mocked-up sensor for testing.

Page 18: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 18

Mock Objects Library

● Mocha – the ruby equivalent of Jmock● Allows:

– Mocking-up Objects.● RealSensor => MockSensor

– Stubbing them with preset return values.● MockSensor.get() => 273.15● MockSensor.get() => 373.15 etc.

Page 19: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 19

Mock Sensor Example Roadmap

● Slide 20: Write a implementation and test stubs. Test imports Mock Objects library.

● Slide 21: Test with MockSensor returning a fixed float value.

● Slide 22: MockSensor returning an initial value then a second steady state value.

● Slide 23: Using assert_equal to examine values.● Slide 24: MockSensor returning alternating

values implemented with a State Machine.

Page 20: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 20

Sensor.rb and Test

# sensor.rb

class Sensor # Returns the reading of the sensor. def get # In the real sensor this would # be code to read and control an # Analog to Digital converter # to return a floating point value. endend

# sensor_test.rb

require 'sensor'require 'test/unit'

require 'rubygems'require 'mocha'

class SensorTest < Test::Unit::TestCase

def test_sensor_get_should_return_float sens = Sensor.new sens.expects(:get).returns(Float) assert_equal Float, sens.get() end

end

Page 21: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 21

Stubbing: Faking return values

def test_sensor_get_return_value Sensor.any_instance.stubs(:get).returns(273.15) sens = Sensor.new assert_equal 273.15, sens.get() end

./sensor_test.rb Loaded suite ./sensor_testStarted..Finished in 0.00148 seconds.

2 tests, 3 assertions, 0 failures, 0 errors

Page 22: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 22

Multiple Return Values def test_sensor_get_multiple_values sens = Sensor.new sens.stubs(:get).returns(273.15).then.returns(373.15) assert_equal 273.15, sens.get() assert_equal 373.15, sens.get() end

./sensor_test.rb Loaded suite ./sensor_testStarted...Finished in 0.002045 seconds.

3 tests, 5 assertions, 0 failures, 0 errors

Page 23: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 23

Using assert_equal as “printf” def test_sensor_get_multiple_values sens = Sensor.new sens.stubs(:get).returns(273.15).then.returns(373.15) assert_equal 273.15, sens.get() assert_equal 373.15, sens.get() assert_equal '', sens.get() end

./sensor_test.rb Loaded suite ./sensor_testStartedF..Finished in 0.007987 seconds. 1) Failure:test_sensor_get_multiple_values(SensorTest) [./sensor_test.rb:29:in `test_sensor_get_multiple_values'<""> expected but was<373.15>.

Page 24: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 24

Mocks & State Machines def test_sensor_get_alternating_values output = states('output_states').starts_as(:low) sens = Sensor.new sens.stubs(:get).when(output.is(:low)).returns(273.15).then(output.is(:high)) sens.stubs(:get).when(output.is(:high)).returns(373.15).then(output.is(:low)) assert_equal 273.15, sens.get() assert_equal 373.15, sens.get() assert_equal 273.15, sens.get() assert_equal 373.15, sens.get() end

./sensor_test.rb Loaded suite ./sensor_testStarted....Finished in 0.003189 seconds.

4 tests, 9 assertions, 0 failures, 0 errors

Page 25: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 25

Mock Objects: Summary

● Useful when real object is not easily available for testing. Eg. FTP server, temperature sensor etc.

● Mock objects simulate the behaviour of real objects so that you can test your code that manipulates the mocked object.

– eg. MockSensor driving your DataLogging code

● Stubbing allows you to intelligently inject return values from the Mocked Object.

Page 26: Introduction to Test Driven Development and Mock …files.meetup.com/1704608/intro_tdd.pdf40% on code). – Re-writing (refactoring) legacy code to be properly testable takes even

Copyright © 2010 September 25, Beyond Broadcast LLP ([email protected]) TDD 26

Beyond Broadcast LLP