首页 文章

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

提问于
浏览
47

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

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

6 回答

  • 1

    扩展第一个答案,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;
    }
    
  • 0

    使用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();

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

  • 0

    基于@dasblinkenlightthis anwser的anwser,我想出了一个我想要分享的用例的实现 .

    我将ServiceProviderPatterndifference API and SPI)用于实现接口 IImporterService 的类 . 如果开发了新的接口实现,则只需更改 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);
    }
    
  • 34

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

  • 3

    我'd probably avoid 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(); 
        }
    
    }
    

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

  • 47

    我知道这是旧的,但是我学会了以稍微不同的变化做到这一点,这很好地工作,你可以将 @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();
        }
    

相关问题