แนวทางปฏิบัติที่ดีที่สุด: เริ่มต้นฟิลด์คลาส JUnit ใน setUp () หรือเมื่อประกาศ?


120

ฉันควรเริ่มต้นฟิลด์คลาสเมื่อประกาศเช่นนี้หรือไม่?

public class SomeTest extends TestCase
{
    private final List list = new ArrayList();

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

หรือใน setUp () แบบนี้?

public class SomeTest extends TestCase
{
    private List list;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        this.list = new ArrayList();
    }

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

ฉันมักจะใช้แบบฟอร์มแรกเพราะมันกระชับกว่าและช่วยให้ฉันใช้ฟิลด์สุดท้ายได้ หากฉันไม่จำเป็นต้องใช้เมธอด setUp () ในการตั้งค่าฉันจะยังคงใช้มันอยู่หรือไม่เพราะเหตุใด

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


2
ระวังว่าคำตอบจะแตกต่างกันใน JUnit 4 (เริ่มต้นในการประกาศ) และ JUnit 3 (ใช้ setUp) นี่คือต้นตอของความสับสน
Nils von Barth

คำตอบ:


99

หากคุณสงสัยเป็นพิเศษเกี่ยวกับตัวอย่างในคำถามที่พบบ่อยของ JUnit เช่นเทมเพลตการทดสอบพื้นฐานฉันคิดว่าแนวทางปฏิบัติที่ดีที่สุดที่แสดงให้เห็นคือชั้นเรียนที่อยู่ระหว่างการทดสอบควรได้รับการสร้างอินสแตนซ์ในเมธอด setUp ของคุณ (หรือในวิธีทดสอบ) .

เมื่อตัวอย่าง JUnit สร้าง ArrayList ในเมธอด setUp พวกเขาทั้งหมดจะทดสอบพฤติกรรมของ ArrayList นั้นโดยมีกรณีเช่น testIndexOutOfBoundException, testEmptyCollection และสิ่งที่คล้ายกัน มุมมองของคนที่เขียนชั้นเรียนและตรวจสอบให้แน่ใจว่าทำงานได้ถูกต้อง

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

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

สำหรับสิ่งที่คุ้มค่าฉันทำงานกับฐานรหัสที่พัฒนา TDD ซึ่งมีอายุหลายปีพอสมควร เราเริ่มต้นสิ่งต่างๆเป็นปกติในการประกาศในรหัสทดสอบและในปีครึ่งที่ฉันเข้าร่วมโครงการนี้ไม่เคยทำให้เกิดปัญหา ดังนั้นอย่างน้อยก็มีหลักฐานบางอย่างที่แสดงว่าเป็นสิ่งที่สมควรทำ


45

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

ไม่มีสิ่งนี้ใช้กับตัวอย่างของการสร้างคอลเล็กชันว่างเนื่องจากจะไม่มีวันโยนทิ้ง แต่เป็นข้อดีของsetUp()วิธีนี้


18

นอกจากคำตอบของ Alex B

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

JUnit จะสร้างอินสแตนซ์ของ testClass สำหรับแต่ละวิธีการทดสอบก่อนและเริ่มรันการทดสอบหลังจากสร้างอินสแตนซ์แต่ละรายการ ก่อนที่จะเรียกใช้วิธีการทดสอบวิธีการตั้งค่าจะถูกรันซึ่งสามารถเตรียมสถานะบางอย่างได้

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

วงจรชีวิตของ JUnits:

  1. สร้างอินสแตนซ์ testclass ที่แตกต่างกันสำหรับแต่ละวิธีการทดสอบ
  2. ทำซ้ำสำหรับแต่ละอินสแตนซ์ testclass: เรียกการตั้งค่า + เรียกวิธีการทดสอบ

ด้วยการบันทึกบางอย่างในการทดสอบด้วยวิธีการทดสอบสองวิธีที่คุณจะได้รับ: (ตัวเลขคือรหัสแฮช)

  • การสร้างอินสแตนซ์ใหม่: 5718203
  • การสร้างอินสแตนซ์ใหม่: 5947506
  • ตั้งค่า: 5718203
  • TestOne: 5718203
  • การตั้งค่า: 5947506
  • ทดสอบสอง: 5947506

3
ถูกต้อง แต่ไม่ตรงประเด็น ฐานข้อมูลเป็นสถานะสากล นี่ไม่ใช่ปัญหาที่ฉันต้องเผชิญ ฉันแค่กังวลกับความเร็วในการดำเนินการของการทดสอบที่เป็นอิสระอย่างเหมาะสม
Craig P. Motlin

คำสั่งเริ่มต้นนี้เป็นจริงใน JUnit 3 เท่านั้นซึ่งเป็นข้อควรระวังที่สำคัญ ในอินสแตนซ์ทดสอบ JUnit 4 ถูกสร้างขึ้นอย่างเฉื่อยชาดังนั้นการเริ่มต้นในการประกาศหรือในวิธีการตั้งค่าจะเกิดขึ้นในเวลาทดสอบ นอกจากนี้สำหรับการตั้งค่าครั้งเดียวสามารถใช้@BeforeClassใน JUnit 4 ได้
Nils von Barth

11

ใน JUnit 4:

  • สำหรับคลาสภายใต้การทดสอบเริ่มต้นด้วย@Beforeวิธีการเพื่อตรวจจับความล้มเหลว
  • สำหรับคลาสอื่น ๆเริ่มต้นในการประกาศ ...
    • ... เพื่อความกะทัดรัดและเพื่อทำเครื่องหมายฟิลด์finalตรงตามที่ระบุไว้ในคำถาม
    • ... เว้นแต่จะเป็นการเริ่มต้นที่ซับซ้อนซึ่งอาจล้มเหลวซึ่งในกรณีนี้จะใช้@Beforeเพื่อตรวจจับความล้มเหลว
  • สำหรับสภาวะโลก (โดยเฉพาะการเริ่มต้นช้าเช่นฐานข้อมูล) ให้ใช้@BeforeClassแต่ระวังการพึ่งพาระหว่างการทดสอบ
  • การกำหนดค่าเริ่มต้นของวัตถุที่ใช้ในการทดสอบครั้งเดียวควรทำในวิธีการทดสอบเอง

การเริ่มต้นด้วย@Beforeวิธีการหรือวิธีการทดสอบช่วยให้คุณได้รับการรายงานข้อผิดพลาดเกี่ยวกับความล้มเหลวที่ดีขึ้น สิ่งนี้มีประโยชน์อย่างยิ่งสำหรับการสร้างอินสแตนซ์ Class Under Test (ซึ่งคุณอาจพัง) แต่ยังมีประโยชน์สำหรับการเรียกระบบภายนอกเช่นการเข้าถึงระบบไฟล์ ("ไม่พบไฟล์") หรือการเชื่อมต่อกับฐานข้อมูล ("การเชื่อมต่อถูกปฏิเสธ")

เป็นที่ยอมรับได้ที่จะมีมาตรฐานที่เรียบง่ายและใช้เสมอ@Before(ข้อผิดพลาดที่ชัดเจน แต่เป็นรายละเอียด) หรือเริ่มต้นในการประกาศเสมอ (กระชับ แต่ให้ข้อผิดพลาดที่สับสน) เนื่องจากกฎการเข้ารหัสที่ซับซ้อนยากที่จะปฏิบัติตามและนี่ไม่ใช่เรื่องใหญ่

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

สิ่งนี้สรุปคำตอบอื่น ๆ อีกมากมายที่ฝัง lede โดยเฉพาะ Craig P.Motlin (คำถามและคำตอบด้วยตนเอง) Moss Collum (ชั้นเรียนที่อยู่ระหว่างการทดสอบ) และ dsaff


7

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

โปรดทราบว่าใน JUnit 4 การเริ่มต้นออบเจ็กต์ทดสอบจะเกิดขึ้นก่อนที่จะทำการทดสอบดังนั้นการใช้ตัวเริ่มต้นภาคสนามจึงปลอดภัยกว่าและรูปแบบที่แนะนำ


น่าสนใจ พฤติกรรมที่คุณอธิบายในตอนแรกใช้กับ JUnit 3 เท่านั้น?
Craig P. Motlin

6

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


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

@Olaf ขอบคุณสำหรับข้อมูลเกี่ยวกับมาตรฐานการเข้ารหัสฉันไม่ได้คิดเรื่องนั้น ฉันมักจะเห็นด้วยกับแนวคิดของ Moss Collum เกี่ยวกับมาตรฐานการเข้ารหัสมากกว่า
Craig P. Motlin

5

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

ตัวอย่างการเพิ่มประสิทธิภาพโดยใช้@BeforeClassวิธีการตั้งค่า: ฉันใช้ dbunit สำหรับการทดสอบการทำงานของฐานข้อมูลบางอย่าง วิธีการตั้งค่ามีหน้าที่ทำให้ฐานข้อมูลอยู่ในสถานะที่ทราบ (ช้ามาก ... 30 วินาที - 2 นาทีขึ้นอยู่กับปริมาณข้อมูล) ฉันโหลดข้อมูลนี้ในวิธีการตั้งค่าที่มีคำอธิบายประกอบ@BeforeClassและเรียกใช้การทดสอบ 10-20 ชุดกับข้อมูลชุดเดียวกันเมื่อเทียบกับการโหลดใหม่ / เริ่มต้นฐานข้อมูลภายในการทดสอบแต่ละครั้ง

การใช้ Junit 3.8 (การขยาย TestCase ตามที่แสดงในตัวอย่างของคุณ) ต้องการการเขียนโค้ดมากกว่าการเพิ่มคำอธิบายประกอบเล็กน้อย แต่ยังสามารถ "รันครั้งเดียวก่อนการตั้งค่าคลาส" ได้


1
+1 เพราะฉันชอบความสามารถในการอ่านมากกว่า อย่างไรก็ตามฉันไม่มั่นใจว่าวิธีที่สองคือการเพิ่มประสิทธิภาพเลย
Craig P. Motlin

@Motlin ฉันได้เพิ่มตัวอย่าง dbunit เพื่อชี้แจงว่าคุณสามารถเพิ่มประสิทธิภาพด้วยการตั้งค่าได้อย่างไร
Alex B

ฐานข้อมูลเป็นสถานะสากล ดังนั้นการย้ายการตั้งค่าฐานข้อมูลไปที่ setUp () ไม่ใช่การเพิ่มประสิทธิภาพจึงจำเป็นสำหรับการทดสอบเพื่อให้เสร็จสมบูรณ์
Craig P. Motlin

@ Alex B: อย่างที่ Motlin กล่าวนี่ไม่ใช่การเพิ่มประสิทธิภาพ คุณแค่เปลี่ยนตำแหน่งในโค้ดที่การเริ่มต้นจะเสร็จสิ้น แต่ไม่ใช่กี่ครั้งหรือเร็วแค่ไหน
Eddie

ฉันตั้งใจจะบอกเป็นนัยว่าใช้คำอธิบายประกอบ "@BeforeClass" แก้ไขตัวอย่างเพื่อชี้แจง
Alex B

2

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

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

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

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


1
+1 เนื่องจากคุณเป็นคนแรกที่สนับสนุนการเริ่มต้นรายการในระหว่างการสร้างวัตถุเพื่อทำเครื่องหมายขั้นสุดท้าย สิ่งที่เกี่ยวกับตัวแปรคงไม่ตรงประเด็นสำหรับคำถาม
Craig P. Motlin

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

ข้อดีของการfinalกล่าวถึงในคำถามแม้ว่า
Nils von Barth

0
  • ค่าคงที่ (ใช้ในการติดตั้งหรือการยืนยัน) ควรเริ่มต้นในการประกาศและfinal(ไม่เคยเปลี่ยนแปลง)

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

  • การอ้างอิงของวัตถุที่อยู่ระหว่างการทดสอบหากสิ่งเหล่านี้ถูกล้อเลียนไม่ควรแม้แต่จะสร้างอินสแตนซ์ด้วยตัวคุณเองวันนี้เฟรมเวิร์กจำลองสามารถสร้างอินสแตนซ์ได้โดยการสะท้อน

การทดสอบโดยไม่ต้องพึ่งพาการเยาะเย้ยอาจมีลักษณะดังนี้:

public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Before
    public void beforeEach() {
       some = new Some(new Foo(), new Bar());
    } 

    @Test
    public void populateList()
         ...
    }
}

การทดสอบที่มีการอ้างอิงเพื่อแยกอาจมีลักษณะดังนี้:

@RunWith(org.mockito.runners.MockitoJUnitRunner.class)
public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Mock
    Foo fooMock;

    @Mock
    Bar barMock;

    @Before
    public void beforeEach() {
       some = new Some(fooMock, barMock);
    }

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