วิธีทดสอบหน่วยวิธีการที่ส่งคืนคอลเลกชันในขณะที่หลีกเลี่ยงตรรกะในการทดสอบ


14

ฉันกำลังทดสอบวิธีการในการสร้างคอลเลกชันของวัตถุข้อมูล ฉันต้องการตรวจสอบว่ามีการตั้งค่าคุณสมบัติของวัตถุอย่างถูกต้อง คุณสมบัติบางอย่างจะถูกตั้งค่าเป็นสิ่งเดียวกัน อื่น ๆ จะถูกกำหนดเป็นค่าซึ่งขึ้นอยู่กับตำแหน่งของพวกเขาในคอลเลกชัน วิธีที่เป็นธรรมชาติในการทำสิ่งนี้ดูเหมือนจะเป็นแบบวนซ้ำ อย่างไรก็ตาม Roy Osherove แนะนำอย่างยิ่งต่อการใช้ตรรกะในการทดสอบหน่วย ( Art of Unit Testing , 178) เขาพูดว่า:

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

การทดสอบควรเป็นชุดของการเรียกใช้เมธอดโดยไม่มีโฟลว์ควบคุมไม่เท่าtry-catchกันและด้วยการเรียกยืนยัน

อย่างไรก็ตามฉันไม่เห็นสิ่งผิดปกติในการออกแบบของฉัน (คุณจะสร้างรายการของวัตถุข้อมูลได้อย่างไรค่าบางค่าขึ้นอยู่กับว่าอยู่ในลำดับที่เป็นอย่างไร - ไม่สามารถสร้างและทดสอบแยกต่างหากได้อย่างแน่นอน มีบางสิ่งที่ไม่เป็นมิตรกับการออกแบบของฉันหรือไม่? หรือการอุทิศตนอย่างจริงจังกับคำสอนของ Osherove เกินไป? หรือมีเวทย์มนตร์ทดสอบหน่วยความลับบางอย่างที่ฉันไม่รู้เกี่ยวกับการหลีกเลี่ยงปัญหานี้หรือไม่? (ฉันกำลังเขียนใน C # / VS2010 / NUnit แต่มองหาคำตอบที่ไม่เชื่อเรื่องภาษาถ้าเป็นไปได้)


4
ฉันไม่แนะนำให้วนลูป หากการทดสอบของคุณคือสิ่งที่สามมีบาร์ตั้งค่าเป็น Frob จากนั้นเขียนการทดสอบเพื่อตรวจสอบโดยเฉพาะว่าบาร์ของสิ่งที่สามคือ Frob นั่นคือหนึ่งการทดสอบด้วยตัวเองตรงไปไม่มีห่วง หากการทดสอบของคุณคือคุณได้รับชุด 5 สิ่งนั่นคือการทดสอบหนึ่งรายการ ไม่ได้บอกว่าคุณไม่เคยมีลูป (ชัดเจนหรือเป็นอย่างอื่น) เป็นเพียงการที่คุณไม่จำเป็นต้องทำบ่อยครั้ง นอกจากนี้ให้ถือว่าหนังสือของ Osherove เป็นแนวทางมากกว่ากฎจริง
Anthony Pegram

1
@AnthonyPegram เซตไม่มีการเรียงลำดับ - Frob บางครั้งอาจเป็นอันดับ 3 และบางครั้งอาจเป็นอันดับ 2 คุณไม่สามารถพึ่งพามันได้ทำให้จำเป็นต้องมีการวนซ้ำ (หรือคุณสมบัติภาษาเช่น Python in) หากการทดสอบคือ "Frob ถูกเพิ่มไปยังคอลเลกชันที่มีอยู่แล้ว"
Izkata

1
@Izbata คำถามของเขาโดยเฉพาะกล่าวว่าการสั่งซื้อเป็นสิ่งสำคัญ คำพูดของเขา: "คนอื่นจะถูกตั้งค่าเป็นค่าซึ่งขึ้นอยู่กับตำแหน่งของพวกเขาในคอลเลกชัน" มีประเภทคอลเลกชันมากมายใน C # (ภาษาที่เขาอ้างถึง) ที่เรียงลำดับไว้ สำหรับเรื่องนั้นคุณสามารถพึ่งพารายการใน Python ซึ่งเป็นภาษาที่คุณพูดถึง
Anthony Pegram

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

ฉันจะไม่ตอบด้วยวิธีนี้เพราะจะทำให้ฉันมีคะแนนโหวตลดลงถึงพันล้าน แต่ฉันมักจะเป็นแค่toString()ชุดสะสมและเปรียบเทียบกับสิ่งที่ควรจะเป็น ง่ายและใช้งานได้
949300

คำตอบ:


16

TL; DR:

  • เขียนแบบทดสอบ
  • หากการทดสอบทำมากเกินไปรหัสอาจทำมากเกินไป
  • อาจไม่ใช่การทดสอบหน่วย (แต่ไม่ใช่การทดสอบที่ไม่ดี)

สิ่งแรกสำหรับการทดสอบคือความเชื่อที่ไม่ช่วยเหลือ ฉันสนุกกับการอ่านThe Way of Testivusซึ่งชี้ให้เห็นปัญหาบางอย่างกับความเชื่อในทางที่เบิกบานใจ

เขียนแบบทดสอบที่จำเป็นต้องเขียน

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

ฉันจะชี้ไปที่บิตในการทดสอบที่น่าเกลียด:

เมื่อรหัสเป็นที่น่าเกลียดการทดสอบอาจจะน่าเกลียด

คุณไม่ชอบเขียนแบบทดสอบที่น่าเกลียด แต่โค้ดที่น่าเกลียดนั้นต้องการการทดสอบมากที่สุด

อย่าปล่อยให้รหัสที่น่าเกลียดไม่ให้คุณเขียนการทดสอบ แต่การปล่อยให้โค้ดที่น่าเกลียดนั้นไม่สามารถเขียนได้มากขึ้น

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


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

คุณควรจะสามารถออกกำลังกายรหัสทั้งหมดที่มีความคุ้มครองที่เหมาะสมผ่านการทดสอบง่ายๆ การทดสอบที่สิ้นสุดการทดสอบมากขึ้นที่จัดการกับคำถามที่มีขนาดใหญ่กว่า ("ฉันมีออบเจ็กต์นี้ถูกรวบรวมเป็น xml ส่งไปยังเว็บเซอร์วิสผ่านกฎกติกากลับออกมาและ unmarshalled") เป็นการทดสอบที่ยอดเยี่ยม - แต่แน่นอนว่าไม่ใช่ ไม่ใช่การทดสอบหน่วย (และอยู่ในขอบเขตการทดสอบการรวม - แม้ว่าจะมีบริการที่ล้อเลียนและเรียกใช้และกำหนดเองในฐานข้อมูลหน่วยความจำเพื่อทำการทดสอบ) มันอาจยังคงใช้เฟรมเวิร์ก XUnit สำหรับการทดสอบ แต่กรอบการทดสอบไม่ได้ทำให้เป็นการทดสอบหน่วย


7

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

ฉันพูดในคำถามเดิม

อย่างไรก็ตามฉันไม่เห็นสิ่งผิดปกติกับการออกแบบของฉัน (คุณสร้างรายการของวัตถุข้อมูลได้อย่างไรค่าบางค่าขึ้นอยู่กับว่าเรียงลำดับอยู่ที่ไหน - ไม่สามารถสร้างและทดสอบแยกต่างหากได้อย่างแน่นอน)

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

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

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

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


4

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

  1. หากคอลเลกชันมีความยาวคงที่ให้เขียนการทดสอบหน่วยเพื่อตรวจสอบความยาว หากเป็นความยาวผันแปรให้เขียนการทดสอบหลายอย่างสำหรับความยาวที่จะบอกลักษณะพฤติกรรมของมัน (เช่น 0, 1, 3, 10) ไม่ควรตรวจสอบคุณสมบัติในการทดสอบเหล่านี้
  2. เขียนการทดสอบหน่วยเพื่อตรวจสอบคุณสมบัติแต่ละอย่าง หากคอลเลกชันมีความยาวคงที่และสั้นเพียงแค่ยืนยันคุณสมบัติหนึ่งของแต่ละองค์ประกอบสำหรับการทดสอบแต่ละครั้ง ถ้ามันมีความยาวคงที่ แต่มีความยาวให้เลือกตัวแทน แต่ตัวอย่างเล็ก ๆ ขององค์ประกอบเพื่อยืนยันกับคุณสมบัติแต่ละรายการ หากเป็นความยาวผันแปรให้สร้างคอลเลกชันที่ค่อนข้างสั้น แต่เป็นตัวแทน (อาจจะเป็นสามองค์ประกอบ) และยืนยันกับคุณสมบัติหนึ่งรายการของแต่ละรายการ

การทำแบบนี้จะทำให้การทดสอบมีขนาดเล็กพอที่จะทำให้เกิดการวนซ้ำไม่เจ็บปวด ตัวอย่าง C # / หน่วยวิธีการที่กำหนดภายใต้การทดสอบICollection<Foo> generateFoos(uint numberOfFoos):

[Test]
void generate_zero_foos_returns_empty_list() { ... }
void generate_one_foo_returns_list_of_one_foo() { ... }
void generate_three_foos_returns_list_of_three_foos() { ... }
void generated_foos_have_sequential_ID()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("ID1", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID2", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID3", foos.Current.id);
}
void generated_foos_have_bar()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
}

หากคุณคุ้นเคยกับกระบวนทัศน์ "การทดสอบหน่วยแบน" (ไม่มีโครงสร้าง / ตรรกะซ้อนกัน) การทดสอบเหล่านี้ดูเหมือนจะสะอาดดี ดังนั้นตรรกะจึงถูกหลีกเลี่ยงในการทดสอบโดยการระบุปัญหาดั้งเดิมว่าพยายามทดสอบคุณสมบัติมากเกินไปในคราวเดียวแทนที่จะขาดลูป


1
Osherove จะมีหัวของคุณบนแผ่นเสียงสำหรับการมี 3 asserts ;) คนแรกที่ล้มเหลวหมายความว่าคุณไม่เคยตรวจสอบส่วนที่เหลือ โปรดทราบว่าคุณไม่ได้หลีกเลี่ยงการวนซ้ำ คุณเพิ่งขยายออกไปอย่างชัดเจนในแบบฟอร์มที่ดำเนินการ ไม่ใช่การวิพากษ์วิจารณ์อย่างหนัก แต่เป็นเพียงข้อเสนอแนะเพื่อให้ฝึกฝนการแยกกรณีทดสอบของคุณให้มากที่สุดเท่าที่จะเป็นไปได้เพื่อให้ข้อเสนอแนะที่เฉพาะเจาะจงกับตัวเองมากขึ้นเมื่อมีอะไรบางอย่างล้มเหลว การตอบรับเฉพาะของตนเอง)
Anthony Pegram

3
@ AnthonyPegram ฉันรู้เกี่ยวกับกระบวนทัศน์หนึ่งยืนยันต่อการทดสอบ ฉันชอบมนต์ "test one things" (ตามที่บ็อบมาร์ตินสนับสนุนโดยคัดค้านหนึ่งข้อต่อการทดสอบในClean Code ) หมายเหตุด้านข้าง: กรอบการทดสอบหน่วยที่มี "คาดหวัง" กับ "ยืนยัน" เป็นสิ่งที่ดี (การทดสอบของ Google) ส่วนที่เหลือทำไมคุณไม่แยกคำแนะนำของคุณออกเป็นคำตอบแบบเต็มพร้อมตัวอย่าง? ฉันคิดว่าฉันสามารถได้รับประโยชน์
Kazark
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.