วิธีง่ายๆในการทดสอบ Junit เดียวกันซ้ำแล้วซ้ำเล่า?


122

เหมือนชื่อเรื่องฉันกำลังมองหาวิธีง่ายๆในการเรียกใช้การทดสอบ JUnit 4.x หลาย ๆ ครั้งติดต่อกันโดยอัตโนมัติโดยใช้ Eclipse

ตัวอย่างจะเรียกใช้การทดสอบเดียวกัน 10 ครั้งติดต่อกันและรายงานผลกลับมา

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

ทางออกที่ดีคือปลั๊กอิน / การตั้งค่า / คุณลักษณะของ Eclipse ที่ฉันไม่รู้จัก


5
ฉันสงสัยมากว่าทำไมคุณถึงอยากทำสิ่งนี้
Buhb

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

แน่นอนยกเว้นว่าคุณต้องการให้มันทำงานจนกว่าจะล้มเหลวในขณะที่ฉันต้องการเรียกใช้หลาย ๆ ครั้งซึ่งอาจส่งผลต่อคำตอบที่ฉันได้รับ
Stefan Thyberg

4
คุณต่อต้าน TestNG หรือไม่เพราะถ้าไม่เช่นนั้นคุณสามารถใช้ @Test (invocationCount = 10) และนั่นคือทั้งหมดที่มีให้
Robert Massaioli

1
ฉันไม่ได้ "ต่อต้าน" TestNG เราแค่ไม่ได้ใช้มันในโครงการนั้น
Stefan Thyberg

คำตอบ:


123

วิธีที่ง่ายที่สุด (เช่นเดียวกับรหัสใหม่ที่ต้องการน้อยที่สุด) ในการดำเนินการนี้คือเรียกใช้การทดสอบเป็นการทดสอบแบบพารามีทริก (ใส่คำอธิบายประกอบด้วย @RunWith(Parameterized.class)และเพิ่มวิธีการเพื่อระบุพารามิเตอร์ว่าง 10 พารามิเตอร์) ด้วยวิธีนี้เฟรมเวิร์กจะทำการทดสอบ 10 ครั้ง

การทดสอบนี้จะต้องเป็นการทดสอบเพียงอย่างเดียวในชั้นเรียนหรือวิธีการทดสอบทั้งหมดที่ดีกว่าควรจะต้องรัน 10 ครั้งในชั้นเรียน

นี่คือตัวอย่าง:

@RunWith(Parameterized.class)
public class RunTenTimes {

    @Parameterized.Parameters
    public static Object[][] data() {
        return new Object[10][0];
    }

    public RunTenTimes() {
    }

    @Test
    public void runsTenTimes() {
        System.out.println("run");
    }
}

จากข้างต้นเป็นไปได้ที่จะทำด้วยตัวสร้างที่ไม่มีพารามิเตอร์ แต่ฉันไม่แน่ใจว่าผู้เขียนเฟรมเวิร์กตั้งใจไว้อย่างนั้นหรือว่าจะพังในอนาคต

หากคุณใช้นักวิ่งของคุณเองคุณสามารถให้นักวิ่งทำการทดสอบ 10 ครั้ง หากคุณใช้นักวิ่งของบุคคลที่สามจากนั้นด้วย 4.7 คุณสามารถใช้@Ruleคำอธิบายประกอบใหม่และใช้MethodRuleอินเทอร์เฟซเพื่อรับคำสั่งและดำเนินการ 10 ครั้งในการวนซ้ำ ข้อเสียในปัจจุบันของแนวทางนี้คือ@Beforeและ@Afterเรียกใช้เพียงครั้งเดียว สิ่งนี้มีแนวโน้มที่จะเปลี่ยนแปลงใน JUnit เวอร์ชันถัดไป ( @Beforeจะทำงานตามหลัง@Rule) แต่ไม่ว่าคุณจะดำเนินการกับอินสแตนซ์เดียวกันของวัตถุ (สิ่งที่ไม่เป็นความจริงสำหรับParameterizedนักวิ่ง) สิ่งนี้จะถือว่านักวิ่งทุกคนที่คุณกำลังวิ่งอยู่ในชั้นเรียนนั้นจดจำ@Ruleคำอธิบายประกอบได้อย่างถูกต้อง นั่นเป็นเพียงกรณีที่มอบหมายให้นักวิ่ง JUnit

หากคุณกำลังวิ่งด้วยนักวิ่งที่กำหนดเองที่ไม่รู้จัก@Ruleคำอธิบายประกอบแสดงว่าคุณติดอยู่กับการต้องเขียนนักวิ่งของคุณเองที่มอบหมายให้กับนักวิ่งคนนั้นอย่างเหมาะสมและวิ่ง 10 ครั้ง

โปรดทราบว่ามีวิธีอื่นในการแก้ปัญหานี้ (เช่น Theories runner) แต่ทั้งหมดนี้ต้องใช้นักวิ่ง น่าเสียดายที่ปัจจุบัน JUnit ไม่รองรับเลเยอร์ของนักวิ่ง นั่นคือนักวิ่งที่ล่ามโซ่นักวิ่งคนอื่น ๆ


2
น่าเสียดายที่ฉันใช้ @RunWith กับนักวิ่งคนอื่นอยู่แล้ว แต่อย่างอื่นนี่จะเป็นทางออกที่ดี
Stefan Thyberg

ใช่นี่เป็นทางออกที่ฉันอยากจะมีและวิธีไหนที่ดีที่สุดสำหรับคนส่วนใหญ่ฉันจะดำเนินการต่อและยอมรับคำตอบ
Stefan Thyberg

สำหรับทางเลือกอื่นและอาจมีวิธีแก้ปัญหา
แฮ็ก

ทางออกที่ดี! ฉันได้รับข้อยกเว้นที่บอกว่าเมธอดข้อมูลควรส่งคืนอาร์เรย์ที่ทำซ้ำได้ ฉันแก้ไขตามนั้น: @ Parameterized.Parameters public static Iterable <Object []> data () {return Arrays.asList (new Object [20] [0]); }
นาเดร

1
ช่วยลิงค์ไปยังคำตอบของ JUnit 5 ได้ไหม อธิบายคุณลักษณะที่ร้องขอซึ่งเพิ่มเข้ามาใน JUnit 5
Marcono1234

101

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

ใส่คำอธิบายภาพที่นี่

เมื่อคุณทำการทดสอบ intellij จะทำการทดสอบทั้งหมดที่คุณเลือกไว้ตามจำนวนครั้งที่คุณระบุ

ตัวอย่างการทดสอบ 624 ครั้ง 10 ครั้ง: ใส่คำอธิบายภาพที่นี่


4
นั่นสมบูรณ์แบบแล้วตอนนี้ถ้าคุณสามารถชี้ไปที่วิธีการทำคราสได้นั่นจะตอบคำถามของ OP ได้ตรงประเด็น
khal

การใช้เครื่องมือเฉพาะเพื่อโฮสต์ตรรกะหรือข้อกำหนดจริงเป็นการต่อต้านรูปแบบ
Mickael

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

หากการทดสอบซ้ำมีประโยชน์สำหรับนักพัฒนา 1 คนก็น่าจะเป็นประโยชน์สำหรับผู้อื่น ดังนั้นหากรันไทม์ทดสอบและโค้ดสามารถโฮสต์ตรรกะเพื่อเปิดใช้งานการทำซ้ำได้ควรเลือกใช้เนื่องจากอนุญาตให้แยกตัวประกอบของความพยายามและวิธีแก้ปัญหาและอนุญาตให้ผู้ร่วมให้ข้อมูลใช้เครื่องมือตามที่ต้องการด้วยผลลัพธ์เดียวกัน การใส่ตรรกะที่ใช้ซ้ำได้ในพื้นที่ IDE / ผู้พัฒนาเมื่อสามารถใส่ในโค้ดได้นั้นเป็นการแยกตัวประกอบที่ขาดหายไป
Mickael

68

ฉันพบว่าคำอธิบายประกอบซ้ำของ Spring มีประโยชน์สำหรับสิ่งนั้น:

@Repeat(value = 10)

ล่าสุด (Spring Framework 4.3.11.RELEASE API) doc:


46
การเปลี่ยนกรอบการทดสอบไม่ใช่สิ่งที่ฉันเรียกว่าเป็นวิธีง่ายๆในการทำ
Stefan Thyberg

3
คุณไม่จำเป็นต้องเปลี่ยนกรอบการทดสอบของคุณ - ทำงานได้ดีกับ JUnit ข้อเสียเปรียบหลักคือ JUnit ยังคงมองว่าเป็นการทดสอบเพียงครั้งเดียว ดังนั้นในครั้งแรกที่พังการประหารชีวิตจะหยุดลง อย่างไรก็ตามหากคุณยังไม่ได้ใช้ Spring ก็อาจจะไม่ใช่ทางที่คุณต้องการ ...
tveon

ดูเหมือนจะไม่ได้ผลสำหรับฉัน (Spring 4.3.6 ผ่าน Spring Boot 1.5.1)
David Tonhofer

ไม่ได้ผลสำหรับฉันกับสปริงบูต 2.1.6 และ
มิถุนายน

ทำงานได้อย่างสมบูรณ์แบบกับสปริงบูต 2 อย่าลืมเพิ่ม @RunWith (SpringRunner :: class) ตามลิงก์ 'การทดสอบหน่วยในฤดูใบไม้ผลิ' ของโปสเตอร์!
Agoston Horvath

33

แรงบันดาลใจจากแหล่งข้อมูลต่อไปนี้:

ตัวอย่าง

สร้างและใช้@Repeatคำอธิบายประกอบดังนี้:

public class MyTestClass {

    @Rule
    public RepeatRule repeatRule = new RepeatRule();

    @Test
    @Repeat(10)
    public void testMyCode() {
        //your test code goes here
    }
}

Repeat.java

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention( RetentionPolicy.RUNTIME )
@Target({ METHOD, ANNOTATION_TYPE })
public @interface Repeat {
    int value() default 1;
}

RepeatRule.java

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class RepeatRule implements TestRule {

    private static class RepeatStatement extends Statement {
        private final Statement statement;
        private final int repeat;    

        public RepeatStatement(Statement statement, int repeat) {
            this.statement = statement;
            this.repeat = repeat;
        }

        @Override
        public void evaluate() throws Throwable {
            for (int i = 0; i < repeat; i++) {
                statement.evaluate();
            }
        }

    }

    @Override
    public Statement apply(Statement statement, Description description) {
        Statement result = statement;
        Repeat repeat = description.getAnnotation(Repeat.class);
        if (repeat != null) {
            int times = repeat.value();
            result = new RepeatStatement(statement, times);
        }
        return result;
    }
}

PowerMock

การใช้โซลูชันนี้กับ@RunWith(PowerMockRunner.class)ต้องอัปเดตเป็นPowermock 1.6.5 (ซึ่งรวมถึงแพตช์ )


ใช่. คุณดำเนินการทดสอบอย่างไร?
R.Oosterholt

ฉันไม่ได้ใช้คราสเอง บางทีคุณอาจไม่ได้ใช้นักวิ่งทดสอบ junut 4? (ดูเอกสาร "การปรับแต่งการกำหนดค่าการทดสอบ" )
R.Oosterholt

29

ด้วย JUnit 5 ฉันสามารถแก้ปัญหานี้ได้โดยใช้คำอธิบายประกอบ@RepeatedTest :

@RepeatedTest(10)
public void testMyCode() {
    //your test code goes here
}

โปรดทราบว่าคำอธิบายประกอบไม่ควรใช้ร่วมกับ@Test@RepeatedTest


ฟังดูมีแนวโน้มดี แต่โปรดทราบว่ายังไม่มีรุ่นที่วางจำหน่าย
9ilsdx 9rvj 0lo

1
JUnit 5 ตี GA เมื่อสองสามสัปดาห์ก่อน
mkobit

11

มีอะไรผิดพลาด:

@Test
void itWorks() {
    // stuff
}

@Test
void itWorksRepeatably() {
    for (int i = 0; i < 10; i++) {
        itWorks();
    }
}

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

ไม่จำเป็นต้องทำในการกำหนดค่าหรือคำอธิบายประกอบสิ่งที่คุณสามารถทำได้ในโค้ด


2
ฉันต้องการเรียกใช้การทดสอบหลายครั้งเหมือนการทดสอบหน่วยปกติและรับการติดตามและสถานะสำหรับแต่ละการทดสอบ
Stefan Thyberg

24
ในกรณีนี้จะไม่เรียกใช้ "@Before" และ "@After"
Bogdan

3
พร้อมกับการเรียก@Beforeวิธีการใส่คำอธิบายประกอบด้วยตนเองก่อนที่จะitWorks() แก้ไขปัญหาของฉัน
João Neves

คุณรู้จักแนวคิด DRY หรือไม่? en.wikipedia.org/wiki/Don%27t_repeat_yourselfฉันขอแนะนำให้ทำการตั้งค่าบางอย่างแทนการคัดลอกวางลูปของคุณทุกที่
Kikiwa

คิวแก้ไขสำหรับคำตอบนี้เต็มแล้ว ดังนั้นฉันจะใส่ความคิดเห็น: สำหรับ JUnit4 การทดสอบจะต้องเปิดเผยต่อสาธารณะ
Richard Jessop

7

วิธีนี้ง่ายกว่ามากสำหรับฉัน

public class RepeatTests extends TestCase {

    public static Test suite() {
        TestSuite suite = new TestSuite(RepeatTests.class.getName());

        for (int i = 0; i < 10; i++) {              
        suite.addTestSuite(YourTest.class);             
        }

        return suite;
    }
}

ยอดเยี่ยมเพราะไม่ใช้เฟรมเวิร์กอื่นและใช้งานได้จริงกับ JUnit 3 (สำคัญมากสำหรับ Android)
Vladimir Ivanov

1
การใช้งานกับ JUnit4 สามารถทำได้ด้วย Runner: public class RepeatRunner extends BlockJUnit4ClassRunner { public RepeatRunner(Class klass) throws InitializationError { super(klass); } @Override public void run(final RunNotifier notifier) { for (int i = 0; i < 10; i++) { super.run(notifier); } } }แม้ว่าอย่างน้อยในปลั๊กอิน Eclipse JUnit คุณจะได้ผลลัพธ์เช่น: "ผ่านการทดสอบ 10/1 ครั้ง"
Peter Wippermann

7

มีคำอธิบายประกอบเป็นระยะ ๆ ในไลบรารีtempus-fugitซึ่งทำงานร่วมกับ JUnit 4.7 @Ruleเพื่อทำการทดสอบซ้ำหลาย ๆ ครั้งหรือด้วย@RunWithที่จะทำซ้ำการทดสอบหลายครั้งหรือ

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

@RunWith(IntermittentTestRunner.class)
public class IntermittentTestRunnerTest {

   private static int testCounter = 0;

   @Test
   @Intermittent(repition = 99)
   public void annotatedTest() {
      testCounter++;
   }
}

หลังจากรันการทดสอบแล้ว (โดยมี IntermittentTestRunner อยู่ใน@RunWith) testCounterจะเท่ากับ 99


ใช่มันเป็นปัญหาเดียวกันกับที่นี่ใช้นักวิ่งคนอื่นอยู่แล้วและไม่สามารถใช้อันนี้ได้ แต่เป็นความคิดที่ดี
Stefan Thyberg

ใช่ฉันมีปัญหาเดียวกันกับ RunWith ... ในขณะที่ฉันปรับ tempus-fugit เพื่อปัดเศษเล็กน้อยคุณสามารถใช้ @Rule แทนที่จะเป็นนักวิ่งเมื่อคุณต้องการวิ่งซ้ำ ๆ คุณทำเครื่องหมายด้วย @ การทำซ้ำแทนที่จะเป็นช่วง ๆ เวอร์ชันของกฎจะไม่รัน @ Before / @ Afters ดูtempus-fugit.googlecode.com/svn/site/documentation/… (เลื่อนลงเพื่อโหลด / แช่การทดสอบ) สำหรับรายละเอียดเพิ่มเติม
Toby

0

ฉันสร้างโมดูลที่อนุญาตให้ทำการทดสอบประเภทนี้ แต่จะเน้นไม่ซ้ำ แต่รับประกันว่ารหัสบางส่วนเป็นเธรดปลอดภัย

https://github.com/anderson-marques/concurrent-testing

การพึ่งพา Maven:

<dependency>
    <groupId>org.lite</groupId>
    <artifactId>concurrent-testing</artifactId>
    <version>1.0.0</version>
</dependency>

ตัวอย่างการใช้งาน:

package org.lite.concurrent.testing;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import ConcurrentTest;
import ConcurrentTestsRule;

/**
 * Concurrent tests examples
 */
public class ExampleTest {

    /**
     * Create a new TestRule that will be applied to all tests
     */
    @Rule
    public ConcurrentTestsRule ct = ConcurrentTestsRule.silentTests();

    /**
     * Tests using 10 threads and make 20 requests. This means until 10 simultaneous requests.
     */
    @Test
    @ConcurrentTest(requests = 20, threads = 10)
    public void testConcurrentExecutionSuccess(){
        Assert.assertTrue(true);
    }

    /**
     * Tests using 10 threads and make 20 requests. This means until 10 simultaneous requests.
     */
    @Test
    @ConcurrentTest(requests = 200, threads = 10, timeoutMillis = 100)
    public void testConcurrentExecutionSuccessWaitOnly100Millissecond(){
    }

    @Test(expected = RuntimeException.class)
    @ConcurrentTest(requests = 3)
    public void testConcurrentExecutionFail(){
        throw new RuntimeException("Fail");
    }
}

นี่คือโครงการโอเพ่นซอร์ส อย่าลังเลที่จะปรับปรุง


0

คุณสามารถเรียกใช้การทดสอบ JUnit ของคุณจากวิธีการหลักและทำซ้ำหลาย ๆ ครั้งที่คุณต้องการ:

package tests;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.Result;

public class RepeatedTest {

    @Test
    public void test() {
        fail("Not yet implemented");
    }

    public static void main(String args[]) {

        boolean runForever = true;

        while (runForever) {
            Result result = org.junit.runner.JUnitCore.runClasses(RepeatedTest.class);

            if (result.getFailureCount() > 0) {
                runForever = false;
               //Do something with the result object

            }
        }

    }

}

0

นี่คือคำตอบที่ Yishai ให้ไว้ข้างต้นเขียนใหม่ใน Kotlin:

@RunWith(Parameterized::class)
class MyTest {

    companion object {

        private const val numberOfTests = 200

        @JvmStatic
        @Parameterized.Parameters
        fun data(): Array<Array<Any?>> = Array(numberOfTests) { arrayOfNulls<Any?>(0) }
    }

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