我有一个接口List
,它的实现包括单向链表、双向链表、循环等。我为单向链表编写了单元测试,这些测试对于大多数双向链表、循环以及接口的任何新实现都适用。因此,为了避免为每个实现重复编写单元测试,JUnit提供了内置模块,让我只需编写一个JUnit测试,就可以针对不同的实现运行它吗?
使用JUnit参数化测试,我可以提供不同的实现,如单向链表、双向链表、循环等,但对于每个实现,同一个对象被用于执行类中的所有测试。
我有一个接口List
,它的实现包括单向链表、双向链表、循环等。我为单向链表编写了单元测试,这些测试对于大多数双向链表、循环以及接口的任何新实现都适用。因此,为了避免为每个实现重复编写单元测试,JUnit提供了内置模块,让我只需编写一个JUnit测试,就可以针对不同的实现运行它吗?
使用JUnit参数化测试,我可以提供不同的实现,如单向链表、双向链表、循环等,但对于每个实现,同一个对象被用于执行类中的所有测试。
我建议避免使用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();
}
}
使用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
方法或@Before
中,使用List testList = (List)listClass.newInstance();
通过以上设置,参数化运行程序将为您提供@Parameters
方法中提供的每个子类创建一个新的测试装置MyListTest
实例,让您能够对需要测试的每个子类执行相同的测试逻辑。
我知道这可能有点老旧,但是我学会了一种稍微不同的方法来实现这个功能,它可以很好地应用 @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();
}
基于@dasblinkenlight和此答案的建议,我开发了一种适用于我的使用情况的实现,并希望分享出来。
我使用 ServiceProviderPattern (API 和 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);
}
对第一个答案进行扩展,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
/**
* 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;
}
我曾经遇到过完全相同的问题,以下是我的解决方法,借助于 JUnit
参数化测试(基于 @dasblinkenlight 的答案)。
@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;
}
}
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