Junit - เรียกใช้วิธีการตั้งค่าหนึ่งครั้ง


120

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


คำตอบ:


205

แม้ว่าฉันจะเห็นด้วยกับ @assylias ว่าการใช้@BeforeClassเป็นวิธีแก้ปัญหาแบบคลาสสิก แต่ก็ไม่สะดวกเสมอไป วิธีการที่ใส่คำอธิบายประกอบ@BeforeClassต้องเป็นแบบคงที่ ไม่สะดวกสำหรับการทดสอบบางอย่างที่จำเป็นต้องมีกรณีทดสอบ ตัวอย่างเช่นการทดสอบตามสปริงที่ใช้@Autowiredเพื่อทำงานกับบริการที่กำหนดไว้ในบริบทฤดูใบไม้ผลิ

ในกรณีนี้ฉันใช้setUp()วิธีการปกติที่มี@Beforeคำอธิบายประกอบและจัดการแฟล็กstatic(!) ที่ กำหนดเองboolean:

private static boolean setUpIsDone = false;
.....
@Before
public void setUp() {
    if (setUpIsDone) {
        return;
    }
    // do the setup
    setUpIsDone = true;
}

10
เพิ่มความคิดเห็นของ Kenny Cason ว่าทำไมต้องเป็นแบบคงที่ ต้องเป็นแบบคงที่เนื่องจาก JUnit สร้างอินสแตนซ์ใหม่ของคลาสทดสอบสำหรับแต่ละวิธี @Test ตัวแปรอินสแตนซ์จะถูกรีเซ็ตเป็นค่าเริ่มต้น (เท็จ) สำหรับแต่ละอินสแตนซ์หากไม่คงที่ ดูข้อมูลเพิ่มเติม: martinfowler.com/bliki/JunitNewInstance.html
dustin.schultz

2
สิ่งนี้ใช้ได้ยกเว้นในกรณีที่setUp()เมธอดอยู่ในซูเปอร์คลาส - ได้โพสต์คำตอบด้านล่างเพื่อพยายามแก้ไขปัญหานี้
Steve Chambers

4
ฉันลังเลที่จะพูดสิ่งนี้กับคนที่มีตัวแทน 84,000 คน แต่ในความเป็นจริงแล้ว BeforeClass ไม่ได้ตอบคำถาม: BeforeClass ถูกเรียกใช้ที่จุดเริ่มต้นของทุกคลาสทดสอบ แต่ OP ขอให้เรียกใช้ "เพียงครั้งเดียวก่อนการทดสอบทั้งหมด" โซลูชันที่คุณเสนอสามารถทำได้ แต่คุณต้องทำให้คลาสทดสอบทั้งหมดของคุณขยายคลาส "CommonTest" ...
ไมค์หนู

1
@mikerodent IMHO OP ถามเกี่ยวกับการทดสอบทั้งหมดในกรณีทดสอบของเขาไม่ใช่การทดสอบทั้งหมดโดยรวม ดังนั้นความคิดเห็นของคุณจึงมีความเกี่ยวข้องน้อยกว่า BTW ไม่ต้องกังวลที่จะพูดอะไรกับบุคคลใด ๆ แม้ว่าชื่อเสียงของเขาจะสูงก็ตาม อย่างน้อยนี่คือสิ่งที่ฉันทำ :) และชื่อเสียงของฉันลดลงอย่างมากเมื่อเดือนสิงหาคม 2012 เมื่อฉันตอบคำถาม
AlexR

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

89

คุณสามารถใช้บันทึกย่อ :BeforeClass

@BeforeClass
public static void setUpClass() {
    //executed only once, before the first test
}

12
ฉันไม่สามารถใช้สิ่งนี้ได้ฉันมีวิธีการตั้งค่าบางอย่างที่ขึ้นอยู่กับส่วนประกอบที่ไม่คงที่เช่น getClass ()
Bober02

1
@ Bober02 BeforeClass ต้องคงที่แน่นอน หากคุณไม่สามารถใช้สิ่งนั้นได้คำตอบอื่นจะเป็นวิธีแก้ปัญหา
assylias

2
แน่ใจว่าคุณไม่สามารถใช้TheClassYouWant.classแทนการโทร getClass () ของคุณได้? นี่คือ Java จริง: String.class.getName().
stolsvik


1
@mikerodent ฉันเข้าใจคำถามนี้ว่า "การทดสอบทั้งหมดในชั้นเรียน" - แต่คุณคิดถูกแล้วอาจไม่ใช่สิ่งที่ OP ต้องการ
assylias

29

ตอนนี้ JUnit 5 มีคำอธิบายประกอบ @BeforeAll:

แสดงว่าเมธอดคำอธิบายประกอบควรถูกเรียกใช้ก่อนเมธอด @Test ทั้งหมดในคลาสปัจจุบันหรือลำดับชั้นคลาส คล้ายกับ @BeforeClass ของ JUnit 4 วิธีการดังกล่าวต้องเป็นแบบคงที่

คำอธิบายประกอบวงจรชีวิตของ JUnit 5 ดูเหมือนจะถูกต้องในที่สุด! คุณสามารถเดาได้ว่ามีคำอธิบายประกอบใดบ้างโดยไม่ต้องดู (เช่น @BeforeEach @AfterAll)


6
แต่ก็มีปัญหาเดียวกันคือมันจะต้องมีการ@BeforeClass staticโซลูชันของ IMO @ AlexR นั้นดีกว่า
zengr

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

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

สิ่งนี้ใช้ได้กับ Maven ที่มีหลายโมดูลแต่ละโมดูลมีการทดสอบหรือไม่
Mark Boon

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

9

เมื่อsetUp()ใดที่อยู่ในซูเปอร์คลาสของคลาสทดสอบ (เช่นAbstractTestBaseด้านล่าง) คำตอบที่ยอมรับสามารถแก้ไขได้ดังนี้:

public abstract class AbstractTestBase {
    private static Class<? extends AbstractTestBase> testClass;
    .....
    public void setUp() {
        if (this.getClass().equals(testClass)) {
            return;
        }

        // do the setup - once per concrete test class
        .....
        testClass = this.getClass();
    }
}

สิ่งนี้น่าจะใช้ได้กับวิธีการเดียวที่ไม่คงที่setUp()แต่ฉันไม่สามารถสร้างสิ่งที่เทียบเท่าได้tearDown()โดยไม่หลงเข้าไปในโลกแห่งการสะท้อนที่ซับซ้อน ... ค่าหัวชี้ให้ทุกคนที่ทำได้!


3

แก้ไข: ฉันเพิ่งค้นพบในขณะที่การดีบักว่าคลาสนั้นถูกสร้างอินสแตนซ์ก่อนการทดสอบทุกครั้งด้วย ฉันเดาว่าคำอธิบายประกอบ @BeforeClass ดีที่สุดที่นี่

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

public UT () {
    // initialize once here
}
@Test
// Some test here...

ctor จะถูกเรียกก่อนการทดสอบเนื่องจากไม่คงที่


0

ลองใช้วิธีนี้: https://stackoverflow.com/a/46274919/907576 :

ด้วย@BeforeAllMethods/ @AfterAllMethodsคำอธิบายประกอบคุณสามารถเรียกใช้เมธอดใดก็ได้ในคลาสทดสอบในบริบทอินสแตนซ์ซึ่งมีค่าที่แทรกทั้งหมด


อาศัยไลบรารีของบุคคลที่สาม
Andrew

0

วิธีแก้สกปรกของฉันคือ:

public class TestCaseExtended extends TestCase {

    private boolean isInitialized = false;
    private int serId;

    @Override
    public void setUp() throws Exception {
        super.setUp();
        if(!isInitialized) {
            loadSaveNewSerId();
            emptyTestResultsDirectory();
            isInitialized = true;
        }
    }

   ...

}

ฉันใช้มันเป็นฐานสำหรับ testCases ทั้งหมดของฉัน


คลาสสาธารณะ TestCaseExtended ขยาย TestCase {บูลีนแบบคงที่ส่วนตัว isInitialized = false; ส่วนตัว TestCaseExtended caseExtended แบบคงที่; int serId ส่วนตัว @Override โมฆะสาธารณะ setUp () พ่น Exception {super.setUp (); ถ้า (! isInitialized) {caseExtended = new TestCaseExtended (); caseExtended.loadSaveNewSerId (); caseExtended.emptyTestResultsDirectory (); isInitialized = จริง; }}
โอบีทู

0

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

public abstract class SuperTest {

    private static final ConcurrentHashMap<Class, Boolean> INITIALIZED = new ConcurrentHashMap<>();
    protected final boolean initialized() {
        final boolean[] absent = {false};
        INITIALIZED.computeIfAbsent(this.getClass(), (klass)-> {
            return absent[0] = true;
        });
        return !absent[0];
    }
}



public class SubTest extends SuperTest {
    @Before
    public void before() {
        if ( super.initialized() ) return;

         ... magic ... 
    }

}

0

ฉันแก้ไขปัญหานี้ดังนี้:

เพิ่มลงในคลาสBase abstract ของคุณ(ฉันหมายถึงคลาสนามธรรมที่คุณเริ่มต้นไดรเวอร์ของคุณในเมธอด setUpDriver () ) ส่วนนี้ของโค้ด:

private static boolean started = false;
static{
    if (!started) {
        started = true;
        try {
            setUpDriver();  //method where you initialize your driver
        } catch (MalformedURLException e) {
        }
    }
}

และตอนนี้ถ้าคลาสทดสอบของคุณจะขยายจากคลาสนามธรรมพื้นฐาน -> setUpDriver ()วิธีการจะถูกดำเนินการก่อน @Test แรกเพียงหนึ่งครั้งต่อการรัน


0

ใช้เมธอด @PostConstruct ของ Spring เพื่อเริ่มต้นทำงานทั้งหมดและวิธีนี้จะทำงานก่อนที่ @Test จะดำเนินการ

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