Thay đổi tên của các bài kiểm tra tham số


204

Có cách nào để đặt tên trường hợp thử nghiệm tùy chỉnh của riêng tôi khi sử dụng thử nghiệm được tham số hóa trong JUnit4 không?

Tôi muốn thay đổi mặc định - [Test class].runTest[n]- thành một cái gì đó có ý nghĩa.

Câu trả lời:


299

Tính năng này đã biến nó thành JUnit 4.11 .

Để sử dụng thay đổi tên của các bài kiểm tra tham số, bạn nói:

@Parameters(name="namestring")

namestring là một chuỗi, có thể có các trình giữ chỗ đặc biệt sau:

  • {index}- chỉ số của bộ đối số này. Mặc định namestring{index}.
  • {0} - giá trị tham số đầu tiên từ lệnh gọi thử nghiệm này.
  • {1} - giá trị tham số thứ hai
  • và như thế

Tên cuối cùng của bài kiểm tra sẽ là tên của phương thức kiểm tra, theo sau là namestringtrong ngoặc, như được hiển thị bên dưới.

Ví dụ: (được điều chỉnh từ bài kiểm tra đơn vị cho Parameterizedchú thích):

@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;
    }
}

sẽ đặt tên như testFib[1: fib(1)=1]testFib[4: fib(4)=3]. (Phần testFibcủa tên là tên phương thức của @Test).


4
Không có lý do gì nó không có trong 4.11, nó là chủ. Bây giờ khi 4.11 sẽ có sẵn, đó là một câu hỏi hay :-)
Matthew Farwell

1
4.11 hiện đang ở giai đoạn thử nghiệm và có thể được tải xuống từ cùng một liên kết như trên :-)
rescdsk

2
Có, nhưng có một lỗi. Nếu bạn đặt dấu ngoặc đơn trong giá trị "tên" tham số như bạn đang làm trong bài đăng này, nó sẽ phá vỡ hiển thị tên thử nghiệm đơn vị trong Eclipse.
djangofan

7
tuyệt vời, nhưng nếu {0}{1}là mảng thì sao? JUnit nên lý tưởng gọi Arrays.toString({0}), không {0}.toString(). Ví dụ, data()phương thức của tôi trả về Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});.
dogbane

1
@djangofan Đây là một lỗi Eclipse 8 năm tuổi: bug.eclipse.org/bugs/show_orms.cgi?id=102512
Pool

37

Nhìn vào JUnit 4.5, người chạy của nó rõ ràng không hỗ trợ điều đó, vì logic đó được chôn trong một lớp riêng bên trong lớp Parameterized. Bạn không thể sử dụng trình chạy Thông số JUnit và tự tạo thay thế để hiểu khái niệm về tên (điều này dẫn đến câu hỏi về cách bạn có thể đặt tên ...).

Từ góc độ JUnit, sẽ tốt hơn nếu thay vì (hoặc ngoài) chỉ vượt qua một gia số, họ sẽ vượt qua các đối số được phân tách bằng dấu phẩy. TestNG làm điều này. Nếu tính năng này quan trọng đối với bạn, bạn có thể nhận xét về danh sách gửi thư yahoo được tham chiếu tại www.junit.org.


3
Tôi sẽ đánh giá rất cao nếu có sự cải thiện cho điều này trong JUnit!
guerda

17
Chỉ cần kiểm tra, có một yêu cầu tính năng nổi bật cho việc này tại: github.com/KentBeck/junit/issues#su/44 Vui lòng bỏ phiếu.
reccles

8
@Frank, tôi nghĩ rằng bản phát hành giải quyết vấn đề này chưa được phát hành. Nó sẽ có trong JUnit 4.11. Vào thời điểm đó (giả sử thiết kế vẫn giữ nguyên), đó sẽ là về một cách thức văn bản chỉ định cách bạn đặt tên cho bài kiểm tra, bao gồm lấy tham số làm tên. Khá đẹp, thực sự.
Yishai

5
JUnit 4.11 hiện đã được phát hành :-)
rescdsk

7
Dưới đây là liên kết được cập nhật cho vấn đề ban đầu github.com/junit-team/junit/issues/44 để tham khảo trong tương lai
kldavis4 26/03/13

20

Gần đây tôi đã gặp một vấn đề tương tự khi sử dụng JUnit 4.3.1. Tôi đã triển khai một lớp mới mở rộng Parameterized gọi là LabellingParameterized. Nó đã được thử nghiệm bằng JUnit 4.3.1, 4.4 và 4.5. Nó xây dựng lại cá thể Mô tả bằng cách sử dụng biểu diễn Chuỗi của đối số đầu tiên của từng mảng tham số từ phương thức @Parameter. Bạn có thể xem mã cho điều này tại:

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

và một ví dụ về việc sử dụng nó tại:

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

Các định dạng mô tả thử nghiệm độc đáo trong Eclipse, đó là điều tôi muốn vì điều này làm cho các thử nghiệm thất bại dễ dàng tìm thấy hơn nhiều! Tôi có thể sẽ tiếp tục tinh chỉnh và ghi lại các lớp học trong vài ngày / tuần tới. Thả '?' một phần của các URL nếu bạn muốn cạnh chảy máu. :-)

Để sử dụng nó, tất cả những gì bạn phải làm là sao chép lớp đó (GPL v3) và thay đổi @RunWith (Parameterized. Class) thành @RunWith (LabellingParameterized. Class) giả sử phần tử đầu tiên của danh sách tham số của bạn là nhãn hợp lý.

Tôi không biết liệu có bất kỳ bản phát hành nào sau này của JUnit giải quyết vấn đề này không, nhưng ngay cả khi chúng đã xảy ra, tôi không thể cập nhật JUnit vì tất cả các nhà đồng phát triển của tôi cũng sẽ phải cập nhật và chúng tôi có mức độ ưu tiên cao hơn so với công cụ lại. Do đó, công việc trong lớp có thể được biên dịch bởi nhiều phiên bản của JUnit.


Lưu ý: có một số jiggery-pokery phản chiếu để nó chạy trên các phiên bản JUnit khác nhau như được liệt kê ở trên. Phiên bản dành riêng cho JUnit 4.3.1 có thể được tìm thấy ở đây và, cho JUnit 4.4 và 4.5, tại đây .


:-) Một trong những nhà đồng phát triển của tôi hôm nay có vấn đề với nó vì phiên bản tôi trỏ đến trong thông báo trên sử dụng JUnit 4.3.1 (không phải 4.4 như tôi đã nói ban đầu). Anh ta đang sử dụng JUnit 4.5.0 và nó gây ra vấn đề. Tôi sẽ giải quyết những điều này ngày hôm nay.
darrenp

Tôi đã mất một thời gian để hiểu rằng bạn cần phải vượt qua tên thử nghiệm trong hàm tạo, nhưng không ghi nhớ nó. Cảm ơn mã!
giraff

Hoạt động tuyệt vời miễn là tôi chạy các thử nghiệm từ Eclipse. Có ai có kinh nghiệm làm cho nó hoạt động với Nhiệm vụ Ant JUnit không? Các báo cáo thử nghiệm được đặt tên execute[0], execute[1] ... execute[n]trong các báo cáo thử nghiệm được tạo ra.
Henrik Aast Sørenen

Rất đẹp. Hoạt động như một lá bùa. Sẽ rất tuyệt, nếu bạn có thể thêm thông tin, thì bắt buộc phải thêm "Nhãn chuỗi, ..." làm tham số đầu tiên cho phương thức @ Test được gọi.
gia

13

Với Parameterizedtư cách là một người mẫu, tôi đã viết bộ chạy / bộ kiểm tra tùy chỉnh của riêng mình - chỉ mất khoảng nửa giờ. Nó hơi khác so với darrenp LabelledParameterizedở chỗ nó cho phép bạn chỉ định một cái tên rõ ràng thay vì dựa vào tham số đầu tiên toString().

Nó cũng không sử dụng mảng vì tôi ghét mảng. :)

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);
    }
  }
}

Và một ví dụ:

@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

từ Junit4.8.2, bạn có thể tạo lớp MyParameterized của riêng mình bằng cách sao chép lớp Parameterized. thay đổi các phương thức getName () và testName () trong TestClassRunnerForParameter.


Tôi đã thử điều này nhưng không giúp được gì. Trong khi tạo lớp mới getParameterMethod không thành công.
java_enthu


2

Bạn có thể tạo một phương thức như

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

Mặc dù tôi sẽ không sử dụng nó mọi lúc, nó sẽ hữu ích để tìm ra chính xác số kiểm tra 143 là gì.


2

Tôi sử dụng rộng rãi nhập tĩnh cho Assert và bạn bè, vì vậy tôi dễ dàng xác định lại xác nhận:

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

Ví dụ: bạn có thể thêm trường "tên" vào lớp kiểm tra của mình, được khởi tạo trong hàm tạo và hiển thị trường đó khi thử nghiệm thất bại. Chỉ cần vượt qua nó như là các yếu tố đầu tiên của mảng tham số của bạn cho mỗi thử nghiệm. Điều này cũng giúp gắn nhãn dữ liệu:

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}
    });
}

Điều này tốt nếu thử nghiệm thất bại khẳng định, nhưng có những trường hợp khác, chẳng hạn như nếu một ngoại lệ bị ném mà không thử nghiệm, hoặc nếu thử nghiệm dự kiến ​​sẽ ném ngoại lệ, điều đó khiến cho suy nghĩ về tên phải vượt quá xử lý theo khuôn khổ.
Yishai

2

Không ai trong số đó làm việc cho tôi, vì vậy tôi đã nhận được nguồn cho Parameterized và sửa đổi nó tạo ra một trình chạy thử nghiệm mới. Tôi đã không phải thay đổi nhiều nhưng CÔNG TRÌNH NÓ !!!

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

Một cách giải quyết khác là bắt và lồng tất cả các throwable vào một throwable mới với một thông báo tùy chỉnh chứa tất cả thông tin về các tham số. Thông báo sẽ xuất hiện trong theo dõi ngăn xếp. Điều này hoạt động bất cứ khi nào một bài kiểm tra thất bại cho tất cả các xác nhận, lỗi và ngoại lệ vì chúng đều là các lớp con của throwable.

Mã của tôi trông như thế này:

@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);
        }
    }

}

Dấu vết ngăn xếp của bài kiểm tra thất bại là:

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

Hãy xem JUnitParams như DSaff đã đề cập, hoạt động bằng cách sử dụng ant để xây dựng các mô tả phương pháp thử nghiệm được tham số hóa trong báo cáo html.

Điều này là sau khi thử LabellingParameterized và thấy rằng mặc dù nó hoạt động với nhật thực nhưng nó không hoạt động với kiến ​​theo như báo cáo html có liên quan.

Chúc mừng


0

Vì tham số được truy cập (ví dụ: "{0}"luôn trả về toString()biểu diễn, một cách giải quyết sẽ là thực hiện ẩn danh và ghi đè toString()trong mỗi trường hợp. Ví dụ:

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...
    );
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.