ฉันจะเขียนแบบทดสอบเพื่อหาวิธีบริสุทธิ์ที่ไม่ส่งคืนสิ่งใดได้อย่างไร


13

ฉันมีวิชาเรียนมากมายที่จัดการกับการตรวจสอบความถูกต้องของค่า ตัวอย่างเช่นRangeValidatorคลาสจะตรวจสอบว่าค่าอยู่ในช่วงที่ระบุหรือไม่

คลาสตัวตรวจสอบความถูกต้องมีสองวิธี: is_valid(value)ซึ่งส่งคืนTrueหรือFalseขึ้นอยู่กับค่าและensure_valid(value)ตรวจสอบค่าที่ระบุและไม่ทำอะไรเลยถ้าค่านั้นถูกต้องหรือส่งข้อยกเว้นเฉพาะถ้าค่าไม่ตรงกับกฎที่กำหนดไว้ล่วงหน้า

ขณะนี้มีการทดสอบสองหน่วยที่เกี่ยวข้องกับวิธีนี้:

  • สิ่งที่ผ่านค่าที่ไม่ถูกต้องและทำให้แน่ใจว่ามีข้อผิดพลาดเกิดขึ้น

    def test_outside_range(self):
        with self.assertRaises(demo.ValidationException):
            demo.RangeValidator(0, 100).ensure_valid(-5)
    
  • สิ่งที่ผ่านค่าที่ถูกต้อง

    def test_in_range(self):
        demo.RangeValidator(0, 100).ensure_valid(25)
    

แม้ว่าการทดสอบครั้งที่สองจะทำงาน - ล้มเหลวหากมีการโยนข้อยกเว้นและประสบความสำเร็จหากensure_validไม่ได้โยนอะไรเลย - ความจริงที่ว่าไม่มีสิ่งใดassertอยู่ข้างในดูแปลก คนที่อ่านรหัสดังกล่าวจะถามตัวเองทันทีว่าทำไมมีการทดสอบซึ่งดูเหมือนว่าไม่ได้ทำอะไรเลย

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



11
จุด Pedantic แต่ถ้าคุณมีฟังก์ชั่นที่ไม่มีการขัดแย้ง (บันทึกสำหรับการselfอ้างอิง) และส่งกลับผลลัพธ์ไม่มันไม่ใช่ฟังก์ชั่นที่บริสุทธิ์
David Arno

10
@DavidArno: นี่ไม่ใช่จุดอวดรู้มันไปทางขวาของคำถาม: วิธีทดสอบยากอย่างแม่นยำเพราะไม่บริสุทธิ์
Jörg W Mittag

@DavidArno จุดอวดความรู้เพิ่มเติมคุณสามารถมีวิธี "ไม่ทำอะไรเลย" (สมมติว่า "ไม่ส่งคืนผลลัพธ์" ถูกตีความว่าเป็น "ส่งคืนประเภทหน่วย" ซึ่งถูกเรียกvoidในหลายภาษาและมีกฎโง่ ๆ ) หรือคุณอาจมีจำนวนอนันต์ วนซ้ำ (ซึ่งทำงานได้แม้เมื่อ "ส่งคืนผลลัพธ์ไม่ได้" จริงๆหมายถึงไม่ส่งคืนผลลัพธ์
Derek Elkins ออกจาก SE

นอกจากนี้หนึ่งในฟังก์ชั่นบริสุทธิ์จากประเภทนี้unit -> unitในภาษาใด ๆ ที่จะพิจารณาหน่วยประเภทข้อมูลมาตรฐานแทนของบางสิ่งบางอย่างน่าอัศจรรย์พิเศษดาด น่าเสียดายที่มันแค่คืนค่ายูนิตและไม่ทำอะไรเลย
Phoshi

คำตอบ:


21

กรอบการทดสอบส่วนใหญ่มีการยืนยันที่ชัดเจนสำหรับ "ไม่โยน" เช่นจัสมีนexpect(() => {}).not.toThrow();และ nUnit และเพื่อนก็มีเหมือนกัน


1
และหากกรอบการทดสอบไม่ได้มีการยืนยันดังกล่าวก็เป็นไปได้ที่จะสร้างวิธีการในท้องถิ่นซึ่งทำสิ่งเดียวกันทุกประการทำให้รหัสอธิบายตนเอง ความคิดที่ดี.
Arseni Mourzenko

7

ขึ้นอยู่กับภาษาและกรอบงานที่ใช้เป็นอย่างมาก การพูดในแง่ของNUnitมีAssert.Throws(...)วิธีการ คุณสามารถส่งผ่านวิธีแลมบ์ดาได้:

Assert.Throws(() => rangeValidator.EnsureValid(-5))

Assert.Throwsซึ่งจะดำเนินการภายใน การโทรไปแลมบ์ดามักถูกห่อหุ้มด้วยtry { } catch { }บล็อกและการตรวจสอบยืนยันล้มเหลวหากมีข้อยกเว้นเกิดขึ้น

หากกรอบงานของคุณไม่มีวิธีการเหล่านี้คุณสามารถแก้ไขได้ด้วยการวางสายด้วยตัวเอง (ฉันเขียนเป็น C #):

// assert that the method does not fail
try
{
    rangeValidator.EnsureValid(50);
}
catch(Exception e)
{
    Assert.IsTrue(false, $"Exception: {e.Message}");
}

// assert that the method does fail
try
{
    rangeValidator.EnsureValid(50);
    Assert.IsTrue(false, $"Method is expected to throw an exception");
}
catch(Exception e)
{
}

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

แก้ไข

ดังที่ Doc Brown ชี้ให้เห็นในความคิดเห็นปัญหาไม่ได้ระบุว่าวิธีการโยน แต่ไม่ได้โยน ใน NUnit ยังมีการยืนยันสำหรับสิ่งนั้น

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))

1

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

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

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


1

คุณสามารถยืนยันได้ว่าวิธีการบางอย่างเรียกว่า (หรือไม่ได้เรียกว่า) อย่างถูกต้อง

ตัวอย่างเช่น

public void SendEmail(User user)
     ... construct email ...
     _emailSender.Send(email);

ในการทดสอบของคุณ:

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.