หน่วยทดสอบคลาสที่ติดตามสถานะ

ฉันกำลังสรุปส่วนการติดตามประวัติของชั้นเรียนของฉันเพื่อให้มีลักษณะดังนี้:

private readonly Stack<MyObject> _pastHistory = new Stack<MyObject>();

internal virtual Boolean IsAnyHistory { get { return _pastHistory.Any(); } }

internal virtual void AddObjectToHistory(MyObject myObject)
{
  if (myObject == null) throw new ArgumentNullException("myObject");
  _pastHistory.Push(myObject);
}

internal virtual MyObject RemoveLastObject()
{
  if(!IsAnyHistory) throw new InvalidOperationException("There is no previous history.");
  return _pastHistory.Pop();
}

ปัญหาของฉันคือฉันต้องการทดสอบหน่วยว่า Remove จะส่งคืนออบเจ็กต์ที่เพิ่มล่าสุด

  • AddObjectToHistory
  • RemoveObjectToHistory -> ส่งคืนสิ่งที่ใส่ไว้ผ่านทาง AddObjectToHistory

อย่างไรก็ตาม ถ้าฉันต้องเรียก Add ก่อนไม่ใช่ Unit Test ใช่ไหม แต่วิธีเดียวที่ฉันสามารถทำได้ในวิธีทดสอบหน่วยที่แท้จริงคือการส่งผ่านวัตถุ Stack ในตัวสร้างหรือจำลอง IsAnyHistory...แต่การเยาะเย้ย SUT ของฉันก็แปลกเช่นกัน ดังนั้น คำถามของฉันคือ จากมุมมองที่ดันทุรัง นี่เป็นการทดสอบหน่วยหรือไม่ ถ้าไม่ ฉันจะทำความสะอาดได้อย่างไร...การฉีดคอนสตรัคเตอร์เป็นวิธีเดียวของฉันเหรอ? มันดูเหมือนเป็นการยืดเวลาที่ต้องผ่านวัตถุธรรมดา ๆ ใช่ไหม? เป็นไปได้ไหมที่จะผลักแม้แต่วัตถุธรรมดา ๆ นี้ออกไปเพื่อฉีด?


person Justin Pihony    schedule 24.06.2013    source แหล่งที่มา
comment
โปรดทราบว่าหากสแต็กว่างเปล่า IsAnyHistory จะโยน InvalidOperationException คุณควรตรวจสอบ Count แทนที่จะใช้ Peek   -  person Mike Zboray    schedule 24.06.2013
comment
@mikez Hrmmm ฉันอ่านเอกสารที่นั่นผิด ขอบคุณสำหรับการจับ   -  person Justin Pihony    schedule 24.06.2013


คำตอบ (5)


มีสองแนวทางในสถานการณ์เหล่านั้น:

  1. แทรกแซงการออกแบบ เช่น การทำ _pastHistory internal/protected หรือการอัดสแต็ค
  2. ใช้วิธีการอื่น (อาจเป็นการทดสอบหน่วย) เพื่อทำการตรวจสอบ

เช่นเคย ไม่มีกฎทอง แม้ว่าโดยทั่วไปแล้วฉันจะบอกว่าคุณควรหลีกเลี่ยงสถานการณ์ที่การทดสอบหน่วยบังคับให้มีการเปลี่ยนแปลงการออกแบบ (เนื่องจากการเปลี่ยนแปลงเหล่านั้นมักจะทำให้เกิดความคลุมเครือ/คำถามที่ไม่จำเป็นแก่ผู้บริโภคโค้ด)

อย่างไรก็ตาม ในท้ายที่สุดแล้ว คุณเองที่ต้องชั่งน้ำหนักว่าคุณต้องการให้โค้ดการทดสอบหน่วยแทรกแซงการออกแบบ (กรณีแรก) มากน้อยเพียงใด หรือทำให้คำจำกัดความ การทดสอบหน่วยที่สมบูรณ์แบบ (กรณีที่สอง) เปลี่ยนไป

โดยปกติแล้ว ฉันพบว่ากรณีที่สองน่าสนใจกว่ามาก - มันไม่ทำให้โค้ดคลาสเดิมเกะกะ และคุณน่าจะทดสอบ Add แล้ว - มัน ปลอดภัยที่จะวางใจได้

person k.m    schedule 24.06.2013
comment
ฉันจะเปิดคำถามนี้ทิ้งไว้ประมาณหนึ่งวัน แต่ฉันเอนเอียงไปทางนั้นเนื่องจาก Add อยู่ระหว่างการทดสอบ และไม่ได้ขึ้นอยู่กับสถานะภายนอก ฉันแค่โกงการทดสอบที่เปราะบางหากเพิ่มการเปลี่ยนแปลงในความสามารถบางอย่าง ... แต่ฉันเดาว่าเอกสารประกอบของ Remove เป็นสิ่งที่อยู่ในแนวของ: ส่งคืนสิ่งที่เพิ่มครั้งล่าสุด .... ดังนั้นจึงเกือบจะต้องเพิ่ม ขอบคุณ! - person Justin Pihony; 24.06.2013

ฉันคิดว่ามันยังคงเป็นการทดสอบหน่วย โดยสมมติว่า MyObject เป็นวัตถุธรรมดา ฉันมักจะสร้างพารามิเตอร์อินพุตให้กับวิธีทดสอบหน่วย

ฉันใช้เกณฑ์การทดสอบหน่วยของ Michael Feather:

การทดสอบไม่ใช่การทดสอบหน่วยหาก:

  • มันคุยกับฐานข้อมูล
  • มันสื่อสารผ่านเครือข่าย
  • มันสัมผัสกับระบบไฟล์
  • ไม่สามารถทำงานพร้อมกันกับการทดสอบหน่วยอื่นๆ ของคุณได้
  • คุณต้องทำสิ่งพิเศษกับสภาพแวดล้อมของคุณ (เช่น การแก้ไขไฟล์กำหนดค่า) เพื่อรัน

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

person neontapir    schedule 24.06.2013
comment
อืม แต่ปัญหาของฉันคือฉันต้องพึ่งพาหน่วยงานอื่น (AddObjectToHistory) เพื่อทดสอบหน่วยงานจริงของฉัน สำหรับฉันแล้ว ฉันรู้สึกเหมือนเป็นการทดสอบหน่วย S มากกว่า หากการเพิ่มเปลี่ยนแปลง การทดสอบการลบของฉันล้มเหลว ดังนั้นจึงทำให้การทดสอบเปราะบางกับหน่วยอื่น...ฉันเห็นทั้งสองฝ่ายจริงๆ...แค่มองหาฉันทามติโดยรวม - person Justin Pihony; 24.06.2013
comment
นั่นไม่ชัดเจนจากคำถามของคุณ ฉันคิดว่าคุณคงต้องการที่จะฉีด Stack แทนสถานะเริ่มต้นที่คุณกำลังทดสอบ ซึ่งยังคงอยู่ภายใต้หลักเกณฑ์เหล่านี้ - person neontapir; 24.06.2013
comment
อัปเดตคำถามของฉันเพื่อชี้แจง - person Justin Pihony; 24.06.2013

2 เซ็นต์ของฉัน... ลูกค้าจะรู้ได้อย่างไรว่าการลบออกได้ผลหรือไม่ ? 'ลูกค้า' ควรโต้ตอบกับวัตถุนี้อย่างไร ลูกค้าจะผลักดันสแต็กไปยังตัวติดตามประวัติหรือไม่? ปฏิบัติต่อการทดสอบในฐานะผู้ใช้/ผู้บริโภค/ลูกค้ารายอื่นของหัวข้อทดสอบ.. โดยใช้การโต้ตอบแบบเดียวกันกับในการผลิตจริงทุกประการ ฉันไม่เคยได้ยินกฎใด ๆ ที่ระบุว่าคุณไม่ได้รับอนุญาตให้เรียกใช้หลายวิธีบนวัตถุที่อยู่ระหว่างการทดสอบ

ในการจำลอง สแต็กไม่ว่างเปล่า ฉันจะโทรหา Add - กรณี 99% ฉันจะงดเว้นจากการทำลายการห่อหุ้มของวัตถุนั้น .. ปฏิบัติต่อวัตถุเหมือนผู้คน (ฉันคิดว่าฉันอ่านสิ่งนั้นในการคิดเชิงวัตถุ) บอกให้ทำของ..ห้ามบุกรุกเข้าไป

เช่น. หากคุณต้องการให้ใครสักคนมีเงินในกระเป๋าสตางค์

  • วิธีง่ายๆ คือการให้เงินพวกเขาและปล่อยให้พวกเขาใส่เข้าไปในกระเป๋าเงินของพวกเขาเป็นการภายใน
  • ทิ้งกระเป๋าเงินของพวกเขาไปและเก็บสิ่งของไว้ในกระเป๋าเงินในกระเป๋าของพวกเขา

ฉันชอบตัวเลือกที่ 1 ดูว่ามันช่วยให้คุณเป็นอิสระจากรายละเอียดการใช้งานได้อย่างไร (ซึ่งทำให้เกิดความเปราะบางในการทดสอบ) สมมติว่าพรุ่งนี้บุคคลนั้นตัดสินใจใช้กระเป๋าเงินออนไลน์ วิธีหลังนี้จะทำให้การทดสอบของคุณพัง - จะต้องอัปเดตเพื่อพุชในกระเป๋าเงินออนไลน์ทันที - แม้ว่าพฤติกรรมของวัตถุจะไม่เสียหายก็ตาม

อีกตัวอย่างหนึ่งที่ฉันเคยเห็นคือสำหรับการทดสอบ Repository.GetX() ซึ่งผู้คนเจาะเข้าไปในฐานข้อมูลเพื่อฉีดบันทึกด้วย SQL ในตอนนี้ในการทดสอบหน่วย .. ซึ่งมันจะสะอาดกว่ามากและเรียกง่ายกว่า Repository.AddX(x ) อันดับแรก. การแยกตัวเป็นที่ต้องการแต่ไม่ถึงขนาดที่จะเอาชนะลัทธิปฏิบัตินิยม

ฉันหวังว่าฉันจะไม่แข็งแกร่งเกินไปที่นี่ .. มันทำให้ฉันเจ็บปวดเมื่อเห็นว่า API ของวัตถุถูก 'บิดเบี้ยวเพื่อการทดสอบ' จนถึงจุดที่มันไม่เหมือนกับ 'สิ่งที่ง่ายที่สุดที่สามารถทำงานได้' อีกต่อไป

person Gishu    schedule 25.06.2013
comment
ฉันเห็นด้วยกับคุณ และถ้าฉันไปฉีด ฉันอาจจะไม่ผ่านในสแต็ก แต่ผ่านในที่เก็บประเภทหนึ่งแทน...ซึ่งยังดูเหมือนเกินความจำเป็น ที่ถูกกล่าวว่านี่ค่อนข้างเหมือนกับคำตอบของจิมมี่ - person Justin Pihony; 25.06.2013
comment
โดยส่วนใหญ่ ยกเว้นว่าการทดสอบควรเลียนแบบผู้บริโภคจริงทุกประการ พวกเขาควร ไม่ ทดสอบผ่านเส้นทางที่ทำให้การทดสอบง่ายขึ้น แต่แตกต่างจากแอปในโหมดการใช้งานจริง - person Gishu; 25.06.2013

ฉันคิดว่าคุณกำลังพยายามเจาะจงมากเกินไปกับคำจำกัดความของการทดสอบหน่วย คุณควรทดสอบพฤติกรรมสาธารณะของชั้นเรียน ไม่ใช่รายละเอียดการใช้งานโดยละเอียด

จากข้อมูลโค้ดของคุณ ดูเหมือนว่าสิ่งที่คุณต้องใส่ใจจริงๆ คือ ก) การเรียก AddObjectToHistory ทำให้ IsAnyHistory คืนค่าเป็นจริงหรือไม่ และ b) RemoveLastObject จะทำให้ IsAnyHistory คืนค่าเท็จในที่สุด

person rossisdead    schedule 24.06.2013
comment
ฉันกำลังคิดที่จะเจาะจงมากเกินไป อย่างไรก็ตาม ฉันไม่เชื่อว่าตัวเองเป็นเช่นนั้นจริงๆ เพราะฉันต้องการทดสอบว่า RemoveLastObject ส่งคืนออบเจ็กต์สุดท้ายนั้น ดูเหมือนจะไม่ใช่การทดสอบที่เฉพาะเจาะจงมากเกินไป กำลังทดสอบเอาต์พุตที่ถูกต้อง - person Justin Pihony; 24.06.2013
comment
ขออภัย พลาดส่วนที่ RemoveLastObject ส่งคืนวัตถุ คุณควรทดสอบว่าตรงตามที่คาดว่าจะใช้ หากวิธีเดียวที่จะนำวัตถุเข้าสู่ประวัติคือการเรียก AddObjectToHistory คุณควรเรียกสิ่งนั้นในการทดสอบของคุณ ไม่จำเป็นต้องเพิ่มการฉีดคอนสตรัคเตอร์เว้นแต่คุณต้องการอนุญาตตั้งแต่แรก (จากนั้นต้องทดสอบหน่วยทั้งหมดนั้นด้วย) - person rossisdead; 24.06.2013

ตามที่ระบุไว้ในคำตอบอื่น ๆ ฉันคิดว่าตัวเลือกของคุณสามารถแยกย่อยได้เช่นนั้น

  1. คุณใช้วิธีการทดสอบที่ไร้เหตุผล และเพิ่ม Constructor Inject สำหรับ Stack Object เพื่อให้คุณสามารถ Inject Stack Object ปลอมของคุณเองและทดสอบวิธีการของคุณได้

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

person Eogcloud    schedule 24.06.2013