我有一个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()`
有什么想法吗?我是以错误的方式攻击这个问题吗?
答案 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的测试框架需要它,所以我希望其他一些读者会觉得这很有帮助。