differences between stub! and should_receive
Posted by PunNeng, Sun Feb 10 16:12:00 UTC 2008
ใน rspec มีศัพท์แสง(method)หลายๆ คำที่ชวนทำผมงง เช่น mock หรือ stub พอเขียนไปเรื่อยๆ ก็มี should_receive โผล่มาอีก ตอน return ก็มี and_return เข้ามาอีก ตอนแรกกับ rspec นี่ ผมแทบร้องไห้ คือพยายามทำความเข้าใจกับมัน แต่ก็ไม่เข้าใจว่า ไอ้ stub นี่ ทำไปทำไม ?? หรือ and_return นี่ มันคือการตรวจสอบค่า return หรือเปล่า ??
หลังจากที่ลองมันไปสักพัก ปรากฏว่ามีอยู่สองอย่างเท่านั้นที่ผมทำยังไงก็ไม่เข้าใจสักที คือ stub! กับ should_receive แต่ตัวที่ทำให้ผมงงจริงๆ กับสองตัวนี้ คงจะเป็น and_return มากกว่า จนแล้วจนรอดก็ไปเจอพระเอกขี่ม้าขาวจนได้ เป็นตัวอย่างในเว็บของ rspec เอง
A.stub!(:msg).and_return(:default_value) A.should_receive(:msg).with(:arg).and_return(:special_value) A.msg => :default_value A.msg(:any_other_arg) => :default_value A.msg(:arg) => :special_value A.msg(:any_other_other_arg) => :default_value A.msg => :default_value
ความแตกต่าง
stub! คือ การจำลองพฤติกรรมของ method ว่าควรจะรับอะไรแล้วส่งอะไร (คล้ายๆ กับ mock แต่ไม่ใช่ mock)
should_receive คือ การตรวจสอบว่า method หรือ variable ที่เราคาดหวังไว้ เป็นไปตามที่เราคิดไว้หรือเปล่า(แต่จะไม่ตรวจสอบการคืนค่า)
and_return คือ การกำหนดการคืนค่า
A.stub!(:msg).and_return(:default_value)จำลองไว้ก่อนว่าถ้ารับ :msg มา จะคืนค่าเป็น :default_value
A.should_receive(:msg).with(:arg).and_return(:special_value)ตรวจสอบว่า มันควรจะได้รับ :msg พร้อมกับ :arg เป็น argument และจะคืนค่าเป็น :special_value
A.msg => :default_valueหลังจากที่มันถูก stub ไปแล้ว การเรียก method จะคืนด่ามาเป็น :default_value แต่ถ้าไม่ถูก stub มาก่อน บรรทัดนี้จะกลายเป็น method undefined ทันที
A.msg(:any_other_arg) => :default_valueเหมือนกรณีข้างบน แต่คราวนี้รับ :any_other_arg การคืนค่าจะเป็น :default_value เหมือนเดิม แต่เรา stub เพิ่มเป็น
A.should_receive(:msg).with(:any_other_arg).and_return(:any_other_value)บรรทัดนี้จะคืนค่าเป็น :any_other_value ทันที
A.msg(:arg) => :special_valueสำหรับบรรทัดนี้ จะทำให้ผลการคาดหวังเป็นจริงทันที เพราะเราคาดหวังไว้ว่า :msg ควรจะถูกเรียกพร้อมกับ :arg เป็น argument ตรงส่วนนี้แหละ ทำให้ผมงงตอนแรก เพราะดันไปเข้าใจผิดว่าเราคาดหวังการ return เข้ามาด้วย แต่จริงๆ แล้ว and_return เป็นแค่การกำหนดการคืนค่าหลังจาก method ถูกเรียก เท่านั้น
A.msg(:any_other_other_arg) => :default_value A.msg => :default_value
สองบรรทัดนี้ก็เหมือนๆ กับกรณีข้างบน
สรุปสิ่งที่ผมเข้าใจผิดก็คือ ผมพยายามจะมองให้มันกลายเป็น unit test ให้ได้ แต่ผมกลับลืมไปว่า BDD จะพิจารณาที่พฤติกรรมเท่านั้น ซึ่งตัวอย่างที่แสดงให้ดู ก็พิจารณาแค่พฤติกรรมของมันจริงๆ
แต่ถ้าจะให้มันเป็น unit test จริงๆ เราสามารถไปเขียน spec สำหรับ test ใน model_spec ครับ ถ้าเราจะพิจารณาไปที่ model หรือ ถ้าเราพิจารณาไปที่ controller ก็เขียนตรวจสอบค่าได้บน controller_spec ได้เลยเหมือนกัน(shoud equal, ==, be หลายตัวเลย) สำหรับตัวอย่าง ไว้จะทยอยเขียนครับ
อ้า pattern andreturn นี่เคยเห้น ใน JMock ของ java ก็ใช้ pattern นี้
expect(cntrDao.findOutstanding(cntrNo)).andReturn(new BigDecimal("1000.00"));
แต่ตัวนี้มันเป็น mock มันก็เลยสามารถ verify ได้ด้วยว่า มีการเรียกตาม pattern ต่างๆเหล่านี้ครบและตามลำดับจริงหรือไม่
ครับ ในกรณีนี้ ถ้าจะตรวจสอบจริงๆ ไปตรวจสอบใน model_spec ก็ได้ครับ แต่มันดูยุ่งยากไปหน่อย
งี้นี่เอง โฮ่ ๆ ๆ ๆ