61

我有一个接口List,其实现包括单链表、Doubly、Circular 等。我为 Singly 编写的单元测试应该适用于大多数 Doubly 以及 Circular 和该接口的任何其他新实现。因此,不是为每个实现重复单元测试,JUnit 是否提供了一些内置的东西,让我有一个 JUnit 测试并针对不同的实现运行它?

使用 JUnit 参数化测试,我可以提供不同的实现,如单、双、循环等,但对于每个实现,相同的对象用于执行类中的所有测试。

4

7 回答 7

66

我可能会避免使用 JUnit 的参数化测试(恕我直言,它的实现非常笨拙),而只是创建一个List可以由测试实现继承的抽象测试类:

public abstract class ListTestBase<T extends List> {

    private T instance;

    protected abstract T createInstance();

    @Before 
    public void setUp() {
        instance = createInstance();
    }

    @Test
    public void testOneThing(){ /* ... */ }

    @Test
    public void testAnotherThing(){ /* ... */ }

}

然后不同的实现得到它们自己的具体类:

class SinglyLinkedListTest extends ListTestBase<SinglyLinkedList> {

    @Override
    protected SinglyLinkedList createInstance(){ 
        return new SinglyLinkedList(); 
    }

}

class DoublyLinkedListTest extends ListTestBase<DoublyLinkedList> {

    @Override
    protected DoublyLinkedList createInstance(){ 
        return new DoublyLinkedList(); 
    }

}

这样做的好处(而不是创建一个测试所有实现的测试类)是,如果您想使用一种实现来测试某些特定的极端情况,您可以向特定的测试子类添加更多测试.

于 2013-04-26T13:12:19.250 回答
40

使用 JUnit 4.0+,您可以使用参数化测试

  • 为您的测试夹具添加@RunWith(value = Parameterized.class)注释
  • 创建一个public static返回方法Collection,用 注释它@Parameters,然后将SinglyLinkedList.class, DoublyLinkedList.class,CircularList.class等放入该集合中
  • 向您的测试夹具添加一个构造函数,该构造函数采用Class: public MyListTest(Class cl),并将 存储Class在实例变量中listClass
  • setUp方法 or@Before中,使用List testList = (List)listClass.newInstance();

通过上述设置,参数化运行器将为MyListTest您在@Parameters方法中提供的每个子类创建一个新的测试夹具实例,让您为需要测试的每个子类执行相同的测试逻辑。

于 2013-04-26T13:11:33.743 回答
3

我知道这很旧,但我学会了以稍微不同的变体来做到这一点,这种变体效果很好,您可以将 应用于@Parameter字段成员以注入值。

在我看来,它只是更干净一点。

@RunWith(Parameterized.class)
public class MyTest{

    private ThingToTest subject;

    @Parameter
    public Class clazz;

    @Parameters(name = "{index}: Impl Class: {0}")
    public static Collection classes(){
        List<Object[]> implementations = new ArrayList<>();
        implementations.add(new Object[]{ImplementationOne.class});
        implementations.add(new Object[]{ImplementationTwo.class});

        return implementations;
    }

    @Before
    public void setUp() throws Exception {
        subject = (ThingToTest) clazz.getConstructor().newInstance();
    }
于 2017-12-28T23:16:22.170 回答
2

基于@dasblinkenlight 的anwser这个anwser,我想出了一个我想分享的用例的实现。

我对实现接口的类使用ServiceProviderPatternAPI 和 SPI 的区别IImporterService) 。如果开发了新的接口实现,只需修改META-INF/services/中的配置文件即可注册实现。

META-INF/services/中的文件以服务接口的全限定类名 ( ) 命名IImporterService,例如

de.myapp.importer.IImporterService

该文件包含实现的案例列表IImporterService,例如

de.myapp.importer.impl.OfficeOpenXMLImporter

工厂类ImporterFactory为客户提供接口的具体实现。


ImporterFactory返回通过ServiceProviderPattern注册的接口的所有实现的列表。该setUp()方法确保为每个测试用例使用一个新实例。

@RunWith(Parameterized.class)
public class IImporterServiceTest {
    public IImporterService service;

    public IImporterServiceTest(IImporterService service) {
        this.service = service;
    }

    @Parameters
    public static List<IImporterService> instancesToTest() {
        return ImporterFactory.INSTANCE.getImplementations();
    }

    @Before
    public void setUp() throws Exception {
        this.service = this.service.getClass().newInstance();
    }

    @Test
    public void testRead() {
    }
}

ImporterFactory.INSTANCE.getImplementations()方法如下所示:

public List<IImporterService> getImplementations() {
    return (List<IImporterService>) GenericServiceLoader.INSTANCE.locateAll(IImporterService.class);
}
于 2016-07-19T12:04:37.060 回答
0

您实际上可以在测试类中创建一个辅助方法,将您的测试设置List为依赖于参数的实现之一的实例。结合这个,你应该能够得到你想要的行为。

于 2013-04-26T13:12:30.417 回答
0

扩展第一个答案,JUnit4 的参数方面工作得很好。这是我在项目测试过滤器中使用的实际代码。该类是使用工厂函数 ( getPluginIO) 创建的,该函数getPluginsNamed使用 SezPoz 和注释获取所有具有名称的 PluginInfo 类,以允许自动检测新类。

@RunWith(value=Parameterized.class)
public class FilterTests {
 @Parameters
 public static Collection<PluginInfo[]> getPlugins() {
    List<PluginInfo> possibleClasses=PluginManager.getPluginsNamed("Filter");
    return wrapCollection(possibleClasses);
 }
 final protected PluginInfo pluginId;
 final IOPlugin CFilter;
 public FilterTests(final PluginInfo pluginToUse) {
    System.out.println("Using Plugin:"+pluginToUse);
    pluginId=pluginToUse; // save plugin settings
    CFilter=PluginManager.getPluginIO(pluginId); // create an instance using the factory
 }
 //.... the tests to run

请注意,将集合作为实际参数的数组的集合提供给构造函数是很重要的(我个人不知道它为什么会这样工作),在这种情况下是一个名为 PluginInfo 的类。wrapCollection 静态函数执行此任务。

/**
 * Wrap a collection into a collection of arrays which is useful for parameterization in junit testing
 * @param inCollection input collection
 * @return wrapped collection
 */
public static <T> Collection<T[]> wrapCollection(Collection<T> inCollection) {
    final List<T[]> out=new ArrayList<T[]>();
    for(T curObj : inCollection) {
        T[] arr = (T[])new Object[1];
        arr[0]=curObj;
        out.add(arr);
    }
    return out;
}
于 2014-01-15T19:44:27.657 回答
0

我遇到了完全相同的问题,这是我借助 JUnit参数化测试的方法(基于@dasblinkenlight 的回答)。

  1. 为所有测试类创建一个基类:
@RunWith(value = Parameterized.class)
public class ListTestUtil {
    private Class<?> listClass = null;

    public ListTestUtil(Class<?> listClass) {
        this.listClass = listClass;
    }

    /**
     * @return a {@link Collection} with the types of the {@link List} implementations.
     */
    @Parameters
    public static Collection<Class<?>> getTypesData() {
        return List.of(MySinglyLinkedList.class, MyArrayList.class);
    }

    public <T> List<Integer> initList(Object... elements) {
        return initList(Integer.class, elements);
    }

    @SuppressWarnings("unchecked")
    public <T> List<T> initList(Class<T> type, Object... elements) {
        List<T> myList = null;
        try {
            myList = (List<T>) listClass.getDeclaredConstructor().newInstance();
            for (Object el : elements)
                myList.add(type.cast(el));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return myList;
    }
}
  1. 包含测试用例的类扩展ListTestUtil,您可以在initList(...)任何地方使用:
public class AddTest extends ListTestUtil {
    public AddTest(Class<?> cl) {
        super(cl);
    }

    @Test
    public void test1() {
        List<Integer> myList = initList(1, 2, 3);
        // List<Integer> myList = initList(Strng.class, "a", "b", "c");
        ...
        System.out.println(myList.getClass());
    }
}

输出证明测试被调用了两次——列表的每个实现一次:

class java.data_structures.list.MySinglyLinkedList
class java.data_structures.list.MyArrayList
于 2020-08-18T13:40:37.727 回答