การทดสอบหน่วยเพื่อทดสอบการสร้างวัตถุโดเมน


11

ฉันมีการทดสอบหน่วยซึ่งมีลักษณะดังนี้:

[Test]
public void Should_create_person()
{
     Assert.DoesNotThrow(() => new Person(Guid.NewGuid(), new DateTime(1972, 01, 01));
}

ฉันยืนยันว่ามีการสร้างวัตถุบุคคลที่นี่เช่นการตรวจสอบความถูกต้องจะไม่ล้มเหลว ตัวอย่างเช่นหาก Guid เป็นโมฆะหรือวันเดือนปีเกิดเร็วกว่า 01/01/1900 การตรวจสอบจะล้มเหลวและข้อยกเว้นจะถูกโยนทิ้ง (หมายถึงการทดสอบล้มเหลว)

ตัวสร้างมีลักษณะดังนี้:

public Person(Id id, DateTime dateOfBirth) :
        base(id)
    {
        if (dateOfBirth == null)
            throw new ArgumentNullException("Date of Birth");
        elseif (dateOfBith < new DateTime(1900,01,01)
            throw new ArgumentException("Date of Birth");
        DateOfBirth = dateOfBirth;
    }

นี่เป็นความคิดที่ดีสำหรับการทดสอบหรือไม่?

หมายเหตุ : ฉันกำลังปฏิบัติตามวิธีการแบบคลาสสิคเพื่อทดสอบหน่วยโดเมนแบบจำลองหากถือครองใด ๆ


คอนสตรัคเตอร์มีตรรกะใด ๆ ที่คุ้มค่าที่จะได้รับการยืนยันในระหว่างการเริ่มต้นหรือไม่?
Laiv

2
อย่าไปสนใจคอนสตรัคเตอร์ทดสอบเลย !!! การก่อสร้างควรตรงไปข้างหน้า คุณคาดหวังว่าจะล้มเหลวใน Guid.NewGuid () หรือตัวสร้างของ DateTime หรือไม่?
ivenxu

@Laiv โปรดดูการปรับปรุงคำถาม
w0051977

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

3
การทดสอบดูดีบันทึกสำหรับสิ่งหนึ่ง: ชื่อ Should_create_person? สิ่งที่ควรสร้างคน? Creating_person_with_valid_data_succeedsให้ชื่อมีความหมายเช่น
David Arno

คำตอบ:


18

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

หากคอนสตรัคของคุณมีลักษณะเช่นนี้:

public Person(Guid guid, DateTime dob)
{
  this.Guid = guid;
  this.Dob = dob;
}

มีจุดมากในการทดสอบว่ามันพ่น? ฉันสามารถเข้าใจพารามิเตอร์ได้หรือไม่ แต่การทดสอบของคุณค่อนข้าง overkill

อย่างไรก็ตามหากการทดสอบของคุณทำสิ่งนี้:

public Person(Guid guid, DateTime dob)
{
  if(guid == default(Guid)) throw new ArgumentException("Guid is invalid");
  if(dob == default(DateTime)) throw new ArgumentException("Dob is invalid");

  this.Guid = guid;
  this.Dob = dob;
}

จากนั้นการทดสอบของคุณจะมีความเกี่ยวข้องมากขึ้น (เมื่อคุณขว้างข้อยกเว้นบางรหัส)

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

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

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

ในตัวอย่างของคุณ:

public Person(Id id, DateTime dateOfBirth) :
        base(id)
    {
        if (dateOfBirth == null)
            throw new ArgumentNullException("Date of Birth");
        elseif (dateOfBith < new DateTime(1900,01,01)
            throw new ArgumentException("Date of Birth");
        DateOfBirth = dateOfBirth;
    }

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

TLDR

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


@Laith โปรดดูการอัปเดตคำถามของฉัน
w0051977

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

"แต่ถ้าการทดสอบของคุณทำอะไรเช่นนี้" <คุณไม่ได้หมายความว่า"ถ้าคุณคอนสตรัคทำอะไรเช่นนี้" ?
Kodos Johnson

"มีค่าบางอย่างสำหรับการทดสอบเหล่านี้" - น่าสนใจสำหรับฉันแล้วค่าจะแสดงว่าเราสามารถทำการทดสอบซ้ำซ้อนโดยใช้คลาสใหม่เพื่อเป็นตัวแทนของบุคคล (เช่นPersonBirthdate) ซึ่งทำการตรวจสอบวันเกิด ในทำนองเดียวกันการGuidตรวจสอบสามารถดำเนินการในIdชั้นเรียน ซึ่งหมายความว่าคุณไม่จำเป็นต้องมีตรรกะการตรวจสอบในตัวPersonสร้างอีกต่อไปเพราะคุณไม่สามารถสร้างหนึ่งด้วยข้อมูลที่ไม่ถูกต้อง - ยกเว้นการnullอ้างอิง แน่นอนคุณต้องมีการเขียนการทดสอบอื่น ๆ สำหรับสองชั้น :)
สตีเฟ่นเบิร์น

12

เป็นคำตอบที่ดีอยู่แล้ว แต่ฉันคิดว่าอีกสิ่งหนึ่งที่ควรค่าแก่การกล่าวถึงคือ

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

โปรดทราบว่าสำหรับ TDD คุณควรเขียนการทดสอบอื่นก่อนเช่น

  Assert.Throws<ArgumentException>(() => new Person(Guid.NewGuid(), 
        new DateTime(1572, 01, 01));

ก่อนที่จะเพิ่มการตรวจสอบDateTime(1900,01,01)ไปยังผู้สร้าง

ในบริบทของ TDD การทดสอบที่แสดงนั้นเหมาะสมอย่างสมบูรณ์


มุมที่ดีฉันจะไม่พิจารณา!
Liath

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

มันเป็นหนังสือจริงหรือ ฉันจะสร้างอินสแตนซ์และเรียกใช้วิธีการทันทีในรหัส จากนั้นฉันก็จะเขียนการทดสอบสำหรับวิธีการนั้นและโดยการทำเช่นนั้นฉันจะต้องสร้างอินสแตนซ์สำหรับวิธีการนั้นดังนั้นทั้งตัวสร้างและวิธีจะครอบคลุมในการทดสอบนั้น เว้นแต่ในตัวสร้างมีตรรกะบางอย่าง แต่ส่วนนั้นถูกปกคลุมด้วย Liath
RafałŁużyński

@ RafałŁużyński: TDD "โดยหนังสือ" เป็นเรื่องเกี่ยวกับการเขียนการทดสอบครั้งแรก จริงๆแล้วมันหมายถึงการเขียนการทดสอบที่ล้มเหลวก่อนเสมอ (ไม่ใช่การรวบรวมการนับว่าล้มเหลวเช่นกัน) ดังนั้นคุณครั้งแรกที่เขียนทดสอบโทรสร้างได้เมื่อมีคอนสตรัคไม่มี จากนั้นคุณพยายามที่จะรวบรวม (ซึ่งล้มเหลว) จากนั้นคุณใช้ตัวสร้างที่ว่างเปล่ารวบรวมรวบรวมเรียกใช้การทดสอบผล = สีเขียว จากนั้นคุณเขียนการทดสอบที่ล้มเหลวครั้งแรกและเรียกใช้ - result = red จากนั้นคุณเพิ่มฟังก์ชันการทำงานเพื่อให้การทดสอบ "สีเขียว" อีกครั้งและอื่น ๆ
Doc Brown

แน่นอน. ฉันไม่ได้หมายความว่าฉันเขียนการใช้งานก่อนแล้วจึงทดสอบ ฉันเพิ่งเขียน "การใช้งาน" ของรหัสนั้นในระดับด้านบนจากนั้นทำการทดสอบรหัสนั้นจากนั้นฉันก็นำไปใช้ ฉันกำลังทำ "Outside TDD" ตามปกติ
RafałŁużyński
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.