java 为接口的多个实现编写单个单元测试
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16237135/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Writing a single unit test for multiple implementations of an interface
提问by ChrisOdney
I have an interface List
whose implementations include Singly Linked List, Doubly, Circular etc. The unit tests I wrote for Singly should do good for most of Doubly as well as Circular and any other new implementation of the interface. So instead of repeating the unit tests for every implementation, does JUnit offer something inbuilt which would let me have one JUnit test and run it against different implementations?
我有一个接口,List
它的实现包括单链表、双链表、循环等。我为单链编写的单元测试应该对大多数双链表以及循环和接口的任何其他新实现都有好处。因此,不是为每个实现重复单元测试,JUnit 是否提供了一些内置的东西,让我有一个 JUnit 测试并针对不同的实现运行它?
Using JUnit parameterized tests I can supply different implementations like Singly, doubly, circular etc but for each implementation the same object is used to execute all the tests in the class.
使用 JUnit 参数化测试,我可以提供不同的实现,如 Singly、double、circular 等,但对于每个实现,使用相同的对象来执行类中的所有测试。
采纳答案by dasblinkenlight
With JUnit 4.0+ you can use parameterized tests:
使用 JUnit 4.0+,您可以使用参数化测试:
- Add
@RunWith(value = Parameterized.class)
annotation to your test fixture - Create a
public static
method returningCollection
, annotate it with@Parameters
, and putSinglyLinkedList.class
,DoublyLinkedList.class
,CircularList.class
, etc. into that collection - Add a constructor to your test fixture that takes
Class
:public MyListTest(Class cl)
, and store theClass
in an instance variablelistClass
- In the
setUp
method or@Before
, useList testList = (List)listClass.newInstance();
@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();
With the above setup in place, the parameterized runner will make a new instance of your test fixture MyListTest
for each subclass that you provide in the @Parameters
method, letting you exercise the same test logic for every subclass that you need to test.
完成上述设置后,参数化运行器将为MyListTest
您在@Parameters
方法中提供的每个子类创建一个新的测试装置实例,让您对需要测试的每个子类执行相同的测试逻辑。
回答by gustafc
I'd probably avoid JUnit's parameterized tests (which IMHO are pretty clumsily implemented), and just make an abstract List
test class which could be inherited by tests implementations:
我可能会避免使用 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(){ /* ... */ }
}
The different implementations then get their own concrete classes:
不同的实现然后得到它们自己的具体类:
class SinglyLinkedListTest extends ListTestBase<SinglyLinkedList> {
@Override
protected SinglyLinkedList createInstance(){
return new SinglyLinkedList();
}
}
class DoublyLinkedListTest extends ListTestBase<DoublyLinkedList> {
@Override
protected DoublyLinkedList createInstance(){
return new DoublyLinkedList();
}
}
The nice thing about doing it this way (instead of making one test class which tests all implementations) is that if there are some specific corner cases you'd like to test with one implementation, you can just add more tests to the specific test subclass.
这样做的好处(而不是制作一个测试所有实现的测试类)是,如果您想用一个实现测试某些特定的极端情况,您可以向特定的测试子类添加更多测试.
回答by mcnichol
I know this is old, but I learned to do this in a slightly different variation which works nicely wherein you can apply the @Parameter
to a field member to inject the values.
我知道这是旧的,但我学会了以稍微不同的变体来做到这一点,该变体效果很好,您可以将 应用于@Parameter
字段成员以注入值。
It's just a little cleaner in my opinion.
在我看来,它只是更清洁一点。
@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();
}
回答by Matthias Holdorf
Based on the anwser of @dasblinkenlightand thisanwser I came up with an implementation for my use case that I'd like to share.
基于@dasblinkenlight 的anwser和这个anwser,我想出了一个我想分享的用例的实现。
I use the ServiceProviderPattern(difference API and SPI) for classes that implement the interface IImporterService
. If a new implementation of the interface is developed, only a configuration file in META-INF/services/needs to be altered to register the implementation.
我将ServiceProviderPattern(不同的 API 和 SPI)用于实现该接口的类IImporterService
。如果开发了新的接口实现,只需更改META-INF/services/ 中的配置文件即可注册该实现。
The file in META-INF/services/is named after the fully qualified class name of the service interface (IImporterService
), e.g.
META-INF/services/ 中的文件以服务接口的全限定类名 ( IImporterService
)命名,例如
de.myapp.importer.IImporterService
de.myapp.importer.IImporterService
This file contains a list of casses that implement IImporterService
, e.g.
此文件包含实现 的案例列表IImporterService
,例如
de.myapp.importer.impl.OfficeOpenXMLImporter
de.myapp.importer.impl.OfficeOpenXMLImporter
The factory class ImporterFactory
provides clients with concrete implementations of the interface.
工厂类ImporterFactory
为客户端提供接口的具体实现。
The ImporterFactory
returns a list of all implementations of the interface, registered via the ServiceProviderPattern. The setUp()
method ensures that a new instance is used for each test case.
在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() {
}
}
The ImporterFactory.INSTANCE.getImplementations()
method looks like the following:
该ImporterFactory.INSTANCE.getImplementations()
方法如下所示:
public List<IImporterService> getImplementations() {
return (List<IImporterService>) GenericServiceLoader.INSTANCE.locateAll(IImporterService.class);
}
回答by LionC
You could actually create a helper method in your test class that sets up your test List
to be an instance of one of your implementations dependent on an argument.
In combination with thisyou should be able to get the behaviour you want.
您实际上可以在您的测试类中创建一个辅助方法,将您的测试设置List
为依赖于参数的实现之一的实例。与此相结合,您应该能够获得所需的行为。
回答by kmader
Expanding on the first answer, the Parameter aspects of JUnit4 work very well. Here is the actual code I used in a project testing filters. The class is created using a factory function (getPluginIO
) and the function getPluginsNamed
gets all PluginInfo classes with the name using SezPoz and annotations to allow for new classes to be automatically detected.
扩展第一个答案,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
Note it is important (I personally have no idea why it works this way) to have the collection as a collection of arrays of the actual parameter fed to the constructor, in this case a class called PluginInfo. The wrapCollection static function performs this task.
请注意,将集合作为提供给构造函数的实际参数数组的集合很重要(我个人不知道为什么会这样),在这种情况下是一个名为 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;
}