การเปลี่ยนชื่อของการทดสอบแบบกำหนดพารามิเตอร์


204

มีวิธีตั้งชื่อกรณีทดสอบแบบกำหนดเองของฉันเองเมื่อใช้การทดสอบแบบกำหนดพารามิเตอร์ใน JUnit4 หรือไม่

ฉันต้องการเปลี่ยนค่าเริ่มต้น[Test class].runTest[n]- เป็นสิ่งที่มีความหมาย

คำตอบ:


299

คุณลักษณะนี้ได้ทำให้มันเป็นJUnit 4.11

หากต้องการใช้เปลี่ยนชื่อของการทดสอบแบบมีพารามิเตอร์คุณพูดว่า:

@Parameters(name="namestring")

namestring เป็นสตริงซึ่งสามารถมีตัวยึดตำแหน่งพิเศษดังต่อไปนี้:

  • {index}- ดัชนีของชุดอาร์กิวเมนต์นี้ ค่าเริ่มต้นคือnamestring{index}
  • {0} - ค่าพารามิเตอร์แรกจากการเรียกใช้การทดสอบนี้
  • {1} - ค่าพารามิเตอร์ที่สอง
  • และอื่น ๆ

ชื่อสุดท้ายของการทดสอบจะเป็นชื่อของวิธีทดสอบตามด้วยnamestringเครื่องหมายวงเล็บตามที่แสดงด้านล่าง

ตัวอย่างเช่น (ดัดแปลงจากการทดสอบหน่วยสำหรับParameterizedคำอธิบายประกอบ):

@RunWith(Parameterized.class)
static public class FibonacciTest {

    @Parameters( name = "{index}: fib({0})={1}" )
    public static Iterable<Object[]> data() {
        return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
                { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
    }

    private final int fInput;
    private final int fExpected;

    public FibonacciTest(int input, int expected) {
        fInput= input;
        fExpected= expected;
    }

    @Test
    public void testFib() {
        assertEquals(fExpected, fib(fInput));
    }

    private int fib(int x) {
        // TODO: actually calculate Fibonacci numbers
        return 0;
    }
}

จะให้ชื่อเหมือนและtestFib[1: fib(1)=1] testFib[4: fib(4)=3]( testFibส่วนหนึ่งของชื่อคือชื่อวิธีการของ@Test)


4
ไม่มีเหตุผลที่มันจะไม่อยู่ใน 4.11 แต่อยู่ในระดับปริญญาโท ตอนนี้เมื่อ 4.11 จะสามารถใช้ได้ที่เป็นคำถามที่ดี :-)
แมทธิว Farwell

1
ตอนนี้ 4.11 อยู่ในรุ่นเบต้าและสามารถดาวน์โหลดได้จากลิงก์เดียวกันกับข้างบน :-)
rescdsk

2
ใช่ แต่มีข้อผิดพลาด หากคุณใส่เครื่องหมายวงเล็บไว้ในพารามิเตอร์ "name" เหมือนกับที่คุณทำในการโพสต์นี้มันจะหยุดการแสดงชื่อหน่วยทดสอบใน Eclipse
djangofan

7
ยอดเยี่ยม แต่จะเกิดอะไรขึ้นถ้ามี{0}และ{1}อาร์เรย์? JUnit ควรโทรไม่Arrays.toString({0}) {0}.toString()ตัวอย่างเช่นdata()วิธีการของฉันกลับArrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});มา
dogbane

1
@djangofan นี่เป็นข้อผิดพลาด Eclipse อายุ 8 ปี: bugs.eclipse.org/bugs/show_bug.cgi?id=102512
พูล

37

ดูที่ JUnit 4.5 นักวิ่งของมันไม่สนับสนุนอย่างชัดเจนเนื่องจากตรรกะนั้นฝังอยู่ในคลาสส่วนตัวภายในคลาส Parameterized คุณไม่สามารถใช้นักวิ่ง JUnit Parameterized และสร้างของคุณเองแทนซึ่งจะเข้าใจแนวคิดของชื่อ (ซึ่งนำไปสู่คำถามที่ว่าคุณอาจตั้งชื่อ ... ) ได้อย่างไร

จากมุมมอง JUnit มันจะดีถ้าแทนที่จะ (หรือเพิ่มเติม) เพียงแค่ผ่านการเพิ่มพวกเขาจะผ่านอาร์กิวเมนต์ที่คั่นด้วยเครื่องหมายจุลภาค TestNG ทำสิ่งนี้ หากคุณลักษณะสำคัญสำหรับคุณคุณสามารถแสดงความคิดเห็นในรายชื่อผู้รับจดหมายของ yahoo ที่อ้างอิงได้ที่ www.junit.org


3
ฉันขอขอบคุณอย่างมากหากมีการปรับปรุงใน JUnit!
guerda

17
เพิ่งตรวจสอบมีคำขอคุณสมบัติที่โดดเด่นสำหรับสิ่งนี้ได้ที่: github.com/KentBeck/junit/issues#issue/44โปรดลงคะแนน
reccles

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

5
JUnit 4.11 เปิดตัวแล้ว :-)
rescdsk

7
นี่คือลิงก์ที่อัปเดตไปที่ปัญหาดั้งเดิมgithub.com/junit-team/junit/issues/44สำหรับการอ้างอิงในอนาคต
kldavis4

20

ฉันเพิ่งเจอปัญหาเดียวกันเมื่อใช้ JUnit 4.3.1 ฉันใช้คลาสใหม่ซึ่งขยายพารามิเตอร์เรียกว่า LabelledParameterized มันได้รับการทดสอบโดยใช้ JUnit 4.3.1, 4.4 และ 4.5 มันสร้างอินสแตนซ์รายละเอียดอีกครั้งโดยใช้การแทนค่าสตริงของอาร์กิวเมนต์แรกของแต่ละพารามิเตอร์อาร์เรย์จากวิธีการ @Parameters คุณสามารถดูรหัสสำหรับสิ่งนี้ได้ที่:

http://code.google.com/p/migen/source/browse/trunk/java/src/.../LabelledParameterized.java?r=3789

และตัวอย่างของการใช้งานที่:

http://code.google.com/p/migen/source/browse/trunk/java/src/.../ServerBuilderTest.java?r=3789

รูปแบบคำอธิบายการทดสอบเป็นอย่างดีใน Eclipse ซึ่งเป็นสิ่งที่ฉันต้องการเนื่องจากทำให้การทดสอบล้มเหลวง่ายต่อการค้นหามาก! ฉันอาจจะปรับแต่งเพิ่มเติมและจัดทำเอกสารชั้นเรียนในอีกไม่กี่วัน / สัปดาห์ วาง '?' ส่วนหนึ่งของ URL หากคุณต้องการขอบเลือดออก :-)

หากต้องการใช้งานสิ่งที่คุณต้องทำคือคัดลอกคลาสนั้น (GPL v3) และเปลี่ยน @RunWith (Parameterized.class) เป็น @RunWith (LabelledParameterized.class) โดยสมมติว่าองค์ประกอบแรกของรายการพารามิเตอร์ของคุณเป็นฉลากที่สมเหตุสมผล

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


หมายเหตุ:มีการสะท้อน jiggery-pokery บางส่วนเพื่อให้ทำงานข้าม JUnit เวอร์ชันต่าง ๆ ดังที่แสดงไว้ด้านบน รุ่นเฉพาะสำหรับ JUnit 4.3.1 สามารถพบได้ที่นี่และสำหรับ JUnit 4.4 และ 4.5 ที่นี่


:-) หนึ่งในนักพัฒนาร่วมของฉันในวันนี้มีปัญหากับมันตั้งแต่รุ่นที่ฉันชี้ไปที่ในข้อความข้างต้นใช้ JUnit 4.3.1 (ไม่ใช่ 4.4 ตามที่ฉันพูดตอนแรก) เขาใช้ JUnit 4.5.0 และทำให้เกิดปัญหา ฉันจะพูดกับพวกนี้วันนี้
darrenp

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

ใช้งานได้ดีตราบใดที่ฉันทำการทดสอบจาก Eclipse ใครบ้างมีประสบการณ์กับการทำให้มันทำงานกับ JUnit Ant Task ได้ไหม? รายงานการทดสอบมีชื่อexecute[0], execute[1] ... execute[n]อยู่ในรายงานการทดสอบที่สร้างขึ้น
Henrik Aasted Sørensen

ดีมาก. ทำงานเหมือนจับใจ น่าจะดีถ้าคุณสามารถเพิ่มข้อมูลได้มันจำเป็นต้องเพิ่ม "String label, ... " เป็นพารามิเตอร์แรกใน @ method-method ที่เรียกใช้
gia

13

ด้วยการParameterizedเป็นนางแบบที่ผมเขียนเองทดสอบที่กำหนดเองวิ่ง / ชุดของฉัน - เพียงใช้เวลาประมาณครึ่งชั่วโมง มันแตกต่างกันเล็กน้อยจาก darrenp ในการที่จะช่วยให้คุณสามารถระบุชื่อชัดเจนมากกว่าอาศัยแห่งแรกของพารามิเตอร์LabelledParameterizedtoString()

มันไม่ได้ใช้อาร์เรย์เพราะฉันเกลียดอาร์เรย์ :)

public class PolySuite extends Suite {

  // //////////////////////////////
  // Public helper interfaces

  /**
   * Annotation for a method which returns a {@link Configuration}
   * to be injected into the test class constructor
   */
  @Retention(RetentionPolicy.RUNTIME)
  @Target(ElementType.METHOD)
  public static @interface Config {
  }

  public static interface Configuration {
    int size();
    Object getTestValue(int index);
    String getTestName(int index);
  }

  // //////////////////////////////
  // Fields

  private final List<Runner> runners;

  // //////////////////////////////
  // Constructor

  /**
   * Only called reflectively. Do not use programmatically.
   * @param c the test class
   * @throws Throwable if something bad happens
   */
  public PolySuite(Class<?> c) throws Throwable {
    super(c, Collections.<Runner>emptyList());
    TestClass testClass = getTestClass();
    Class<?> jTestClass = testClass.getJavaClass();
    Configuration configuration = getConfiguration(testClass);
    List<Runner> runners = new ArrayList<Runner>();
    for (int i = 0, size = configuration.size(); i < size; i++) {
      SingleRunner runner = new SingleRunner(jTestClass, configuration.getTestValue(i), configuration.getTestName(i));
      runners.add(runner);
    }
    this.runners = runners;
  }

  // //////////////////////////////
  // Overrides

  @Override
  protected List<Runner> getChildren() {
    return runners;
  }

  // //////////////////////////////
  // Private

  private Configuration getConfiguration(TestClass testClass) throws Throwable {
    return (Configuration) getConfigMethod(testClass).invokeExplosively(null);
  }

  private FrameworkMethod getConfigMethod(TestClass testClass) {
    List<FrameworkMethod> methods = testClass.getAnnotatedMethods(Config.class);
    if (methods.isEmpty()) {
      throw new IllegalStateException("@" + Config.class.getSimpleName() + " method not found");
    }
    if (methods.size() > 1) {
      throw new IllegalStateException("Too many @" + Config.class.getSimpleName() + " methods");
    }
    FrameworkMethod method = methods.get(0);
    int modifiers = method.getMethod().getModifiers();
    if (!(Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
      throw new IllegalStateException("@" + Config.class.getSimpleName() + " method \"" + method.getName() + "\" must be public static");
    }
    return method;
  }

  // //////////////////////////////
  // Helper classes

  private static class SingleRunner extends BlockJUnit4ClassRunner {

    private final Object testVal;
    private final String testName;

    SingleRunner(Class<?> testClass, Object testVal, String testName) throws InitializationError {
      super(testClass);
      this.testVal = testVal;
      this.testName = testName;
    }

    @Override
    protected Object createTest() throws Exception {
      return getTestClass().getOnlyConstructor().newInstance(testVal);
    }

    @Override
    protected String getName() {
      return testName;
    }

    @Override
    protected String testName(FrameworkMethod method) {
      return testName + ": " + method.getName();
    }

    @Override
    protected void validateConstructor(List<Throwable> errors) {
      validateOnlyOneConstructor(errors);
    }

    @Override
    protected Statement classBlock(RunNotifier notifier) {
      return childrenInvoker(notifier);
    }
  }
}

และตัวอย่าง:

@RunWith(PolySuite.class)
public class PolySuiteExample {

  // //////////////////////////////
  // Fixture

  @Config
  public static Configuration getConfig() {
    return new Configuration() {
      @Override
      public int size() {
        return 10;
      }

      @Override
      public Integer getTestValue(int index) {
        return index * 2;
      }

      @Override
      public String getTestName(int index) {
        return "test" + index;
      }
    };
  }

  // //////////////////////////////
  // Fields

  private final int testVal;

  // //////////////////////////////
  // Constructor

  public PolySuiteExample(int testVal) {
    this.testVal = testVal;
  }

  // //////////////////////////////
  // Test

  @Ignore
  @Test
  public void odd() {
    assertFalse(testVal % 2 == 0);
  }

  @Test
  public void even() {
    assertTrue(testVal % 2 == 0);
  }

}

6

จาก junit4.8.2 คุณสามารถสร้างคลาส MyParameterized ของคุณเองโดยเพียงแค่คัดลอกคลาส Parameterized เปลี่ยนเมธอด getName () และ testName () ใน TestClassRunnerForParameters


ฉันลองสิ่งนี้ แต่ไม่ได้ช่วย ในขณะที่สร้างคลาสใหม่ getParametersMethod ล้มเหลว
java_enthu


2

คุณสามารถสร้างวิธีการเช่น

@Test
public void name() {
    Assert.assertEquals("", inboundFileName);
}

ในขณะที่ฉันจะไม่ใช้มันตลอดเวลามันจะมีประโยชน์ที่จะคิดออกว่าหมายเลขทดสอบ 143 คืออะไร


2

ฉันใช้การนำเข้าแบบคงที่สำหรับ Assert และเพื่อน ๆ ดังนั้นจึงเป็นเรื่องง่ายสำหรับฉันที่จะกำหนดคำยืนยันใหม่:

private <T> void assertThat(final T actual, final Matcher<T> expected) {
    Assert.assertThat(editThisToDisplaySomethingForYourDatum, actual, expected);
}

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

public ExampleTest(final String testLabel, final int one, final int two) {
    this.testLabel = testLabel;
    // ...
}

@Parameters
public static Collection<Object[]> data() {
    return asList(new Object[][]{
        {"first test", 3, 4},
        {"second test", 5, 6}
    });
}

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

2

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.junit.Assert;
import org.junit.internal.runners.ClassRoadie;
import org.junit.internal.runners.CompositeRunner;
import org.junit.internal.runners.InitializationError;
import org.junit.internal.runners.JUnit4ClassRunner;
import org.junit.internal.runners.MethodValidator;
import org.junit.internal.runners.TestClass;
import org.junit.runner.notification.RunNotifier;

public class LabelledParameterized extends CompositeRunner {
static class TestClassRunnerForParameters extends JUnit4ClassRunner {
    private final Object[] fParameters;

    private final String fParameterFirstValue;

    private final Constructor<?> fConstructor;

    TestClassRunnerForParameters(TestClass testClass, Object[] parameters, int i) throws InitializationError {
        super(testClass.getJavaClass()); // todo
        fParameters = parameters;
        if (parameters != null) {
            fParameterFirstValue = Arrays.asList(parameters).toString();
        } else {
            fParameterFirstValue = String.valueOf(i);
        }
        fConstructor = getOnlyConstructor();
    }

    @Override
    protected Object createTest() throws Exception {
        return fConstructor.newInstance(fParameters);
    }

    @Override
    protected String getName() {
        return String.format("%s", fParameterFirstValue);
    }

    @Override
    protected String testName(final Method method) {
        return String.format("%s%s", method.getName(), fParameterFirstValue);
    }

    private Constructor<?> getOnlyConstructor() {
        Constructor<?>[] constructors = getTestClass().getJavaClass().getConstructors();
        Assert.assertEquals(1, constructors.length);
        return constructors[0];
    }

    @Override
    protected void validate() throws InitializationError {
        // do nothing: validated before.
    }

    @Override
    public void run(RunNotifier notifier) {
        runMethods(notifier);
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface Parameters {
}

private final TestClass fTestClass;

public LabelledParameterized(Class<?> klass) throws Exception {
    super(klass.getName());
    fTestClass = new TestClass(klass);

    MethodValidator methodValidator = new MethodValidator(fTestClass);
    methodValidator.validateStaticMethods();
    methodValidator.validateInstanceMethods();
    methodValidator.assertValid();

    int i = 0;
    for (final Object each : getParametersList()) {
        if (each instanceof Object[])
            add(new TestClassRunnerForParameters(fTestClass, (Object[]) each, i++));
        else
            throw new Exception(String.format("%s.%s() must return a Collection of arrays.", fTestClass.getName(), getParametersMethod().getName()));
    }
}

@Override
public void run(final RunNotifier notifier) {
    new ClassRoadie(notifier, fTestClass, getDescription(), new Runnable() {
        public void run() {
            runChildren(notifier);
        }
    }).runProtected();
}

private Collection<?> getParametersList() throws IllegalAccessException, InvocationTargetException, Exception {
    return (Collection<?>) getParametersMethod().invoke(null);
}

private Method getParametersMethod() throws Exception {
    List<Method> methods = fTestClass.getAnnotatedMethods(Parameters.class);
    for (Method each : methods) {
        int modifiers = each.getModifiers();
        if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))
            return each;
    }

    throw new Exception("No public static parameters method on class " + getName());
}

public static Collection<Object[]> eachOne(Object... params) {
    List<Object[]> results = new ArrayList<Object[]>();
    for (Object param : params)
        results.add(new Object[] { param });
    return results;
}
}

2

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

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

@RunWith(Parameterized.class)
public class ParameterizedTest {

    int parameter;

    public ParameterizedTest(int parameter) {
        super();
        this.parameter = parameter;
    }

    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] { {1}, {2} });
    }

    @Test
    public void test() throws Throwable {
        try {
            assertTrue(parameter%2==0);
        }
        catch(Throwable thrown) {
            throw new Throwable("parameter="+parameter, thrown);
        }
    }

}

การติดตามสแต็กของการทดสอบที่ล้มเหลวคือ:

java.lang.Throwable: parameter=1
    at sample.ParameterizedTest.test(ParameterizedTest.java:34)
Caused by: java.lang.AssertionError
    at org.junit.Assert.fail(Assert.java:92)
    at org.junit.Assert.assertTrue(Assert.java:43)
    at org.junit.Assert.assertTrue(Assert.java:54)
    at sample.ParameterizedTest.test(ParameterizedTest.java:31)
    ... 31 more

0

ตรวจสอบ JUnitParams ตามที่กล่าวถึง dsaff, ทำงานโดยใช้ ant เพื่อสร้างคำอธิบายวิธีการทดสอบแบบพารามิเตอร์ในรายงาน html

ปัญหานี้เกิดขึ้นหลังจากลอง LabelledParameterized และพบว่ามันทำงานได้กับ eclipse แต่จะไม่ทำงานกับ ant เท่าที่รายงาน html เกี่ยวข้อง

ไชโย


0

เนื่องจากพารามิเตอร์ที่เข้าถึง (เช่นมีการ"{0}"ส่งคืนการtoString()แทนเสมอการแก้ปัญหาหนึ่งข้อคือการใช้งานแบบไม่ระบุชื่อและการลบล้างtoString()ในแต่ละกรณีตัวอย่างเช่น:

public static Iterable<? extends Object> data() {
    return Arrays.asList(
        new MyObject(myParams...) {public String toString(){return "my custom test name";}},
        new MyObject(myParams...) {public String toString(){return "my other custom test name";}},
        //etc...
    );
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.