是否可以通过单个测试来测试多个类

时间:2012-08-02 13:24:54

标签: java unit-testing junit

使用JUnit4,我想要做的是能够测试一组不同的java项目,这些项目都做同样的事情,但不必写出测试用例来测试每个项目,我想知道是否可能编写一个可以在多个类上运行的单个测试?

如果使用JUnit4无法做到这一点,是否可以通过其他方式执行此操作?

我知道这不对,但这只是为了简要介绍一下我的目的:

@Test
public void test(Class insertClassNameHere, Method nameOfMethod){
    Class insertClassNameHere = new Class();
    assertEquals(insertClassNameHere.nameOfMethod(),1);
}

4 个答案:

答案 0 :(得分:3)

您可以使用JUnit的@Parameterized。

@RunWith(Parameterized.class)
public class BehaviorTest {
    @Parameters
    public static Collection<Object[]> classesAndMethods() {
        List<Object[]> list = new ArrayList<Object[]>();
        list.add(new Object[]{ Foo.class, Foo.class.getMethod("foo") });
        return list;
    }

    private Class clazz;
    private Method method;

    public BehaviorTest(Class clazz, Method method) {
         this.clazz = clazz;
         this.method = method;
    }

    @Test
    public void testBehavior() {
        // Do stuff
    }
}

答案 1 :(得分:1)

怎么样......

for(Class cls: new Class[] { Class1.class, ... } )
    assertEquals(1, nameOfMethod.invoke(cls.newInstance()));

答案 2 :(得分:1)

当然。没有技术限制。但这不可取。有一个很好的理由,他们称之为单位测试。

如果您要对多个类使用相同的测试,那么这通常是您的应用程序中存在重复代码的提示。


测试类是普通的java类,因此您可以使用继承来实现您的想法:包含实际测试的基础(测试)类和执行某些设置,iaw,初始化测试的子(测试)类与要测试的不同类的实例。您甚至可以使用反射来创建实例和方法对象。

这可能是一个解决方案,如果你必须为你预期相同任务的几十个不同类的作业准备单元测试。但对于大多数其他情况,我宁愿复制测试方法,然后创建复杂的测试类。

答案 3 :(得分:0)

很抱歉这么晚才进入这个阶段。由于有类似的需求,我开发了许多演示和培训,以演示重构后给定课程的变化。因此,测试是相同的,但类本身会发生变化。每个版本都包含在不同的包中。班级名称保持不变。这听起来与OP面临的相似。

我发现参数化解决方案很麻烦。我用了两个解决方案。它们都是用Groovy编写的,但下面的第二个例子应该可以作为纯java解决方案实现。

这是第一个:

public class ItemTest {
    def defaultConstructedClasses = [
         new com.groovifyingjava.essentialgroovification.Item()
        ,new com.groovifyingjava.rewriteequals.Item()
        ,new com.groovifyingjava.removesemicolons.Item()
        ,new com.groovifyingjava.removeparentheses.Item()
        ,new com.groovifyingjava.removeaccessors.Item()
        ,new com.groovifyingjava.removeconstructors.Item()
        ,new com.groovifyingjava.removeimports.Item()
        ,new com.groovifyingjava.removereturn.Item()
        ,new com.groovifyingjava.clarifyidentityequality.Item()
        ,new com.groovifyingjava.coercetypes.Item()
        ,new com.groovifyingjava.defaultaccessmodifiers.Item()
        ,new com.groovifyingjava.eachiteration.Item()
        ,new com.groovifyingjava.fieldaccessnotation.Item()
        ,new com.groovifyingjava.namedparameters.Item()
        ,new com.groovifyingjava.optionaldatatyping.Item()
        ,new com.groovifyingjava.safelynavigate.Item()
        ,new com.groovifyingjava.simplifylistmapsetcreation.Item()
        ,new com.groovifyingjava.stringinterpolation.Item()
        ,new com.groovifyingjava.useelvisoperator.Item()
        ,new com.groovifyingjava.useequalsoperator.Item()
        ,new com.groovifyingjava.usemathoperators.Item()
        ,new com.groovifyingjava.useprintln.Item()
    ]

    def overloadedConstructorArgs = [itemId: "14-101", manufacturer: "Raleigh", model: "Superbe Roadster", cost: 179.89, quantityOnHand: 10]

    def overloadedConstructedClasses = [
         new com.groovifyingjava.essentialgroovification.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.rewriteequals.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.removesemicolons.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.removeparentheses.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.removeaccessors.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.removeconstructors.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.removeimports.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.removereturn.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.clarifyidentityequality.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.coercetypes.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.defaultaccessmodifiers.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.eachiteration.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.fieldaccessnotation.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.namedparameters.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.optionaldatatyping.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.safelynavigate.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.simplifylistmapsetcreation.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.stringinterpolation.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.useelvisoperator.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.useequalsoperator.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.usemathoperators.Item(overloadedConstructorArgs)
        ,new com.groovifyingjava.useprintln.Item(overloadedConstructorArgs)
    ]

    @Test public void canCreateDefaultInstance() {
        for(def item in defaultConstructedClasses) {
            assertNull "Default construction of class ${item.class.name} failed to properly initialize field.", item.itemId
            assertNull "Default construction of class ${item.class.name} failed to properly initialize field.", item.manufacturer
            assertNull "Default construction of class ${item.class.name} failed to properly initialize field.", item.model
            assertNull "Default construction of class ${item.class.name} failed to properly initialize field.", item.cost
            assertEquals "Default construction of class ${item.class.name} failed to properly initialize field.", 0, item.quantityOnHand
            assertEquals "Default construction of class ${item.class.name} failed to properly initialize field.", BigDecimal.ZERO, item.inventoryValue as BigDecimal
        }
    }

    @Test public void canCreateInstancesFromOverloadedConstructor() {
        for(def item in overloadedConstructedClasses) {
            assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.","14-101", item.itemId
            assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.", "Raleigh", item.manufacturer
            assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.", "Superbe Roadster", item.model
            assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.", 179.89, item.cost
            assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.", 10, item.quantityOnHand
            assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.", new BigDecimal("1798.90"), item.inventoryValue as BigDecimal
        }
    }
}

这很好用,很容易实现。它使用Groovy duck typing来测试每个类。缺点是Eclipse中的junit控制台将显示最后一次测试运行。这可能不是什么大问题。此外,它会在失败的测试中停止执行。请注意,我在断言消息字符串中包含类名。这样可以很容易地识别失败的类。

第二个版本需要一个抽象测试类,并且每个包中都有一个测试类,对应于被测试类的每个包。这些只不过是存根,但允许您谨慎地或在套件内运行测试。

public abstract class CommonItemTest {
    def overloadedConstructorArgs = [itemId: "14-101", manufacturer: "Raleigh", model: "Superbe Roadster", cost: 179.89, quantityOnHand: 10]

    @Test public void canCreateDefaultInstance() {
        def item = defaultConstructedClass

        assertNull "Default construction of class ${item.class.name} failed to properly initialize field. Expected itemId to be null.", item.itemId
        assertNull "Default construction of class ${item.class.name} failed to properly initialize field. Expected manufacturer to be null.", item.manufacturer
        assertNull "Default construction of class ${item.class.name} failed to properly initialize field. Expected model to be null.", item.model
        assertNull "Default construction of class ${item.class.name} failed to properly initialize field. Expected cost to be null.", item.cost
        assertEquals "Default construction of class ${item.class.name} failed to properly initialize field. Expected quantityOnHand to be zero.", 0, item.quantityOnHand
        assertEquals "Default construction of class ${item.class.name} failed to properly initialize field. Expected inventoryValue to be zero.", BigDecimal.ZERO, item.inventoryValue as BigDecimal
    }

    @Test public void canCreateInstancesFromOverloadedConstructor() {
        def item = overloadedConstructedClass

        assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.","14-101", item.itemId
        assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.", "Raleigh", item.manufacturer
        assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.", "Superbe Roadster", item.model
        assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.", 179.89, item.cost
        assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.", 10, item.quantityOnHand
        assertEquals "Instantiation of class ${item.class.name} using overloaded constructor failed to properly initialize field.", new BigDecimal("1798.90"), item.inventoryValue as BigDecimal
    }

    abstract Object getDefaultConstructedClass();
    abstract Object getOverloadedConstructedClass();
}

和实施......

public class ItemTest extends CommonItemTest {
    public Object getDefaultConstructedClass() {
        return new Item()
    }
    public Object getOverloadedConstructedClass() {
        return new Item(overloadedConstructorArgs)
    }
}