为接口的多个实现编写单个单元测试

时间:2013-04-26 13:01:22

标签: java unit-testing junit junit4 junit-runner

我有一个接口List,其实现包括Singly Linked List,Doubly,Circular等。我为Singly编写的单元测试应该对大多数双重以及循环和接口的任何其他新实现都有好处。因此,JUnit不是为每个实现重复单元测试,而是提供内置的东西,让我有一个JUnit测试并针对不同的实现运行它?

使用JUnit参数化测试我可以提供不同的实现,如Singly,doublely,circular等,但是对于每个实现,相同的对象用于执行类中的所有测试。

7 个答案:

答案 0 :(得分:52)

我可能会避免使用JUnit的参数化测试(IMHO非常笨拙地实现),只需创建一个抽象的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(); 
    }

}

这样做的好处(而不是制作一个测试所有实现的测试类)是,如果有一些特定的极端情况你想用一个实现测试,你可以添加更多的测试特定的测试子类。

答案 1 :(得分:35)

使用JUnit 4.0+,您可以使用parameterized tests

  • @RunWith(value = Parameterized.class)注释添加到测试夹具
  • 创建一个public static方法,返回Collection,并使用@Parameters对其进行注释,并将SinglyLinkedList.classDoublyLinkedList.classCircularList.class等放入其中那个集合
  • 将构造函数添加到需要Classpublic MyListTest(Class cl)的测试工具中,并将Class存储在实例变量listClass
  • setUp方法或@Before中,使用List testList = (List)listClass.newInstance();

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

答案 2 :(得分: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();
    }

答案 3 :(得分:1)

基于@dasblinkenlightthis anwser的anwser,我为我的用例提出了一个我想分享的实现。

我对实现接口IImporterService的类使用ServiceProviderPatterndifference API and SPI)。如果开发了新的接口实现,则只需更改 META-INF / services / 中的配置文件即可注册实现。

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

  

de.myapp.importer.IImporterService

此文件包含实现IImporterService的casses列表,例如

  

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

答案 4 :(得分:0)

实际上,您可以在测试类中创建一个帮助器方法,将测试List设置为依赖于参数的某个实现的实例。 与this结合使用,您应该能够获得所需的行为。

答案 5 :(得分: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;
}

答案 6 :(得分: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