ตกลงหรือไม่ที่จะมีการยืนยันหลายชุดในการทดสอบหน่วยเดียว?


397

ในความคิดเห็นต่อโพสต์ที่ยอดเยี่ยมนี้ Roy Osherove ได้กล่าวถึงโครงการOAPTที่ออกแบบมาเพื่อใช้ในการทดสอบแต่ละครั้งในการทดสอบครั้งเดียว

ต่อไปนี้เป็นลายลักษณ์อักษรในโฮมเพจของโครงการ:

การทดสอบหน่วยที่เหมาะสมควรล้มเหลวด้วยเหตุผลเดียวนั่นคือเหตุผลที่คุณควรใช้หนึ่งยืนยันต่อการทดสอบหน่วย

และรอยยังเขียนความคิดเห็น:

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

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


2
คุณจะล้อเลียนโดยไม่ต้องยืนยันหลายวิธีได้อย่างไร ความคาดหวังในการจำลองแต่ละครั้งเป็นการยืนยันในตัวเองรวมถึงลำดับการโทรที่คุณกำหนด
Christopher Creutzig

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

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

2
ฉันเคยเห็นกรณีที่มีการใช้การยืนยันหลายครั้งแทนที่จะเป็นRowTest(MbUnit) / TestCase(NUnit) เพื่อทดสอบพฤติกรรมของกรณีขอบที่หลากหลาย ใช้เครื่องมือที่เหมาะสมสำหรับงาน! (น่าเสียดายที่ MSTest ยังไม่มีความสามารถในการทดสอบแถว)
GalacticCowboy

@GalacticCowboy คุณสามารถรับฟังก์ชันการทำงานที่คล้ายกันRowTestและTestCaseใช้แหล่งข้อมูลการทดสอบ ฉันใช้ไฟล์ CSV ง่าย ๆ ที่ประสบความสำเร็จ
julealgon

คำตอบ:


236

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

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

คุณจะแก้ปัญหาหลายชุดได้อย่างไร


9
เช่นเดียวกับคำตอบนี้ - คือมันใช้ได้ แต่ไม่ใช่โดยทั่วไปดี (-:
Murph

5
ehh? ทำไมคุณจะทำเช่นนั้น? วิธีการดำเนินการเหมือนกันหรือไม่
jgauffin

197
การยืนยันเดียวต่อการทดสอบหน่วยเป็นวิธีที่ดีในการทดสอบความสามารถของผู้อ่านในการเลื่อนขึ้นและลง
ทอม

3
เหตุผลใดที่อยู่เบื้องหลังสิ่งนี้? ตามที่เป็นอยู่คำตอบปัจจุบันนี้เพียงระบุสิ่งที่ควร แต่ไม่ใช่ทำไม
Steven Jeuris

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

296

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

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

ฉันยินดีที่ได้เห็นข้อความยืนยันหลายข้อความที่เป็นส่วนหนึ่งของการทดสอบการกระทำเดียวกัน เช่น

[Test]
public void ValueIsInRange()
{
  int value = GetValueToTest();

  Assert.That(value, Is.GreaterThan(10), "value is too small");
  Assert.That(value, Is.LessThan(100), "value is too large");
} 

หรือ

[Test]
public void ListContainsOneValue()
{
  var list = GetListOf(1);

  Assert.That(list, Is.Not.Null, "List is null");
  Assert.That(list.Count, Is.EqualTo(1), "Should have one item in list");
  Assert.That(list[0], Is.Not.Null, "Item is null");
} 

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

เช่นคนแรกอาจเป็น

Assert.IsTrue((10 < value) && (value < 100), "Value out of range"); 

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

หมายเหตุ : รหัสที่ฉันเขียนที่นี่คือ C # กับ NUnit แต่หลักการจะเก็บไว้กับภาษาและกรอบงานอื่น ๆ ไวยากรณ์อาจคล้ายกันมาก


32
กุญแจสำคัญคือคุณมีการกระทำเดียวเท่านั้นจากนั้นคุณตรวจสอบผลลัพธ์ของการกระทำนั้นโดยใช้การยืนยัน
Amitābha

1
ฉันคิดว่ามันน่าสนใจเช่นกันที่จะมี Assert มากกว่าหนึ่งถ้า Arrange เสียเวลามาก
Rekshino

1
@Rekshino ถ้าการจัดเรียงเป็นเวลาแพงเราสามารถแชร์รหัสการจัดการได้เช่นโดยใส่รหัสการจัดเรียงในรูทีนการเตรียมใช้งานการทดสอบ
Shaun Luttin

2
ดังนั้นถ้าฉันเปรียบเทียบกับ Jaco ตอบ "เพียงคนเดียวยืนยัน" กลายเป็น "กลุ่มเดียวยืนยัน" ซึ่งทำให้ฉันมีความหมายมากขึ้น
Walfrat

2
นี่เป็นคำตอบที่ดี แต่ฉันไม่เห็นด้วยว่าการยืนยันเพียงครั้งเดียวไม่ดีขึ้น ตัวอย่างการยืนยันที่ไม่ดีนั้นไม่ดีกว่า แต่นั่นไม่ได้หมายความว่าการยืนยันเพียงครั้งเดียวจะไม่ดีกว่าถ้าทำถูกต้อง ห้องสมุดหลายแห่งอนุญาตให้มี asserts / matchers ที่กำหนดเองดังนั้นจึงสามารถสร้างบางสิ่งได้หากยังไม่มี ตัวอย่างเช่นการAssert.IsBetween(10, 100, value)พิมพ์Expected 8 to be between 10 and 100 นั้นดีกว่าสองข้อความยืนยันแยกกันในความคิดของฉัน คุณสามารถโต้เถียงได้อย่างแน่นอนว่าไม่จำเป็น แต่โดยปกติแล้วมันก็คุ้มค่าที่จะพิจารณาว่าเป็นเรื่องง่ายที่จะลดการยืนยันเพียงครั้งเดียวก่อนที่จะทำทั้งชุด
Thor84no

85

ฉันไม่เคยคิดว่าการยืนยันมากกว่าหนึ่งเป็นสิ่งที่ไม่ดี

ฉันทำมันตลอดเวลา:

public void ToPredicateTest()
{
    ResultField rf = new ResultField(ResultFieldType.Measurement, "name", 100);
    Predicate<ResultField> p = (new ConditionBuilder()).LessThanConst(400)
                                                       .Or()
                                                       .OpenParenthesis()
                                                       .GreaterThanConst(500)
                                                       .And()
                                                       .LessThanConst(1000)
                                                       .And().Not()
                                                       .EqualsConst(666)
                                                       .CloseParenthesis()
                                                       .ToPredicate();
    Assert.IsTrue(p(ResultField.FillResult(rf, 399)));
    Assert.IsTrue(p(ResultField.FillResult(rf, 567)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 400)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 666)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 1001)));

    Predicate<ResultField> p2 = (new ConditionBuilder()).EqualsConst(true).ToPredicate();

    Assert.IsTrue(p2(new ResultField(ResultFieldType.Confirmation, "Is True", true)));
    Assert.IsFalse(p2(new ResultField(ResultFieldType.Confirmation, "Is False", false)));
}

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

ฉันทำการทดสอบเพียงหน่วยเดียว ( ToPredicateวิธีการ) แต่ฉันครอบคลุมทุกอย่างที่ฉันสามารถคิดถึงได้ในการทดสอบ


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

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

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

19
กรณีของคุณเป็นตัวแทนอย่างมากนั่นคือเหตุผลที่ NUnit มีแอตทริบิวต์เพิ่มเติม TestCase - nunit.org/?p=testCase&r=2.5
Restuta

10
กรอบการทดสอบของ Google C ++ มี ASSERT () และ EXPECT () ASSERT () หยุดทำงานเมื่อเกิดข้อผิดพลาดขณะที่ EXPECT () ดำเนินการต่อ มันมีประโยชน์มากเมื่อคุณต้องการตรวจสอบมากกว่าหนึ่งอย่างในการทดสอบ
ratkok

21

เมื่อฉันใช้การทดสอบหน่วยเพื่อตรวจสอบพฤติกรรมระดับสูงฉันใส่การยืนยันหลายอย่างในการทดสอบครั้งเดียว นี่คือการทดสอบที่ฉันใช้จริงสำหรับรหัสแจ้งเตือนฉุกเฉินบางอย่าง รหัสที่รันก่อนการทดสอบทำให้ระบบอยู่ในสถานะที่หากตัวประมวลผลหลักทำงาน, สัญญาณเตือนจะถูกส่ง

@Test
public void testAlarmSent() {
    assertAllUnitsAvailable();
    assertNewAlarmMessages(0);

    pulseMainProcessor();

    assertAllUnitsAlerting();
    assertAllNotificationsSent();
    assertAllNotificationsUnclosed();
    assertNewAlarmMessages(1);
}

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

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


เมื่อคุณทำเช่นนั้นคุณควรใช้เฟรมเวิร์กการทดสอบด้วยการทดสอบแบบพึ่งพามันจะดีกว่า (เช่น testng รองรับฟีเจอร์นี้)
Kemoda

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

คุณมีความคิดเห็นอย่างไรเกี่ยวกับการปรับเปลี่ยนให้เป็น assertAlarmStatus (int numberOfAlarmMessages);
Borjab

1
การยืนยันของคุณจะทำให้ชื่อการทดสอบที่ดีมาก
Bjorn Tipling

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

8

อีกเหตุผลหนึ่งที่ฉันคิดว่าการยืนยันหลายวิธีในหนึ่งวิธีนั้นไม่ใช่สิ่งเลวร้ายที่อธิบายไว้ในรหัสต่อไปนี้:

class Service {
    Result process();
}

class Result {
    Inner inner;
}

class Inner {
    int number;
}

ในการทดสอบของฉันฉันต้องการทดสอบว่าservice.process()คืนค่าตัวเลขที่ถูกต้องในInnerคลาสอินสแตนซ์

แทนที่จะทดสอบ ...

@Test
public void test() {
    Result res = service.process();
    if ( res != null && res.getInner() != null ) Assert.assertEquals( ..., res.getInner() );
}

ฉันกำลังทำ

@Test
public void test() {
    Result res = service.process();
    Assert.notNull(res);
    Assert.notNull(res.getInner());
    Assert.assertEquals( ..., res.getInner() );
}

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

2
Assert.notNulls ของคุณซ้ำซ้อนการทดสอบของคุณจะล้มเหลวด้วย NPE หากเป็นโมฆะ
sara

1
ตัวอย่างแรกของคุณ (ที่มีif) จะผ่านถ้าresเป็นnull
sara

1
@kai notNull นั้นซ้ำซ้อนฉันเห็นด้วย แต่ฉันรู้สึกว่ามันดีกว่าที่จะมีการยืนยัน (และถ้าฉันไม่ขี้เกียจด้วยข้อความที่เหมาะสม) แทนที่จะยกเว้น ...
Betlista

1
การยืนยันที่ล้มเหลวก็มีข้อยกเว้นเช่นกันและในทั้งสองกรณีคุณได้รับลิงก์โดยตรงไปยังแถวที่แน่นอนที่มีการติดตามแบบสแต็กประกอบดังนั้นโดยส่วนตัวแล้ว ฉันต้องการออนไลเนอร์à la Assert.assertEquals(..., service.process().getInner());เป็นไปได้กับตัวแปรที่แยกออกมาหากบรรทัดได้รับ "ยาวเกินไป"
sara

6

ฉันคิดว่ามีหลายกรณีที่การเขียนข้อความยืนยันหลายชุดใช้ได้ภายในกฎที่การทดสอบควรล้มเหลวด้วยเหตุผลเดียวเท่านั้น

ตัวอย่างเช่นสมมติว่าฟังก์ชันแยกวิเคราะห์สตริงวันที่:

function testParseValidDateYMD() {
    var date = Date.parse("2016-01-02");

    Assert.That(date.Year).Equals(2016);
    Assert.That(date.Month).Equals(1);
    Assert.That(date.Day).Equals(0);
}

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


3

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

โดยทั่วไปนี่หมายความว่าบรรทัดสุดท้ายของโค้ดภายในเมธอด [SetUp] ควรเก็บค่าคุณสมบัติหรือค่าส่งคืนในตัวแปรอินสแตนซ์ส่วนตัวของ [TestFixture] จากนั้นคุณอาจยืนยันสิ่งต่าง ๆ มากมายเกี่ยวกับตัวแปรอินสแตนซ์นี้จากวิธี [ทดสอบ] ที่แตกต่างกัน คุณอาจยืนยันถึงคุณสมบัติที่แตกต่างกันของวัตถุภายใต้การทดสอบที่ถูกตั้งค่าเป็นตอนนี้ว่ามันอยู่ในสถานะที่ต้องการ

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

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

ตัวอย่าง (สิ่งนี้จะถูกแบ่งเป็นหลายไฟล์):

namespace Tests.AcctTests
{
    [TestFixture]
    public class no_events
    {
        private Acct _acct;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
        }

        [Test]
        public void balance_0() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_0
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(0m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_negative
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(-0.01m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }
}

คุณจะจัดการกับอินพุต TestCase ในกรณีนี้ได้อย่างไร
Simon Gillbee

ดังนั้นในองค์กรปัจจุบันของฉันเรามีการทดสอบมากกว่า 20,000 หน่วยที่คล้ายกับสิ่งที่คุณแสดง นี่คือฝันร้าย รหัสการตั้งค่าการทดสอบส่วนใหญ่ถูกคัดลอก / วางส่งผลให้เกิดการตั้งค่าการทดสอบที่ไม่ถูกต้องและการทดสอบที่ไม่ถูกต้องซึ่งผ่าน สำหรับแต่ละ[Test]วิธีคลาสนั้นจะถูกสร้างอินสแตนซ์ใหม่และ[SetUp]วิธีการจะถูกดำเนินการอีกครั้ง สิ่งนี้ฆ่า. NET Garbage Collector และทำให้การทดสอบทำงานช้ามาก: 5+ นาทีในเครื่อง, 20+ นาทีบนบิลด์เซิร์ฟเวอร์ การทดสอบ 20K ควรใช้เวลาประมาณ 2 - 3 นาที ฉันจะไม่แนะนำรูปแบบการทดสอบนี้เลยโดยเฉพาะชุดทดสอบขนาดใหญ่
fourpastmidnight

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

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

@ still_dreaming_1 wrt "โปรแกรมเมอร์ที่ไม่รับผิดชอบ": ฉันยอมรับว่าพฤติกรรมนี้เป็นปัญหาที่สำคัญ อย่างไรก็ตามโครงสร้างการทดสอบประเภทนี้จริง ๆ แล้วเชิญลักษณะการทำงานแบบนี้ให้ดีขึ้นหรือแย่ลง แนวทางการพัฒนาที่ไม่ดีนอกเหนือจากข้อโต้แย้งหลักของฉันในแบบฟอร์มนี้คือมันฆ่าประสิทธิภาพการทดสอบได้จริงๆ ไม่มีอะไรเลวร้ายไปกว่าชุดทดสอบที่ใช้งานช้า ชุดทดสอบที่ใช้งานช้าหมายความว่าผู้คนจะไม่เรียกใช้การทดสอบในพื้นที่และถูกล่อลวงให้ข้ามการสร้างระดับกลาง - อีกครั้งเป็นปัญหาของคน แต่มันเกิดขึ้น - ซึ่งสามารถหลีกเลี่ยงได้ทั้งหมดโดยให้แน่ใจว่าคุณมีการทดสอบที่รวดเร็ว กับ
fourpastmidnight

2

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

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


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

@ ริชาร์ด: รับผลแล้วแยกบางสิ่งออกจากผลลัพธ์นั้นจะเป็นกระบวนการในหลายขั้นตอนดังนั้นฉันจึงกล่าวถึงในย่อหน้าที่สองของคำตอบ
Guffa

2
Rule of thumb: หากคุณมีการยืนยันที่หลากหลายในการทดสอบแต่ละคนควรมีข้อความที่แตกต่างกัน จากนั้นคุณไม่มีปัญหานี้
แอนโทนี่

และการใช้นักวิ่งทดสอบที่มีคุณภาพเช่น NCrunch จะแสดงให้คุณเห็นอย่างชัดเจนถึงสิ่งที่การทดสอบล้มเหลวทั้งในรหัสทดสอบและในรหัสที่อยู่ภายใต้การทดสอบ
fourpastmidnight

2

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

String actual = "val1="+val1+"\nval2="+val2;
assertEquals(
    "val1=5\n" +
    "val2=hello"
    , actual
);

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


2

หากคุณมีการยืนยันหลายครั้งในฟังก์ชั่นการทดสอบเดียวฉันคาดว่าพวกเขาจะเกี่ยวข้องโดยตรงกับการทดสอบที่คุณกำลังทำอยู่ ตัวอย่างเช่น,

@Test
test_Is_Date_segments_correct {

   // It is okay if you have multiple asserts checking dd, mm, yyyy, hh, mm, ss, etc. 
   // But you would not have any assert statement checking if it is string or number,
   // that is a different test and may be with multiple or single assert statement.
}

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


1

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

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


1

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

การทดสอบหน่วยที่เหมาะสมควรล้มเหลวด้วยเหตุผลเดียวนั่นคือเหตุผลที่คุณควรใช้หนึ่งยืนยันต่อการทดสอบหน่วย

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

เราสามารถพูดคุยและพูดว่าระบบใด ๆ ของการยืนยันหลาย ๆ สามารถเขียนใหม่เป็นการยืนยันเดียวและการยืนยันเดียวใด ๆ ที่สามารถย่อยสลายเป็นชุดของการยืนยันที่มีขนาดเล็ก

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


0

คำตอบนั้นง่ายมาก - ถ้าคุณทดสอบฟังก์ชั่นที่เปลี่ยนแปลงมากกว่าหนึ่งคุณสมบัติของวัตถุเดียวกันหรือแม้แต่สองวัตถุที่แตกต่างกันและความถูกต้องของฟังก์ชั่นนั้นขึ้นอยู่กับผลลัพธ์ของการเปลี่ยนแปลงเหล่านั้นทั้งหมดแล้วคุณต้องการยืนยัน ว่าการเปลี่ยนแปลงเหล่านั้นถูกดำเนินการอย่างถูกต้อง!

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

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

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


-1

คำถามนี้เกี่ยวข้องกับปัญหาคลาสสิกของการปรับสมดุลระหว่างปัญหาเกี่ยวกับสปาเก็ตตี้และลาซานญ่า

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

มีข้อยกเว้นบางประการ แต่ในกรณีนี้การรักษาลูกตุ้มที่อยู่ตรงกลางคือคำตอบ


-3

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

สิ่งนี้ไม่สามารถทำได้ตลอดเวลาและเมื่อการทดสอบมีความซับซ้อนชื่อที่สื่อความหมาย (ยาว) และการทดสอบสิ่งต่าง ๆ มีน้อยลง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.