JUnit 3.x中的参数化测试用例类

时间:2008-10-13 12:18:43

标签: java junit

我有一个JUnit 3.x TestCase,我希望能够参数化。我想参数化整个TestCase(包括夹具)。但是,TestSuite.addTestSuite()方法不允许传递TestCase对象,只是一个类:

   TestSuite suite = new TestSuite("suite");
   suite.addTestSuite(MyTestCase.class);

我希望能够将参数(字符串)传递给测试运行时创建的MyTestCase实例。就像现在一样,我必须为每个参数值设置一个单独的类。

我试过传递一个无关紧要的子类:

   MyTestCase testCase = new MyTestCase() {
       String getOption() {
           return "some value";
       }
   }

   suite.addTestSuite(testCase.getClass());

然而,断言失败了:

   ... MyTestSuite$1 has no public constructor TestCase(String name) or TestCase()`

有什么想法吗?我是以错误的方式攻击这个问题吗?

5 个答案:

答案 0 :(得分:3)

不是为要测试的多个/不同后端创建参数化测试用例,而是考虑使我的测试用例抽象化。 API的每个新实现都需要提供一个实现TestCase类。

如果您目前的测试方法类似于

public void testSomething() {
   API myAPI = new BlahAPI();
   assertNotNull(myAPI.something());
}

只需向TestCase添加一个抽象方法,该方法返回要使用的特定API对象。

public abstract class AbstractTestCase extends TestCase {
    public abstract API getAPIToTest();

    public void testSomething() {
       API myAPI = getAPIToTest();
       assertNotNull(myAPI.something());
    }

    public void testSomethingElse() {
       API myAPI = getAPIToTest();
       assertNotNull(myAPI.somethingElse());
    }
}

然后,您要测试的新实现的TestCase只需实现您的AbstractTestCase并提供API类的具体实现:

public class ImplementationXTestCase extends AbstractTestCase{

    public API getAPIToTest() {
        return new ImplementationX();
    }
}

然后,所有在抽象类中测试API的测试方法都会自动运行。

答案 1 :(得分:3)

好的,这是JUnit 4如何运行参数化测试的快速模型,但在JUnit 3.8.2中完成。

基本上我是子类并严重劫持TestSuite类,根据testMethods和参数的交叉产品填充测试列表。

不幸的是,我不得不从TestSuite本身复制一些辅助方法,并且一些细节并不完美,例如IDE中的测试名称在参数集之间是相同的(JUnit 4.x追加{ {1}},[0],...)。

尽管如此,在JUnit以及Eclipse中附带的文本和AWT [1]中似乎运行良好。

这是ParameterizedTestSuite,并且是使用它的参数化测试的一个(愚蠢的)示例。

(最后注意:我在编写这篇文章时考虑到了Java 5,如果需要的话应该很容易适应1.4)

ParameterizedTestSuite.java:

TestRunner

ParameterizedTest.java:

package junit.parameterized;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class ParameterizedTestSuite extends TestSuite {

    public ParameterizedTestSuite(
            final Class<? extends TestCase> testCaseClass,
            final Collection<Object[]> parameters) {

        setName(testCaseClass.getName());

        final Constructor<?>[] constructors = testCaseClass.getConstructors();
        if (constructors.length != 1) {
            addTest(warning(testCaseClass.getName()
                    + " must have a single public constructor."));
            return;
        }

        final Collection<String> names = getTestMethods(testCaseClass);

        final Constructor<?> constructor = constructors[0];
        final Collection<TestCase> testCaseInstances = new ArrayList<TestCase>();
        try {
            for (final Object[] objects : parameters) {
                for (final String name : names) {
                    TestCase testCase = (TestCase) constructor.newInstance(objects);
                    testCase.setName(name);
                    testCaseInstances.add(testCase);
                }
            }
        } catch (IllegalArgumentException e) {
            addConstructionException(e);
            return;
        } catch (InstantiationException e) {
            addConstructionException(e);
            return;
        } catch (IllegalAccessException e) {
            addConstructionException(e);
            return;
        } catch (InvocationTargetException e) {
            addConstructionException(e);
            return;
        }


        for (final TestCase testCase : testCaseInstances) {
            addTest(testCase);
        }       
    }
    private Collection<String> getTestMethods(
            final Class<? extends TestCase> testCaseClass) {
        Class<?> superClass= testCaseClass;
        final Collection<String> names= new ArrayList<String>();
        while (Test.class.isAssignableFrom(superClass)) {
            Method[] methods= superClass.getDeclaredMethods();
            for (int i= 0; i < methods.length; i++) {
                addTestMethod(methods[i], names, testCaseClass);
            }
            superClass = superClass.getSuperclass();
        }
        return names;
    }
    private void addTestMethod(Method m, Collection<String> names, Class<?> theClass) {
        String name= m.getName();
        if (names.contains(name))
            return;
        if (! isPublicTestMethod(m)) {
            if (isTestMethod(m))
                addTest(warning("Test method isn't public: "+m.getName()));
            return;
        }
        names.add(name);
    }

    private boolean isPublicTestMethod(Method m) {
        return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
     }

    private boolean isTestMethod(Method m) {
        String name= m.getName();
        Class<?>[] parameters= m.getParameterTypes();
        Class<?> returnType= m.getReturnType();
        return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE);
     }

    private void addConstructionException(Exception e) {
        addTest(warning("Instantiation of a testCase failed "
                + e.getClass().getName() + " " + e.getMessage()));
    }

}

注意:package junit.parameterized; import java.util.Arrays; import java.util.Collection; import junit.framework.Test; import junit.framework.TestCase; import junit.parameterized.ParameterizedTestSuite; public class ParameterizedTest extends TestCase { private final int value; private int evilState; public static Collection<Object[]> parameters() { return Arrays.asList( new Object[] { 1 }, new Object[] { 2 }, new Object[] { -2 } ); } public ParameterizedTest(final int value) { this.value = value; } public void testMathPow() { final int square = value * value; final int powSquare = (int) Math.pow(value, 2) + evilState; assertEquals(square, powSquare); evilState++; } public void testIntDiv() { final int div = value / value; assertEquals(1, div); } public static Test suite() { return new ParameterizedTestSuite(ParameterizedTest.class, parameters()); } } 变量就是为了表明所有测试实例都应该是不同的,并且它们之间没有共享状态。

答案 2 :(得分:2)

如果这是Java 5或更高版本,您可能需要考虑切换到JUnit 4,它支持内置的参数化测试用例。

答案 3 :(得分:1)

  

一些细节并不完美,例如IDE中测试的名称在参数集之间是相同的(JUnit 4.x附加[0],[1],...)。

要解决此问题,您只需要覆盖getName()并更改测试用例类中的构造函数:

 private String displayName;

 public ParameterizedTest(final int value) {
     this.value = value;
     this.displayName = Integer.toString(value);
 }

 @Override
 public String getName() {
     return super.getName() + "[" + displayName + "]";
 }

答案 4 :(得分:1)

对于Android项目,我们编写了一个名为Burst的库,用于测试参数化。例如

public class ParameterizedTest extends TestCase {
  enum Drink { COKE, PEPSI, RC_COLA }

  private final Drink drink;

  // Nullary constructor required by Android test framework
  public ConstructorTest() {
    this(null);
  }

  public ConstructorTest(Drink drink) {
    this.drink = drink;
  }

  public void testSomething() {
    assertNotNull(drink);
  }
}

由于你没有使用Android,因此不是你问题的答案,但很多仍然使用JUnit 3的项目都是这样做的,因为Android的测试框架需要它,所以我希望其他一些读者会觉得这很有帮助。