มันจะเป็นแบบไดนามิกมากกว่าพิมพ์แบบคงที่ การพิมพ์เป็ดจะทำหน้าที่เดียวกับที่ส่วนต่อประสานทำในภาษาที่พิมพ์แบบคงที่ นอกจากนี้คลาสจะสามารถแก้ไขได้ที่รันไทม์เพื่อให้กรอบการทดสอบสามารถ stub หรือวิธีการจำลองในคลาสที่มีอยู่ได้อย่างง่ายดาย Ruby เป็นหนึ่งในภาษาดังกล่าว rspecเป็นกรอบการทดสอบชั้นนำสำหรับ TDD
วิธีการพิมพ์แบบไดนามิกช่วยการทดสอบ
ด้วยการพิมพ์แบบไดนามิกคุณสามารถสร้างวัตถุจำลองโดยเพียงแค่สร้างคลาสที่มีอินเทอร์เฟซเดียวกัน (ลายเซ็นวิธีการ) วัตถุผู้ทำงานร่วมกันที่คุณต้องการจำลอง ตัวอย่างเช่นสมมติว่าคุณมีคลาสที่ส่งข้อความ:
class MessageSender
def send
# Do something with a side effect
end
end
สมมติว่าเรามี MessageSenderUser ที่ใช้อินสแตนซ์ของ MessageSender:
class MessageSenderUser
def initialize(message_sender)
@message_sender = message_sender
end
def do_stuff
...
@message_sender.send
...
@message_sender.send
...
end
end
สังเกตการใช้งานที่นี่ของการฉีดพึ่งพาซึ่งเป็นแก่นของการทดสอบหน่วย เราจะกลับมาที่
คุณต้องการทดสอบว่าการMessageSenderUser#do_stuff
โทรส่งสองครั้ง เช่นเดียวกับที่คุณพิมพ์ด้วยภาษาแบบคงที่คุณสามารถสร้าง MessageSender จำลองซึ่งนับจำนวนครั้งที่send
ถูกเรียก แต่ไม่เหมือนกับภาษาที่พิมพ์แบบคงที่คุณไม่จำเป็นต้องมีคลาสอินเตอร์เฟส คุณเพิ่งสร้างและสร้างมันขึ้นมา:
class MockMessageSender
attr_accessor :send_count
def initialize
@send_count = 0
end
def send
@send_count += 1
end
end
และใช้ในการทดสอบของคุณ:
mock_sender = MockMessageSender.new
MessageSenderUser.new(mock_sender).do_stuff
assert_equal(mock_sender.send_count, 2)
โดยตัวของมันเอง "การพิมพ์เป็ด" ของภาษาที่พิมพ์แบบไดนามิกไม่ได้เพิ่มความสามารถในการทดสอบมากนักเมื่อเทียบกับภาษาที่พิมพ์แบบคงที่ แต่ถ้าหากคลาสไม่ได้ถูกปิด แต่สามารถแก้ไขได้ที่ runtime? นั่นเป็นตัวเปลี่ยนเกม เรามาดูกันว่า
จะทำอย่างไรถ้าคุณไม่ต้องใช้การฉีดพึ่งพาเพื่อให้คลาสทดสอบได้
สมมติว่า MessageSenderUser จะใช้ MessageSender เพื่อส่งข้อความเท่านั้นและคุณไม่จำเป็นต้องอนุญาตให้มีการแทนที่ MessageSender ด้วยคลาสอื่น ภายในโปรแกรมเดียวสิ่งนี้มักเกิดขึ้น ลองเขียน MessageSenderUser ใหม่เพื่อที่จะสร้างและใช้ MessageSender โดยไม่ต้องพึ่งพาการฉีด
class MessageSenderUser
def initialize
@message_sender = MessageSender.new
end
def do_stuff
...
@message_sender.send
...
@message_sender.send
...
end
end
MessageSenderUser ตอนนี้ใช้ง่ายกว่า: ไม่มีใครสร้างมันจำเป็นต้องสร้าง MessageSender เพื่อให้มันใช้ ดูเหมือนว่าจะไม่ได้รับการปรับปรุงอย่างใหญ่หลวงในตัวอย่างง่ายๆนี้ แต่ตอนนี้จินตนาการว่า MessageSenderUser ถูกสร้างขึ้นในสถานที่มากกว่าหนึ่งครั้งหรือว่ามีการขึ้นต่อกันสามครั้ง ขณะนี้ระบบมีอินสแตนซ์ที่ส่งผ่านจำนวนมากทั่วเพื่อให้หน่วยทดสอบมีความสุขไม่ใช่เพราะมันจำเป็นต้องปรับปรุงการออกแบบเลย
คลาสที่เปิดให้คุณทดสอบโดยไม่ต้องพึ่งพาการฉีด
กรอบการทดสอบในภาษาที่มีการพิมพ์แบบไดนามิกและคลาสเปิดสามารถทำให้ TDD ค่อนข้างดี นี่คือข้อมูลโค้ดจากการทดสอบ rspec สำหรับ MessageSenderUser:
mock_message_sender = mock MessageSender
MessageSender.should_receive(:new).and_return(mock_message_sender)
mock_message_sender.should_receive(:send).twice.with(no_arguments)
MessageSenderUser.new.do_stuff
นั่นคือการทดสอบทั้งหมด หากMessageSenderUser#do_stuff
ไม่ได้เรียกใช้MessageSender#send
สองครั้งแน่นอนการทดสอบนี้จะล้มเหลว คลาสของ MessageSender ที่แท้จริงนั้นไม่เคยถูกเรียกใช้: เราบอกการทดสอบว่าเมื่อใดก็ตามที่มีคนพยายามสร้าง MessageSender พวกเขาควรจะได้รับ MessageSender จำลองของเราแทน ไม่จำเป็นต้องฉีดพึ่งพา
เป็นเรื่องที่ดีมากที่ได้ทำแบบทดสอบที่ง่ายกว่านี้ มันดีกว่าที่จะไม่ต้องใช้การฉีดพึ่งพาเว้นแต่ว่ามันสมเหตุสมผลสำหรับการออกแบบของคุณ
แต่สิ่งนี้เกี่ยวข้องกับคลาสที่เปิดอยู่หรือไม่? หมายเหตุ: MessageSender.should_receive
การเรียกร้องให้ เราไม่ได้กำหนด #should_receive เมื่อเราเขียน MessageSender แล้วใครล่ะที่ทำ คำตอบก็คือกรอบการทดสอบทำให้การปรับเปลี่ยนคลาสระบบอย่างระมัดระวังสามารถทำให้มันปรากฏขึ้นโดยกำหนด #should_receive ในทุกวัตถุ ถ้าคุณคิดว่าการปรับเปลี่ยนคลาสของระบบเช่นนั้นต้องการความระมัดระวังคุณก็พูดถูก แต่มันเป็นสิ่งที่สมบูรณ์แบบสำหรับสิ่งที่ห้องสมุดทดสอบกำลังทำอยู่ที่นี่และชั้นเรียนแบบเปิดทำให้เป็นไปได้