Mengubah nama uji parameterisasi

204

Apakah ada cara untuk menetapkan nama kasus pengujian khusus saya saat menggunakan tes parameter di JUnit4?

Saya ingin mengubah default - [Test class].runTest[n]- ke sesuatu yang bermakna.

Epaga
sumber

Jawaban:

299

Fitur ini telah membuatnya menjadi JUnit 4.11 .

Untuk menggunakan ubah nama pengujian parameter, Anda mengatakan:

@Parameters(name="namestring")

namestring adalah string, yang dapat memiliki penampung khusus berikut:

  • {index}- indeks kumpulan argumen ini. Standarnya namestringadalah{index} .
  • {0} - nilai parameter pertama dari permohonan tes ini.
  • {1} - nilai parameter kedua
  • dan seterusnya

Nama akhir pengujian akan menjadi nama metode pengujian, diikuti oleh namestringtanda kurung, seperti yang ditunjukkan di bawah ini.

Misalnya (diadaptasi dari tes unit untuk Parameterizedanotasi):

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

akan memberi nama seperti testFib[1: fib(1)=1]dan testFib[4: fib(4)=3]. (Bagian testFibdari nama adalah nama metode dari @Test).

rescdsk
sumber
4
Tidak ada alasan itu tidak akan di 4,11, itu di master. Sekarang ketika 4.11 akan tersedia, itu adalah pertanyaan yang bagus :-)
Matthew Farwell
1
4.11 sekarang dalam versi beta, dan dapat diunduh dari tautan yang sama seperti di atas :-)
rescdsk
2
Ya, tapi ada bug. Jika Anda meletakkan tanda kurung di nilai "nama" parameter seperti yang Anda lakukan dalam posting ini, itu memecah tampilan nama tes unit di Eclipse.
djangofan
7
hebat, tetapi bagaimana jika {0}dan {1}apakah array? JUnit idealnya menelepon Arrays.toString({0}), bukan {0}.toString(). Sebagai contoh, data()metode saya kembali Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});.
dogbane
1
@djangofan Ini adalah bug Eclipse berusia 8 tahun: bugs.eclipse.org/bugs/show_bug.cgi?id=102512
Pool
37

Melihat JUnit 4.5, pelarinya jelas tidak mendukung itu, karena logika itu terkubur di dalam kelas privat di dalam kelas Parameterisasi. Anda tidak dapat menggunakan pelari Parameterized JUnit, dan sebaliknya membuat sendiri yang akan memahami konsep nama (yang mengarah ke pertanyaan tentang bagaimana Anda dapat menetapkan nama ...).

Dari perspektif JUnit, alangkah baiknya jika alih-alih (atau sebagai tambahan) hanya melewati kenaikan, mereka akan melewati argumen yang dibatasi koma. TestNG melakukan ini. Jika fitur ini penting bagi Anda, Anda dapat mengomentari milis yahoo yang dirujuk di www.junit.org.

Yishai
sumber
3
Saya akan sangat menghargai jika ada perbaikan untuk ini di JUnit!
guerda
17
Baru saja diperiksa, ada permintaan fitur yang luar biasa untuk ini di: github.com/KentBeck/junit/issues#issue/44 Harap beri suara.
reccles
8
@ Sejujurnya, saya pikir rilis yang membahas masalah ini belum dirilis. Itu akan berada di JUnit 4.11. Pada saat itu (dengan asumsi desain tetap sama) itu akan tentang cara tekstual menentukan bagaimana Anda memberi nama tes, termasuk mengambil parameter sebagai nama. Cukup bagus, sebenarnya.
Yishai
5
JUnit 4.11 sekarang telah dirilis :-)
rescdsk
7
Berikut ini tautan yang diperbarui ke edisi asli github.com/junit-team/junit/issues/44 untuk referensi di masa mendatang
kldavis4
20

Saya baru-baru ini menemukan masalah yang sama ketika menggunakan JUnit 4.3.1. Saya menerapkan kelas baru yang memperluas Parameterized disebut LabelledParameterized. Ini telah diuji menggunakan JUnit 4.3.1, 4.4 dan 4.5. Itu merekonstruksi instance Deskripsi menggunakan representasi String argumen pertama dari setiap parameter array dari metode @ Parameter. Anda dapat melihat kode ini di:

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

dan contoh penggunaannya di:

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

Uraian tes format bagus di Eclipse yang saya inginkan karena ini membuat tes gagal jauh lebih mudah ditemukan! Saya mungkin akan lebih memperbaiki dan mendokumentasikan kelas selama beberapa hari / minggu ke depan. Jatuhkan '?' bagian dari URL jika Anda ingin tepi berdarah. :-)

Untuk menggunakannya, yang harus Anda lakukan adalah menyalin kelas itu (GPL v3), dan mengubah @RunWith (Parameterized.class) menjadi @RunWith (LabelledParameterized.class) dengan asumsi elemen pertama dari daftar parameter Anda adalah label yang masuk akal.

Saya tidak tahu apakah ada rilis JUnit nanti yang mengatasi masalah ini, tetapi bahkan jika mereka melakukannya, saya tidak dapat memperbarui JUnit karena semua co-developer saya juga harus memperbarui dan kami memiliki prioritas yang lebih tinggi daripada menginstal ulang. Oleh karena itu pekerjaan di kelas dapat dikompilasi oleh beberapa versi JUnit.


Catatan: ada beberapa jiggery-pokery refleksi sehingga berjalan melintasi versi JUnit yang berbeda seperti yang tercantum di atas. Versi khusus untuk JUnit 4.3.1 dapat ditemukan di sini dan, untuk JUnit 4.4 dan 4.5, di sini .

darrenp
sumber
:-) Salah satu rekan pengembang saya hari ini memiliki masalah dengan itu karena versi yang saya tunjukkan dalam pesan di atas menggunakan JUnit 4.3.1 (bukan 4.4 seperti yang saya katakan sebelumnya). Dia menggunakan JUnit 4.5.0 dan itu menyebabkan masalah. Saya akan membahas ini hari ini.
darrenp
Saya mengambil beberapa waktu untuk memahami bahwa Anda harus lulus nama tes di konstruktor, tetapi tidak menghafalnya . Terima kasih untuk kodenya!
Giraff
Bekerja dengan baik selama saya menjalankan tes dari Eclipse. Apakah ada yang punya pengalaman dengan membuatnya bekerja dengan Tugas Ant JUnit? Laporan pengujian disebutkan execute[0], execute[1] ... execute[n]dalam laporan pengujian yang dihasilkan.
Henrik Aasted Sørensen
Sangat bagus. Bekerja seperti pesona. Alangkah baiknya, jika Anda bisa menambahkan info, bahwa itu diperlukan untuk menambahkan "label String, ..." sebagai parameter pertama ke metode @ Test-dipanggil.
gia
13

Dengan Parameterizedsebagai model, saya menulis runner / suite test saya sendiri - hanya membutuhkan waktu sekitar setengah jam. Ini sedikit berbeda dari darrenp LabelledParameterizedkarena memungkinkan Anda menentukan nama secara eksplisit daripada mengandalkan parameter pertama toString().

Itu juga tidak menggunakan array karena saya benci array. :)

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

Dan sebuah contoh:

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

}
David Moles
sumber
6

dari junit4.8.2, Anda dapat membuat kelas MyParameterized Anda sendiri hanya dengan menyalin kelas Parameterized. ubah metode getName () dan testName () di TestClassRunnerForParameters.

Yliang
sumber
Saya mencoba ini tetapi tidak membantu. Saat membuat getParametersMethod kelas baru gagal.
java_enthu
2

Anda dapat membuat metode seperti

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

Meskipun saya tidak akan menggunakannya sepanjang waktu, akan berguna untuk mengetahui dengan tepat nomor tes 143.


sumber
2

Saya banyak menggunakan impor statis untuk Assert dan teman-teman, jadi mudah bagi saya untuk mendefinisikan kembali pernyataan:

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

Misalnya, Anda bisa menambahkan bidang "nama" ke kelas tes Anda, diinisialisasi dalam konstruktor, dan menampilkannya pada kegagalan tes. Cukup berikan sebagai elemen pertama array parameter Anda untuk setiap tes. Ini juga membantu memberi label pada data:

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}
    });
}
binkley
sumber
Ini bagus jika tes gagal menegaskan, tetapi ada kasus lain, seperti jika pengecualian dilemparkan yang gagal tes, atau jika tes mengharapkan pengecualian dilemparkan, itu membuat memikirkan nama overhead yang seharusnya ditangani oleh framework.
Yishai
2

Tidak ada yang bekerja untuk saya, jadi saya mendapatkan sumber untuk Parameter dan memodifikasinya membuat test runner baru. Saya tidak perlu banyak berubah tetapi ITU BEKERJA !!!

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;
}
}
Kristen
sumber
2

Solusinya adalah menangkap dan menumpuk semua Throwables ke dalam Throwable baru dengan pesan khusus yang berisi semua informasi tentang parameter. Pesan akan muncul di jejak tumpukan. Ini bekerja setiap kali tes gagal untuk semua asersi, kesalahan, dan pengecualian karena semuanya adalah subclass dari Throwable.

Kode saya terlihat seperti ini:

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

}

Jejak tumpukan tes gagal adalah:

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
mmirwaldt
sumber
0

Lihat JUnitParams seperti yang disebutkan dsaff, bekerja menggunakan semut untuk membangun deskripsi metode pengujian parameter dalam laporan html.

Ini setelah mencoba LabelledParameterized dan menemukan bahwa itu meskipun bekerja dengan gerhana itu tidak bekerja dengan semut sejauh laporan html yang bersangkutan.

Bersulang,

kuarkonium
sumber
0

Karena parameter diakses (misalnya dengan "{0}"selalu mengembalikantoString() representasi, satu solusi adalah membuat implementasi anonim dan menimpa toString()dalam setiap kasus. Misalnya:

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...
    );
}
Sina Madani
sumber