Top Banner
継継継継継継継継継継継継継 継継 継 2013/6/19
23

継承を用いたコードのテスト

Aug 22, 2015

Download

Education

You Kobayashi
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: 継承を用いたコードのテスト

継承を用いたコードのテスト

小林 裕

2013/6/19

Page 2: 継承を用いたコードのテスト

目次• 前回より

– 役割のテストを使ったテストダブルの検証

• 継承されたコードのテスト– 継承インターフェース– サブクラスの責任– 固有な動作のテスト

• さいごに2013/6/19 2

Page 3: 継承を用いたコードのテスト

役割テストを使ったテストダブルの検証

2013/6/19 3

Page 4: 継承を用いたコードのテスト

疑問class DiameterDouble def diameter 10 endend

class GearTest < MiniTest::Unit::TestCase def test_calculates_gear_inches gear = Gear.new( chainring: 52, cog: 11, wheel: DiameterDouble.new) assert_in_delta(47.27, gear.gear_inches, 0.01) endend

2013/6/19 4

Diameterizable のインターフェースは ‘ width’ に変更されたが、これらテストダブルと Gear は ‘ diameter’ から変更していない

テストを通過しなくなるのでは?

Page 5: 継承を用いたコードのテスト

テストのモジュール化class WheelTest < MiniTest::Unit::TestCase include DiameterizableInterfaceTest

def setup @wheel = Wheel.new(26, 1.5) end def test_implements_the_diameterizable_interface assert_respond_to(@wheel, :width) end def test_calculates_diameter # ... endend

2013/6/19 5

この部分をモジュール化

Page 6: 継承を用いたコードのテスト

リファクタリング• プログラムの振る舞いを変えることなく、

ソースコードを整理すること

2013/6/19 6

Page 7: 継承を用いたコードのテスト

テストダブルの検証 (1/2)

2013/6/19 7

class DiameterDouble def diameter 10 endend

class DiameterDoubleTest < MiniTest::Unit::TestCase include DiameterizableInterfaceTest

def setup @object = DiameterDouble.new endend

class GearTest < MiniTest::Unit::TestCase # ...end

DiameterDoubleTest FAIL test_implements_the_diameterizablie_interface Expected #<DiameterDouble:…> (DiameterDouble) to respond to #width.GearTest PASS test_calculates_gear_inches

width に変更

Page 8: 継承を用いたコードのテスト

テストダブルの検証 (2/2)

2013/6/19 8

class DiameterDouble def width 10 endend

class DiameterDoubleTest < MiniTest::Unit::TestCase include DiameterizableInterfaceTest

def setup @object = DiameterDouble.new endend

class GearTest < MiniTest::Unit::TestCase # ...end

DiameterDoubleTest PASS test_implements_the_diameterizable_interface

GearTest ERROR test_calculates_gear_inches undefined method ‘diameter’ for #<DiameterDouble:0x0000010090a7f8> gear_test.rb:35:in ‘gear_inches’ gear_test.rb:86:in ‘test_calculates_gear_inches’

class Gear

def gear_inches ratio * wheel.width end

#...end

Page 9: 継承を用いたコードのテスト

テストダブルの検証• 役割に対して共有可能なテストモジュール

2013/6/19 9

Page 10: 継承を用いたコードのテスト

継承されたコードのテスト

2013/6/19 10

Page 11: 継承を用いたコードのテスト

スーパークラスclass Bicycle attr_reader :size, :chain, :tire_size def initialize(args={}) @size = args[:size] @chain = args[:chain] || default_chain @tire_size = args[:tire_size] || default_tire_size post_initialize(args) end

def spares { tire_size: tire_size, chain: chain }.merge(local_spares) end

def default_tire_size raise NotImplementedError end

# サブクラスでオーバーライドされる可能性がある def post_initialize(args) nil end

def local_spares {} end

def default_chain ‘10-speed’ endend

2013/6/19 11

Page 12: 継承を用いたコードのテスト

サブクラスclass RoadBike < Bicycle attr_reader :tape_color

def post_initialize(args) @tape_color = args[:tape_color] end

def local_spares {tape_color: tape_color} end

def default_tire_size ‘23’ endend

2013/6/19 12

ヒエラルキー内のすべてのオブジェクトをリスコフの置換原則に従って生成する

ヒエラルキー内のすべてのオブジェクト間で共通のテストが共有可能

Page 13: 継承を用いたコードのテスト

共通インターフェース (1/2)module BycycleInterfaceTest def test_responds_to_default_tire_size assert_respond_to (@object, :default_tire_size) end

def test_responds_to_default_chain assert_respond_to (@object, :default_chain) end

def test_responds_to_chain assert_respond_to(@object, :chain) end

def test_responds_to_size assert_respond_to(@object, :size) end

def test_responds_to_tire_size assert_respond_to(@object, :tire_size) end

def test_responds_to_spares assert_respond_to(@object, :spares) endend

2013/6/19 13

Page 14: 継承を用いたコードのテスト

共通インターフェース (2/2)class BicycleTest < MiniTest::Unit::TestCase include BicycleInterfaceTest def setup @bike = @object = Bicycle.new({tire_size: 0}) endend

class RoadBikeTest < MiniTest::Unit::TestCase include BicycleInterfaceTest def setup @bike = @object = RoadBike.new endend

2013/6/19 14

BicycleTest PASS test_responds_to_default_chain PASS test_responds_to_tire_size PASS test_responds_to_chain PASS test_responds_to_spares PASS test_responds_to_default_tire_size

RoadBikeTest PASS test_responds_to_default_chain PASS test_responds_to_tire_size PASS test_responds_to_chain PASS test_responds_to_spares PASS test_responds_to_default_tire_size

Page 15: 継承を用いたコードのテスト

サブクラスの責任• サブクラスの動作確認• スーパークラスによる強制

2013/6/19 15

Page 16: 継承を用いたコードのテスト

サブクラスの動作確認 (1/2)module BicycleSubclassTest def test_responds_to_post_initialize assert_respond_to(@object, :post_initialize) end def test_responds_to_local_spares assert_respond_to(@object, :local_spares) end def test_responds_to_default_tire_size assert_respond_to(@object, :default_tire_size) endend

継承するかは自由

サブクラスによって実行

2013/6/19 16

Page 17: 継承を用いたコードのテスト

サブクラスの動作確認 (2/2)class RoadBikeTest < MiniTest::Unit::TestCase include BicycleInterfaceTest include BicycleSubclassTest def setup @bike = @object = RoadBike.new endend

class MountainBikeTest < MiniTest::Unit::TestCase include BicycleInterfaceTest include BicycleSubclassTest def setup @bike = @object = MountainBike.new endend

2013/6/19 17

RoadBikeTest PASS test_responds_to_default_tire_size PASS test_responds_to_spares PASS test_responds_to_chain PASS test_responds_to_post_initialize PASS test_responds_to_local_spares PASS test_responds_to_size PASS test_responds_to_tire_size PASS test_responds_to_default_chain

併せてインクルードすることで正しくサブクラスを生成することが可能

Page 18: 継承を用いたコードのテスト

スーパークラスによる強制class BicycleTest < MiniTest::Unit::TestCase include BicycleInterfaceTest

def setup @bike = @object = RoadBike.new({tire_size: 0}) end def test_forces_subclasses_to_implement_default_tire_size assert_raises(NotImplementedError) {@bike.default_tire_size} endend

2013/6/19 18

BicycleTest PASS test_responds_to_default_tire_size PASS test_responds_to_size PASS test_responds_to_default_chain PASS test_responds_to_tire_size PASS test_responds_to_chain PASS test_responds_to_spares PASS test_forces_subclasses_to_implement_default_tire_size

Page 19: 継承を用いたコードのテスト

固有な動作のテスト• サブクラスにおける動作のテスト• スーパークラスにおける動作のテスト

2013/6/19 19

Page 20: 継承を用いたコードのテスト

サブクラスにおける動作のテストclass RoadBikeTest < MiniTest::Unit::TestCase include BicycleInterfaceTest include BicycleSubclassTest def setup @bike = @object = RoadBike.new({tape_color: 'red‘}) end def test_puts_tape_color_in_local_spares assert_equal 'red', @bike.local_spares[:tape_color] endend

2013/6/19 20

RoadBikeTest PASS test_responds_to_default_chain PASS test_responds_to_default_tire_size PASS test_puts_tape_color_in_local_spares PASS test_responds_to_spares PASS test_responds_to_size PASS test_responds_to_local_spares PASS test_responds_to_post_initialize PASS test_responds_to_tire_size PASS test_responds_to_chain

Page 21: 継承を用いたコードのテスト

スーパークラスにおける動作のテストclass StubbedBike < Bicycle def default_tire_size 0 end def local_spares {saddle: 'painful'} endend

class BicycleTest < MiniTest::Unit::TestCase include BicycleInterfaceTest

def setup @bike = @object = Bicycle.new({tire_size: 0}) @stubbed_bike = StubbedBike.new end

def test_forces_subclasses_to_implement_default_tire_size assert_raises(NotImplementedError) { @bike.default_tire_size} end def test_includes_local_spares_in_spares assert_equal @stubbed_bike.spares { tire_size: 0, chain: '10-speed', saddle: 'painful'} endend

2013/6/19 21

BicycleTest PASS test_responds_to_spares PASS test_responds_to_tire_size PASS test_responds_to_default_chain PASS test_responds_to_default_tire_size PASS test_forces_subclasses_to_implement_default_tire_size PASS test_responds_to_chain PASS test_includes_local_spares_in_spares PASS test_responds_to_size

Page 22: 継承を用いたコードのテスト

さいごに• テストは不可欠• アプリケーションは高レベルで抽象化

– 変更に対して安全性のテストが必要• テストは一度、適切な場所に

• 依存関係の管理• インターフェースの生成• Persist, Practice, Experiment, Imagine2013/6/19 22

ResponsibilitiesDependencies

InterfacesDucks

InheritanceBehavior sharing

CompositionTesting

Page 23: 継承を用いたコードのテスト

ありがとうございました

2013/6/19 23