ภาษาใหม่จะมีลักษณะอย่างไรถ้ามันถูกออกแบบมาตั้งแต่เริ่มต้นจนถึง TDD ง่าย?


9

ด้วยบางภาษาที่พบบ่อยที่สุด (Java, C #, Java, ฯลฯ ) บางครั้งดูเหมือนว่าคุณกำลังทำงานกับภาษาเมื่อคุณต้องการ TDD รหัสของคุณอย่างเต็มที่

ตัวอย่างเช่นใน Java และ C # คุณจะต้องการเยาะเย้ยการพึ่งพาใด ๆ ของชั้นเรียนของคุณและกรอบการเยาะเย้ยส่วนใหญ่จะแนะนำให้คุณจำลองการเชื่อมต่อไม่ได้เรียน บ่อยครั้งซึ่งหมายความว่าคุณมีอินเทอร์เฟซมากมายกับการใช้งานเพียงครั้งเดียว (เอฟเฟกต์นี้จะสังเกตได้ชัดเจนยิ่งขึ้นเพราะ TDD จะบังคับให้คุณเขียนคลาสที่มีขนาดเล็กกว่า) โซลูชันที่ให้คุณจำลองคลาสคอนกรีตอย่างถูกต้องทำสิ่งต่าง ๆ เช่นดัดแปลงคอมไพเลอร์หรือแทนที่ตัวโหลดคลาส ฯลฯ ซึ่งน่ารังเกียจ

แล้วภาษาจะเป็นอย่างไรถ้ามันถูกออกแบบมาตั้งแต่เริ่มต้นจนถึงเป็น TDD ที่ยอดเยี่ยม? อาจเป็นไปได้ที่ภาษาระดับวิธีการอธิบายการพึ่งพา (แทนที่จะผ่านอินเตอร์เฟซไปยังตัวสร้าง) และความสามารถในการแยกอินเทอร์เฟซของชั้นเรียนโดยไม่ทำอย่างชัดเจน?


แล้วภาษาที่ไม่ต้องการ TDD ล่ะ? blog.8thlight.com/uncle-bob/2011/10/20/Simple-Hickey.html
งาน

2
ไม่ต้องการภาษาTDD TDD คือการปฏิบัติที่เป็นประโยชน์และเป็นหนึ่งในจุด Hickey คือว่าเพียงเพราะคุณทดสอบไม่ได้หมายความว่าคุณอาจหยุดคิด
Frank Shearar

การทดสอบการพัฒนาขับเคลื่อนนั้นเกี่ยวกับการรับสิทธิ์APIภายในและภายนอกของคุณและทำสิ่งนั้นล่วงหน้า ดังนั้นใน Java มันคือทั้งหมดที่เกี่ยวกับอินเตอร์เฟส - คลาสจริงเป็นผลพลอยได้

คำตอบ:


6

หลายปีที่ผ่านมาฉันรวมต้นแบบที่ตอบคำถามคล้าย ๆ กัน นี่คือภาพหน้าจอ:

การทดสอบปุ่มเป็นศูนย์

แนวคิดก็คือการยืนยันนั้นสอดคล้องกับรหัสและการทดสอบทั้งหมดจะดำเนินการตามการกดแป้นแต่ละครั้ง ดังนั้นทันทีที่คุณทำการทดสอบผ่านคุณจะเห็นวิธีเปลี่ยนเป็นสีเขียว


2
ฮ่าฮ่ามันน่าทึ่งมาก! จริง ๆ แล้วฉันชอบความคิดในการทดสอบร่วมกับโค้ด มันค่อนข้างน่าเบื่อ (แม้ว่าจะมีเหตุผลที่ดีมาก) ใน. NET ที่มีแอสเซมบลีแยกต่างหากพร้อมกับเนมสเปซแบบขนานสำหรับการทดสอบหน่วย นอกจากนี้ยังทำให้การปรับโครงสร้างใหม่ง่ายขึ้นเนื่องจากรหัสการเคลื่อนย้ายจะทำการทดสอบโดยอัตโนมัติ: P
Geoff

แต่คุณต้องการออกจากการทดสอบที่นั่นหรือไม่ คุณจะปล่อยให้พวกเขาเปิดใช้งานสำหรับรหัสการผลิตหรือไม่ บางทีพวกเขาอาจเป็น # ifdef ที่จะออกหา C มิฉะนั้นเรากำลังดูความนิยมของโค้ด / เวลาทำงาน
Mawg กล่าวว่านำสถานะโมนิก้ากลับคืนมาใน

มันเป็นต้นแบบอย่างหมดจด ถ้ามันเป็นของจริงเราจะต้องพิจารณาสิ่งต่าง ๆ เช่นประสิทธิภาพและขนาด แต่มันเร็วไปกว่านั้นที่จะต้องกังวลเกี่ยวกับเรื่องนั้นและถ้าเราถึงจุดนั้นมันจะไม่ยากที่จะเลือกว่าจะออกไปไหน หรือหากต้องการให้ออกจากการยืนยันรหัสรวบรวม ขอบคุณสำหรับความสนใจของคุณ
Carl Manaster

5

มันจะเป็นแบบไดนามิกมากกว่าพิมพ์แบบคงที่ การพิมพ์เป็ดจะทำหน้าที่เดียวกับที่ส่วนต่อประสานทำในภาษาที่พิมพ์แบบคงที่ นอกจากนี้คลาสจะสามารถแก้ไขได้ที่รันไทม์เพื่อให้กรอบการทดสอบสามารถ 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 ในทุกวัตถุ ถ้าคุณคิดว่าการปรับเปลี่ยนคลาสของระบบเช่นนั้นต้องการความระมัดระวังคุณก็พูดถูก แต่มันเป็นสิ่งที่สมบูรณ์แบบสำหรับสิ่งที่ห้องสมุดทดสอบกำลังทำอยู่ที่นี่และชั้นเรียนแบบเปิดทำให้เป็นไปได้


คำตอบที่ดี! พวกคุณกำลังเริ่มคุยกับฉันกลับไปสู่ภาษาแบบไดนามิก :) ฉันคิดว่าการพิมพ์เป็ดเป็นกุญแจสำคัญที่นี่เคล็ดลับด้วย. ใหม่ก็อาจจะเป็นในภาษาที่พิมพ์แบบคงที่ (แม้ว่ามันจะสง่าน้อย
เจฟฟ์

3

แล้วภาษาจะเป็นอย่างไรถ้ามันถูกออกแบบมาตั้งแต่เริ่มต้นจนถึงเป็น TDD ที่ยอดเยี่ยม?

'ใช้งานได้ดีกับ TDD' แน่นอนไม่เพียงพอที่จะอธิบายภาษาดังนั้นจึงสามารถ "ดู" เหมือนอะไรก็ได้ Lisp, Prolog, C ++, Ruby, Python ... เลือกสิ่งที่คุณต้องการ

นอกจากนี้ยังไม่ชัดเจนว่าการรองรับ TDD เป็นสิ่งที่จัดการได้ดีที่สุดโดยภาษานั้น แน่นอนว่าคุณสามารถสร้างภาษาที่ทุกฟังก์ชั่นหรือวิธีการมีการทดสอบที่เกี่ยวข้องและคุณสามารถสร้างในการสนับสนุนสำหรับการค้นพบและดำเนินการทดสอบเหล่านั้น แต่กรอบการทดสอบหน่วยจัดการการค้นพบและส่วนการดำเนินการเป็นอย่างดีและมันยากที่จะเห็นวิธีการเพิ่มความต้องการของการทดสอบสำหรับทุกฟังก์ชั่นอย่างหมดจด การทดสอบจำเป็นต้องมีการทดสอบด้วยหรือไม่ หรือมีฟังก์ชั่นการเรียนสองประเภท - วิชาปกติที่ต้องการทดสอบและฟังก์ชั่นทดสอบที่ไม่ต้องการ นั่นไม่ได้ดูสวยงามมาก

อาจเป็นการดีกว่าที่จะสนับสนุน TDD ด้วยเครื่องมือและกรอบงาน สร้างเป็น IDE สร้างกระบวนการพัฒนาที่กระตุ้นให้มัน

นอกจากนี้หากคุณออกแบบภาษาก็ควรที่จะคิดในระยะยาว โปรดจำไว้ว่า TDD เป็นเพียงวิธีการหนึ่งและไม่ใช่วิธีการทำงานที่ทุกคนต้องการ อาจเป็นเรื่องยากที่จะจินตนาการ แต่เป็นไปได้ว่าวิธีที่ดีกว่ากำลังจะมา ในฐานะนักออกแบบภาษาคุณต้องการให้คนอื่นละทิ้งภาษาของคุณเมื่อเกิดอะไรขึ้น?

ทั้งหมดที่คุณสามารถพูดได้เพื่อตอบคำถามคือภาษาที่เอื้อต่อการทดสอบ ฉันรู้ว่าไม่ได้ช่วยอะไรมาก แต่ฉันคิดว่าปัญหาอยู่ที่คำถาม


เห็นด้วยมันเป็นคำถามที่ยากมากที่จะพูดได้ดี ฉันคิดว่าสิ่งที่ฉันหมายถึงคือเครื่องมือทดสอบในปัจจุบันสำหรับภาษาอย่าง Java / C # ให้ความรู้สึกว่าภาษาเริ่มเข้ามาเล็กน้อยและคุณลักษณะภาษาพิเศษ / ทางเลือกบางอย่างจะทำให้ประสบการณ์ทั้งหมดดูสง่างามยิ่งขึ้น (เช่นไม่มีอินเทอร์เฟซสำหรับ 90 % ของชั้นเรียนของฉันมีเพียงชั้นเรียนเท่านั้นที่เหมาะสมจากมุมมองการออกแบบระดับสูงกว่า)
เจฟฟ์

0

ภาษาที่พิมพ์แบบไดนามิกไม่จำเป็นต้องมีส่วนต่อประสานที่ชัดเจน ดู Ruby หรือ PHP เป็นต้น

ในทางกลับกันภาษาที่พิมพ์แบบคงที่เช่น Java และ C # หรือ C ++ บังคับให้พิมพ์และบังคับให้คุณเขียนอินเตอร์เฟสเหล่านั้น

สิ่งที่ฉันไม่เข้าใจคือสิ่งที่คุณมีปัญหากับพวกเขา อินเทอร์เฟซเป็นองค์ประกอบสำคัญของการออกแบบและถูกนำมาใช้ในทุกรูปแบบการออกแบบและในการเคารพหลักการของโซลิด ยกตัวอย่างเช่นฉันใช้อินเทอร์เฟซใน PHP บ่อยครั้งเพราะพวกมันทำให้การออกแบบชัดเจนและพวกเขาก็บังคับใช้การออกแบบด้วย ในทางกลับกันใน Ruby คุณไม่มีทางที่จะบังคับประเภทมันเป็นภาษาที่พิมพ์เป็ด แต่ถึงกระนั้นคุณต้องจินตนาการถึงอินเทอร์เฟซที่มีและคุณต้องนามธรรมการออกแบบในใจของคุณเพื่อที่จะใช้งานได้อย่างถูกต้อง

ดังนั้นแม้ว่าคำถามของคุณอาจฟังดูน่าสนใจ แต่ก็หมายความว่าคุณมีปัญหาเกี่ยวกับความเข้าใจหรือการใช้เทคนิคการฉีดแบบพึ่งพา

และเพื่อตอบคำถามของคุณโดยตรง Ruby และ PHP มีโครงสร้างพื้นฐานการเยาะเย้ยที่ยิ่งใหญ่ทั้งที่สร้างขึ้นในกรอบการทดสอบหน่วยและจัดส่งแยกต่างหาก (ดู Mockery สำหรับ PHP) ในบางกรณีกรอบงานเหล่านี้ช่วยให้คุณสามารถทำสิ่งที่คุณกำลังแนะนำสิ่งต่าง ๆ เช่นการเยาะเย้ยการโทรคงที่หรือการเริ่มต้นวัตถุโดยไม่ต้องพึ่งพาการอ้างอิงอย่างชัดเจน


1
ฉันยอมรับว่าส่วนต่อประสานนั้นยอดเยี่ยมและเป็นองค์ประกอบสำคัญในการออกแบบ อย่างไรก็ตามในรหัสของฉันฉันพบว่า 90% ของชั้นเรียนมีอินเตอร์เฟซและมีการใช้งานเพียงสองครั้งของส่วนต่อประสานนั้นชั้นเรียนและ mocks ของชั้นเรียนนั้น แม้ว่านี่จะเป็นจุดเชื่อมต่อทางเทคนิค แต่ฉันก็อดไม่ได้ที่จะรู้สึกว่ามันไม่เหมาะ
เจฟฟ์

ฉันไม่ค่อยคุ้นเคยกับการล้อเลียนใน Java และ C # แต่เท่าที่ฉันรู้วัตถุที่ล้อเลียนเลียนแบบวัตถุจริง ฉันมักจะทำการฉีดพึ่งพาโดยใช้พารามิเตอร์ประเภทของวัตถุและส่งจำลองไปที่วิธีการ / ชั้นแทน ฟังก์ชั่น someName คล้ายกับอะไรบางอย่าง (AnotherClass $ object = null) {$ this-> anotherObject = $ object? : AnotherClass ใหม่ } นี่เป็นเคล็ดลับที่ใช้บ่อยในการฉีดการพึ่งพาโดยไม่ได้มาจากส่วนต่อประสาน
Patkos Csaba

1
นี่คือสิ่งที่ภาษาไดนามิกมีความได้เปรียบเหนือภาษา Java / C # ประเภทที่เกี่ยวกับคำถามของฉัน การจำลองทั่วไปของคลาสที่เป็นรูปธรรมจะสร้างคลาสย่อยของคลาสซึ่งหมายความว่าตัวสร้างคลาสที่เป็นรูปธรรมจะถูกเรียกซึ่งเป็นสิ่งที่คุณต้องการหลีกเลี่ยงอย่างแน่นอน (มีข้อยกเว้น แต่มีปัญหาของตัวเอง) การจำลองแบบไดนามิกใช้ประโยชน์จากการพิมพ์เป็ดเท่านั้นดังนั้นจึงไม่มีความสัมพันธ์ระหว่างคลาสที่เป็นรูปธรรมและการเยาะเย้ยของมัน ฉันเคยใช้รหัสใน Python บ่อยครั้ง แต่นั่นก็อยู่ก่อนหน้า TDD ของฉันบางทีอาจถึงเวลาที่จะมองอีกครั้ง!
เจฟฟ์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.