iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
自我挑戰組

入坑 RoR 必讀 - Ruby 物件導向設計實踐系列 第 20

Day20 CH9 設計節省成本的測試(下)

  • 分享至 

  • xImage
  •  

今天,要來說明鴨子類型的測試,選擇好測試案例後,我們就可以根據前面兩天的步驟來測試輸入及輸出訊息。

測試鴨子類型

測試角色

以第5章的程式碼為範例,程式碼包含了PreparersTrip之間的合作,它現在可以被當作是一個 Preparable

class Mechanic
  def prepare_trip(trip)
    trip.bicycles.each {|bicycle|
	  prepare_bicycle(bicycle)}
  end 

  #...
end

class TripCoordinator
  def prepare_trip(trip)
    buy_food(trip.customers)
  end

  # ...
end

class Driver
  def prepare__trip(trip)
    vehicle = trip.vehicle
    gas_up(vehicle)
    fill_water_tank(vehicle) 
  end

  # ...
end

class Trip
  attr_reader :bicycles, :customers, :vehicle
  def prepare(preparers) 
    preparers.each {|preparer|
      preparer.prepare_trip(self)}
  end 
end
  • PreparerlnterfaceTest定義為模組,測試便可以只撰寫一次,然後在所有扮演該角色的物件中重複使用。
  • test_implements_the_preparer_interface方法是用於測試輸入訊息,因此它是屬於接收物件的測試。
module PreparerInterfaceTest
  def test_implements_the_preparer_interface 
    assert_respond_to(@object, :prepare_trip) 
  end
end
class MechanicTest < MiniTest::Unit::TestCase
  include PreparerInterfaceTest

  def setup
    @mechanic = @object = Mechanic.new
  end
  #其他依賴於©mechanic的測試 
end

class TripCoordinatorTest < MiniTest::Uiiit::Testcase 
  include PreparerInterfaceTest

  def setup
    @trip_coordinator = @object = Tripcoordinator.new
  end 
end

class DriverTest < MiniTest::Uiiit::TestCase 
  include PreparerlnterfaceTest

  def setup
    @driver = @object = Driver.new 
  end
end
DriverTest
	PASS test_implements_the_preparer_interface

MechanicTest
	PASS test_irr^)lements_the_preparer_interface

TripCoordinatorTest
	PASS test_in^>lements_the_preparer_interface

證明所有的接收者都正確地實作了prepare.trip

現在用昨天學到的測試方法證明Trip有正確地傳送訊息。

class TripTest < MiniTest::Unit::Testcase 
  def test_requests_trip_preparation
    @preparer = MiniTest::Mock.new
    @trip = Trip.new 
    @preparer.expect(:prepare_trip, nil, [@trip])

    @trip.prepare{[@preparer])
    @preparer.verify
  end
end

TripTest
	PASS test_requests_trip_preparation

使用角色測試驗證替身

在先前的「測試輸入訊息」一節裡,介紹了「活在夢中」的問題。而在該節的最終測試裡,有一項應該失敗但卻通過的測試,因為測試替身使用了過時的方法。

test_implements_the_diameterizable_interfaceWheel裡擷取成一個獨立的模組,將撷取出來的行為重新引入到WheelTest,並使用Wheel初始化@object

module DiameterizablelnterfaceTest
  def test_implements_the_diaineterizable_interface 
    assert_respond_to(@object, :width)
  end
end
class WheelTest < MiniTest: :Uiiit: :Testcase
  include DiameterizablelnterfaceTest

  def setup
    @wheel = @object = Wheel.new(26, 1.5)
  end

  def test_calculates_diameter 
    # ...
  end 
end

WheelTest
	PASS test_implements_the_diameterizable_interface
	PASS test_calculates_diameter

測試也會有時效性的問題,我們利用這個模組來防止測試替身過時。

class DiameterDouble 
	def diameter
		10
	end
end 

#證明測試替身遵從了
#這項測試所期望的介面。
class DiameterDoubleTest < MiniTest::Unit::TestCase
  include DiameterizablelnterfaceTest
  def setup
    @object = DiameterDouble.new end
  end
end

class GearTest < MiniTost::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)
  end
end
DiameterDoubleTest
	FAIL test_implements_the_diameterizable_interface
		Expected #<DiameterDouble:...> (DiameterDouble) to respond to #width.
GearTest
	PASS test_calculates_gear_inches

藉由測試失敗會致使我們修正DiameterDouble, 去補上width

class DiameterDouble 
  def width
    10
  end
end

通過之後又會再撞到下一個錯誤,因為diameter已經被換成width,而gear_inches還未跟上

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
	# 終於,「width」 替換成了 「diameter」

	ratio* wheel .width
 end

# ...
end
DiameterDoubleTest
	PASS test_inplements_the_diameterizable_interface 03
GearTest
	PASS test_calculates_gear_inches

測試鴨子類型,會產生測試可共用角色的需求。從受測物件的角度來看,其他所有的物件都是角色。視物件為角色的代表,這樣能夠讓應用程式和測試裡的耦合變得鬆散並且提昇靈活性。

實作上就是透過一步一步的修正以通過測試,確認所有改動都是合理且可行的。

參考資料:

  • Practical Object-Oriented Design in Ruby: An Agile Primer

上一篇
Day19 CH9 設計節省成本的測試(中)
下一篇
Day21 CH9 設計節省成本的測試(延伸)
系列文
入坑 RoR 必讀 - Ruby 物件導向設計實踐30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言