ฉันยังใหม่กับการทดสอบหน่วยและฉันได้ยินคำว่า 'วัตถุจำลอง' อย่างต่อเนื่องโยนไปมามาก ในแง่ของคนธรรมดาคนสามารถอธิบายว่าอะไรคือวัตถุจำลองและสิ่งที่พวกเขามักจะใช้เมื่อเขียนการทดสอบหน่วย?
ฉันยังใหม่กับการทดสอบหน่วยและฉันได้ยินคำว่า 'วัตถุจำลอง' อย่างต่อเนื่องโยนไปมามาก ในแง่ของคนธรรมดาคนสามารถอธิบายว่าอะไรคือวัตถุจำลองและสิ่งที่พวกเขามักจะใช้เมื่อเขียนการทดสอบหน่วย?
คำตอบ:
เนื่องจากคุณบอกว่าคุณยังใหม่ต่อการทดสอบหน่วยและขอวัตถุจำลองใน "เงื่อนไขของคนธรรมดา" ฉันจะลองตัวอย่างของคนธรรมดา
ลองนึกภาพการทดสอบหน่วยสำหรับระบบนี้:
cook <- waiter <- customer
โดยทั่วไปแล้วจะง่ายต่อการมองเห็นการทดสอบส่วนประกอบระดับต่ำเช่นcook
:
cook <- test driver
โปรแกรมควบคุมการทดสอบเพียงแค่สั่งอาหารที่แตกต่างกันและตรวจสอบการปรุงอาหารส่งกลับจานที่ถูกต้องสำหรับการสั่งซื้อแต่ละครั้ง
มันยากที่จะทดสอบองค์ประกอบกลางเช่นบริกรที่ใช้พฤติกรรมของส่วนประกอบอื่น ๆ เครื่องมือทดสอบที่ไร้เดียงสาอาจทดสอบองค์ประกอบของบริกรในลักษณะเดียวกับที่เราทดสอบอุปกรณ์ประกอบอาหาร
cook <- waiter <- test driver
ผู้ทดสอบจะสั่งอาหารจานต่าง ๆ และตรวจสอบให้แน่ใจว่าพนักงานเสิร์ฟส่งกลับจานที่ถูกต้อง แต่น่าเสียดายที่นั่นหมายความว่าการทดสอบส่วนประกอบบริกรนี้อาจขึ้นอยู่กับพฤติกรรมที่ถูกต้องขององค์ประกอบปรุงอาหาร การพึ่งพานี้ยิ่งแย่กว่ากันถ้าองค์ประกอบการปรุงอาหารมีลักษณะการทดสอบที่ไม่เป็นมิตรใด ๆ เช่นพฤติกรรมที่ไม่ได้กำหนดไว้ (เมนูรวมถึงความประหลาดใจของเชฟในฐานะที่เป็นจาน) การพึ่งพาจำนวนมาก แหล่งข้อมูล (อาหารบางอย่างต้องใช้ส่วนผสมที่มีราคาแพงหรือใช้เวลาทำอาหารหนึ่งชั่วโมง)
เนื่องจากนี่คือการทดสอบบริกรเราจึงต้องการทดสอบเพียงบริกรไม่ใช่ผู้ปรุง โดยเฉพาะเราต้องการตรวจสอบให้แน่ใจว่าพนักงานเสิร์ฟบ่งบอกถึงคำสั่งของลูกค้าในการปรุงอาหารอย่างถูกต้องและส่งมอบอาหารให้กับลูกค้าอย่างถูกต้อง
หน่วยทดสอบวิธีการทดสอบหน่วยอิสระดังนั้นวิธีการที่ดีกว่าจะแยกองค์ประกอบภายใต้การทดสอบ (บริกร) โดยใช้สิ่งที่เป็นฟาวเลอร์เรียกคู่ทดสอบ (หุ่นต้นขั้วปลอม mocks)
-----------------------
| |
v |
test cook <- waiter <- test driver
ที่นี่การทำอาหารทดสอบคือ "in cahoots" พร้อมกับโปรแกรมควบคุมการทดสอบ ระบบที่อยู่ภายใต้การทดสอบได้รับการออกแบบมาเพื่อให้ผู้ปรุงอาหารสามารถแทนที่ ( ฉีด ) เพื่อทำงานกับบริกรโดยไม่ต้องเปลี่ยนรหัสการผลิต (เช่นโดยไม่ต้องเปลี่ยนรหัสพนักงาน)
ตอนนี้ตัวทดสอบการทดสอบ (ทดสอบสองครั้ง) สามารถใช้งานได้หลายวิธี:
ดูบทความของ Fowler สำหรับข้อมูลเฉพาะเพิ่มเติมเกี่ยวกับ fakes vs stubs vs mocks vs dummiesแต่ตอนนี้เรามามุ่งเน้นไปที่การทำอาหารจำลอง
-----------------------
| |
v |
mock cook <- waiter <- test driver
ส่วนใหญ่ของการทดสอบหน่วยส่วนประกอบของบริกรเน้นที่วิธีที่พนักงานเสิร์ฟโต้ตอบกับองค์ประกอบของอาหาร วิธีการแบบจำลองจะเน้นไปที่การระบุอย่างสมบูรณ์ว่าการโต้ตอบที่ถูกต้องคืออะไรและตรวจจับได้เมื่อมันผิดพลาด
วัตถุจำลองรู้ล่วงหน้าว่าควรจะเกิดอะไรขึ้นในระหว่างการทดสอบ (เช่นการเรียกวิธีการใดที่จะถูกเรียกใช้ ฯลฯ ) และวัตถุจำลองรู้ว่ามันควรจะตอบสนองอย่างไร (เช่นค่าตอบแทนที่จะให้) จำลองจะระบุว่าสิ่งที่เกิดขึ้นจริงแตกต่างจากสิ่งที่ควรจะเกิดขึ้น วัตถุจำลองที่กำหนดเองสามารถสร้างขึ้นได้จากศูนย์สำหรับแต่ละกรณีทดสอบเพื่อดำเนินการตามพฤติกรรมที่คาดไว้สำหรับกรณีทดสอบนั้น แต่กรอบการเยาะเย้ยนั้นมุ่งมั่นที่จะอนุญาตให้มีการระบุพฤติกรรมอย่างชัดเจนและง่ายดายในกรณีทดสอบโดยตรง
การสนทนารอบ ๆ การทดสอบตามแบบจำลองอาจมีลักษณะเช่นนี้:
คนขับทดสอบเพื่อจำลองการปรุงอาหาร : คาดหวังว่าจะสั่งฮอทดอกและให้ฮอทดอกตัวนี้ในการตอบสนอง
ขับทดสอบ (วางตัวเป็นลูกค้า) เพื่อบริกร : ผมอยากสุนัขร้อนกรุณา
บริกรที่จะเยาะเย้ยปรุงอาหาร : 1 สุนัขร้อนโปรด
ปรุงอาหารจำลองที่จะบริกร : เพื่อขึ้น: 1 สุนัขร้อนพร้อม (ให้สุนัขร้อนหุ่นบริกร)
บริกรในการขับทดสอบ : นี่คือฮอทดอกของคุณ (มอบฮอทด็อกฮ็อตเพื่อทดสอบไดรเวอร์)ไดรเวอร์ทดสอบ : ทดสอบสำเร็จแล้ว!
แต่เนื่องจากบริกรของเราเป็นของใหม่นี่คือสิ่งที่อาจเกิดขึ้น:
คนขับทดสอบเพื่อจำลองการปรุงอาหาร : คาดหวังว่าจะสั่งฮอทดอกและให้ฮอทดอกตัวนี้ในการตอบสนอง
คนขับทดสอบ (วางตัวเป็นลูกค้า) ถึงพนักงานเสิร์ฟ : ฉันต้องการสุนัขร้อนโปรดให้
พนักงานเยาะเย้ยปรุงอาหาร : 1 แฮมเบอร์เกอร์โปรด
ล้อเลียนแม่ครัวหยุดทำการทดสอบ: ฉันถูกสั่งให้คาดหวังว่าจะสั่งสุนัขร้อน!คนขับทดสอบบันทึกปัญหา: การทดสอบล้มเหลว! - บริกรเปลี่ยนคำสั่ง
หรือ
คนขับทดสอบเพื่อจำลองการปรุงอาหาร : คาดหวังว่าจะสั่งฮอทดอกและให้ฮอทดอกตัวนี้ในการตอบสนอง
คนขับทดสอบ (วางตัวเป็นลูกค้า) ถึงบริกร : ฉันต้องการฮ็อตด็อก
บริกรที่จะเยาะเย้ยปรุงอาหาร : 1 สุนัขร้อนโปรด
ปรุงอาหารจำลองที่จะบริกร : เพื่อขึ้น: 1 สุนัขร้อนพร้อม (ให้สุนัขร้อนหุ่นบริกร)
บริกรในการขับทดสอบ : นี่คือเฟรนช์ฟรายส์ของคุณ (ให้เฟรนช์ฟรายส์จากคำสั่งอื่น ๆ เพื่อทดสอบไดรเวอร์)คนขับรถทดสอบบันทึกเฟรนช์ฟรายส์ที่ไม่คาดคิด: การทดสอบล้มเหลว! บริกรให้อาหารผิดจานคืน
มันอาจจะยากที่จะเห็นความแตกต่างระหว่างวัตถุจำลองและต้นขั้วอย่างชัดเจนโดยไม่มีตัวอย่างที่แตกต่างจากต้นขั้วไปกับสิ่งนี้ แต่คำตอบนี้ยาวเกินไปแล้ว :-)
นอกจากนี้โปรดทราบว่านี่เป็นตัวอย่างที่ค่อนข้างง่ายและกรอบการเยาะเย้ยช่วยให้คุณสมบัติที่ซับซ้อนของพฤติกรรมที่คาดหวังจากส่วนประกอบเพื่อรองรับการทดสอบที่ครอบคลุม มีเนื้อหามากมายเกี่ยวกับวัตถุจำลองและกรอบการจำลองสำหรับข้อมูลเพิ่มเติม
วัตถุจำลองเป็นวัตถุที่ใช้แทนวัตถุจริง ในการเขียนโปรแกรมเชิงวัตถุวัตถุจำลองเป็นวัตถุจำลองที่เลียนแบบพฤติกรรมของวัตถุจริงด้วยวิธีการควบคุม
โดยทั่วไปแล้วโปรแกรมเมอร์คอมพิวเตอร์จะสร้างวัตถุจำลองเพื่อทดสอบพฤติกรรมของวัตถุอื่น ๆ ในลักษณะเดียวกับที่นักออกแบบรถยนต์ใช้จำลองการทดสอบการชนเพื่อจำลองพฤติกรรมแบบไดนามิกของมนุษย์ในผลกระทบของยานพาหนะ
http://en.wikipedia.org/wiki/Mock_object
วัตถุจำลองช่วยให้คุณสามารถตั้งค่าสถานการณ์จำลองการทดสอบโดยไม่ต้องแบกรับทรัพยากรที่ใหญ่และไม่สะดวกเช่นฐานข้อมูล แทนที่จะเรียกฐานข้อมูลสำหรับการทดสอบคุณสามารถจำลองฐานข้อมูลของคุณโดยใช้วัตถุจำลองในการทดสอบหน่วยของคุณ สิ่งนี้ทำให้คุณปลอดภาระในการตั้งค่าและทำลายฐานข้อมูลจริงเพียงทดสอบวิธีเดียวในชั้นเรียนของคุณ
คำว่า "เยาะเย้ย" บางครั้งใช้แทนกันอย่างผิดพลาดกับ "ต้นขั้ว" ความแตกต่างระหว่างคำสองคำนี้อธิบายไว้ที่นี่ โดยพื้นฐานแล้วการเยาะเย้ยเป็นวัตถุต้นขั้วที่รวมถึงความคาดหวัง (เช่น "การยืนยัน") สำหรับพฤติกรรมที่เหมาะสมของวัตถุ / วิธีการภายใต้การทดสอบ
ตัวอย่างเช่น:
class OrderInteractionTester...
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
Mock warehouse = mock(Warehouse.class);
Mock mailer = mock(MailService.class);
order.setMailer((MailService) mailer.proxy());
mailer.expects(once()).method("send");
warehouse.expects(once()).method("hasInventory")
.withAnyArguments()
.will(returnValue(false));
order.fill((Warehouse) warehouse.proxy());
}
}
ขอให้สังเกตว่าวัตถุwarehouse
และmailer
จำลองจะถูกโปรแกรมด้วยผลลัพธ์ที่คาดหวัง
วัตถุจำลองเป็นวัตถุจำลองที่เลียนแบบพฤติกรรมของวัตถุจริง โดยทั่วไปคุณเขียนวัตถุจำลองหาก:
จำลองวัตถุเป็นหนึ่งในชนิดของการทดสอบคู่ คุณกำลังใช้ mockobjects เพื่อทดสอบและตรวจสอบโปรโตคอล / การโต้ตอบของคลาสที่อยู่ภายใต้การทดสอบกับคลาสอื่น ๆ
โดยทั่วไปแล้วคุณจะคาดหวัง 'โปรแกรม' หรือ 'บันทึก': วิธีการโทรคุณคาดว่าชั้นของคุณจะทำกับวัตถุต้นแบบ
ตัวอย่างเช่นเรากำลังทดสอบวิธีการบริการเพื่ออัปเดตฟิลด์ในวิดเจ็ต และในสถาปัตยกรรมของคุณมี WidgetDAO ซึ่งเกี่ยวข้องกับฐานข้อมูล การพูดกับฐานข้อมูลช้าและตั้งค่าและทำความสะอาดหลังจากนั้นซับซ้อนดังนั้นเราจะเยาะเย้ย WidgetDao
ลองคิดว่าบริการต้องทำอย่างไร: ควรได้รับ Widget จากฐานข้อมูลทำอะไรกับมันแล้วบันทึกอีกครั้ง
ดังนั้นในภาษาหลอกด้วยห้องสมุดหลอกจำลองเราจะมีสิ่งที่ชอบ:
Widget sampleWidget = new Widget();
WidgetDao mock = createMock(WidgetDao.class);
WidgetService svc = new WidgetService(mock);
// record expected calls on the dao
expect(mock.getById(id)).andReturn(sampleWidget);
expect(mock.save(sampleWidget);
// turn the dao in replay mode
replay(mock);
svc.updateWidgetPrice(id,newPrice);
verify(mock); // verify the expected calls were made
assertEquals(newPrice,sampleWidget.getPrice());
ด้วยวิธีนี้เราสามารถทดสอบการพัฒนาคลาสได้อย่างง่ายดายซึ่งขึ้นอยู่กับคลาสอื่น ๆ
ฉันขอแนะนำบทความที่ดีโดย Martin Fowlerอธิบายว่า mocks คืออะไรและแตกต่างจาก stubs อย่างไร
เมื่อหน่วยทดสอบบางส่วนของโปรแกรมคอมพิวเตอร์คุณต้องการทดสอบพฤติกรรมของส่วนนั้นโดยเฉพาะ
ตัวอย่างเช่นดูรหัสหลอกด้านล่างจากชิ้นส่วนจินตภาพของโปรแกรมที่ใช้โปรแกรมอื่นเพื่อเรียกใช้งานการพิมพ์:
If theUserIsFred then
Call Printer(HelloFred)
Else
Call Printer(YouAreNotFred)
End
หากคุณกำลังทดสอบสิ่งนี้คุณจะต้องการทดสอบส่วนที่ดูว่าผู้ใช้เป็น Fred หรือไม่ คุณไม่ต้องการทดสอบPrinter
สิ่งต่าง ๆ นั่นจะเป็นการทดสอบอีกครั้ง
นี่คือสิ่งที่วัตถุจำลองเข้ามาพวกเขาแกล้งทำเป็นประเภทอื่น ในกรณีนี้คุณจะใช้ Mock Printer
เพื่อให้มันทำหน้าที่เหมือนเครื่องพิมพ์จริง แต่จะไม่ทำสิ่งที่ไม่สะดวกเช่นการพิมพ์
มีวัตถุแกล้งประเภทอื่น ๆ อีกหลายชนิดที่คุณสามารถใช้ที่ไม่ใช่ Mocks สิ่งสำคัญที่ทำให้ Mocks Mocks คือพวกเขาสามารถกำหนดค่ากับพฤติกรรมและความคาดหวัง
ความคาดหวังอนุญาตให้เยาะเย้ยของคุณเพื่อเพิ่มข้อผิดพลาดเมื่อมีการใช้อย่างไม่ถูกต้อง ดังนั้นในตัวอย่างข้างต้นคุณอาจต้องการให้แน่ใจว่าเครื่องพิมพ์นั้นถูกเรียกใช้ด้วย HelloFred ในกรณีทดสอบ "user is Fred" หากนั่นไม่เกิดขึ้นเยาะเย้ยของคุณสามารถเตือนคุณ
พฤติกรรมใน Mocksหมายถึงตัวอย่างเช่นรหัสของคุณทำสิ่งที่ชอบ:
If Call Printer(HelloFred) Returned SaidHello Then
Do Something
End
ตอนนี้คุณต้องการทดสอบว่าโค้ดของคุณทำอะไรเมื่อเรียกใช้เครื่องพิมพ์และส่งคืน SaidHello ดังนั้นคุณสามารถตั้งค่า Mock เพื่อส่งคืน SaidHello เมื่อเรียกใช้ด้วย HelloFred
แหล่งข้อมูลที่ดีแห่งหนึ่งคือ Martin Fowlers โพสต์Mocks Ar ไม่ใช่ Stubs
วัตถุจำลองและต้นขั้วเป็นส่วนสำคัญของการทดสอบหน่วย ในความเป็นจริงพวกเขาไปไกลเพื่อให้แน่ใจว่าคุณกำลังทดสอบหน่วยมากกว่ากลุ่มของหน่วย
โดยสรุปคุณใช้สตับเพื่อหยุดการพึ่งพา (ระบบภายใต้การทดสอบ) ของ SUT บนวัตถุและ mocks อื่น ๆ เพื่อทำสิ่งนั้นและตรวจสอบว่า SUT เรียกวิธีการ / คุณสมบัติบางอย่างของการพึ่งพา สิ่งนี้กลับไปสู่หลักการพื้นฐานของการทดสอบหน่วย - การทดสอบควรอ่านง่ายรวดเร็วและไม่ต้องการการกำหนดค่าซึ่งการใช้คลาสจริงทั้งหมดอาจบ่งบอกถึง
โดยทั่วไปคุณสามารถมีสตับมากกว่าหนึ่งในการทดสอบของคุณ แต่คุณควรมีเพียงหนึ่งเยาะเย้ย นี่เป็นเพราะวัตถุประสงค์ของการเยาะเย้ยคือการตรวจสอบพฤติกรรมและการทดสอบของคุณควรทดสอบสิ่งเดียวเท่านั้น
สถานการณ์ง่าย ๆ โดยใช้ C # และ Moq:
public interface IInput {
object Read();
}
public interface IOutput {
void Write(object data);
}
class SUT {
IInput input;
IOutput output;
public SUT (IInput input, IOutput output) {
this.input = input;
this.output = output;
}
void ReadAndWrite() {
var data = input.Read();
output.Write(data);
}
}
[TestMethod]
public void ReadAndWriteShouldWriteSameObjectAsRead() {
//we want to verify that SUT writes to the output interface
//input is a stub, since we don't record any expectations
Mock<IInput> input = new Mock<IInput>();
//output is a mock, because we want to verify some behavior on it.
Mock<IOutput> output = new Mock<IOutput>();
var data = new object();
input.Setup(i=>i.Read()).Returns(data);
var sut = new SUT(input.Object, output.Object);
//calling verify on a mock object makes the object a mock, with respect to method being verified.
output.Verify(o=>o.Write(data));
}
ในตัวอย่างข้างต้นฉันใช้ Moq เพื่อสาธิต stubs และ mocks Moq ใช้คลาสเดียวกันสำหรับทั้งคู่ - Mock<T>
ซึ่งทำให้สับสนเล็กน้อย ไม่ว่า ณ รันไทม์การทดสอบจะล้มเหลวหากoutput.Write
ไม่ได้รับการเรียกด้วยข้อมูลในparameter
ขณะที่ความล้มเหลวในการโทรinput.Read()
จะไม่ล้มเหลว
เป็นคำตอบอื่นที่แนะนำผ่านลิงก์ไปยัง " Mocks Ar ไม่ใช่ Stubs " mocks เป็นรูปแบบของ "test double" เพื่อใช้แทนวัตถุจริง สิ่งที่ทำให้พวกเขาแตกต่างจากรูปแบบอื่น ๆ ของการทดสอบคู่เช่นวัตถุต้นขั้วคือการทดสอบอื่น ๆ คู่เสนอการตรวจสอบสถานะ (และการจำลองทางเลือก) ในขณะที่ mocks เสนอการตรวจสอบพฤติกรรม
ด้วย stub คุณอาจเรียกวิธีการต่าง ๆ บน stub ในลำดับใด ๆ (หรือแม้แต่ repitiously) และตรวจสอบความสำเร็จถ้า stub ได้บันทึกค่าหรือสถานะที่คุณต้องการ ในทางตรงกันข้ามวัตถุจำลองคาดหวังว่าฟังก์ชั่นที่เฉพาะเจาะจงมากจะถูกเรียกในลำดับที่เฉพาะเจาะจงและแม้กระทั่งจำนวนครั้งที่เฉพาะเจาะจง การทดสอบด้วยวัตถุจำลองจะถูกพิจารณาว่า "ล้มเหลว" เพียงเพราะวิธีการที่ถูกเรียกใช้ในลำดับที่แตกต่างกันหรือนับ - แม้ว่าวัตถุจำลองจะมีสถานะที่ถูกต้องเมื่อการทดสอบสรุป!
ด้วยวิธีนี้วัตถุจำลองมักถูกพิจารณาว่าเป็นรหัสคู่กับ SUT ที่แน่นกว่าวัตถุที่ไม่สมบูรณ์ นั่นอาจเป็นสิ่งที่ดีหรือไม่ดีขึ้นอยู่กับสิ่งที่คุณพยายามตรวจสอบ
ส่วนหนึ่งของการใช้วัตถุจำลองคือพวกเขาไม่จำเป็นต้องนำไปใช้จริงตามข้อมูลจำเพาะ พวกเขาสามารถตอบสนองแบบจำลองได้ เช่นถ้าคุณต้องใช้ส่วนประกอบ A และ B และ "การโทร" (โต้ตอบกับ) ซึ่งกันและกันคุณจะไม่สามารถทดสอบ A จนกว่าจะมีการใช้งาน B และในทางกลับกัน ในการพัฒนาที่ขับเคลื่อนด้วยการทดสอบนี่เป็นปัญหา ดังนั้นคุณจึงสร้างวัตถุจำลอง ("จำลอง") สำหรับ A และ B ที่ง่ายมาก แต่จะให้การตอบสนองบางอย่างเมื่อพวกเขาโต้ตอบ ด้วยวิธีนี้คุณสามารถนำไปใช้และทดสอบ A โดยใช้วัตถุจำลองสำหรับ B
สำหรับ php และ phunit มีการอธิบายอย่างดีในเอกสาร phpunit ดูเอกสาร phpunitที่นี่
ในคำที่จำลองวัตถุอย่างง่ายเป็นเพียงวัตถุจำลองของวัตถุดั้งเดิมของคุณและส่งคืนค่าส่งคืนค่าส่งคืนนี้สามารถใช้ในชั้นทดสอบได้
มันเป็นหนึ่งในมุมมองหลักของการทดสอบหน่วย ใช่คุณกำลังพยายามทดสอบรหัสหน่วยเดียวและผลการทดสอบของคุณไม่ควรเกี่ยวข้องกับพฤติกรรมของถั่วหรือวัตถุอื่น ๆ ดังนั้นคุณควรเยาะเย้ยพวกเขาโดยใช้วัตถุจำลองที่มีการตอบสนองที่สอดคล้องกันง่ายขึ้น