У меня есть 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 has no public constructor TestCase(String name) or TestCase()`
Есть идеи? Неправильно ли я подхожу к проблеме?
Я тестирую уровень API, который может работать с несколькими внутренними реализациями. Я хочу провести один и тот же тест для всех определенных серверных ВМ (в настоящее время только два, но это изменится).




Если это Java 5 или выше, вы можете рассмотреть возможность перехода на JUnit 4, который имеет встроенную поддержку параметризованных тестовых случаев.
Да, я знаю. К сожалению, экспорт Ant Build File не поддерживает JUnit 4.x, поэтому мне приходится выбирать автоматические сборки и JUnit 4.x :-(
Я про Затмение, забыл сказать.
Ах хорошо. Мы не используем здесь Ant, но мы используем Eclipse и JUnit 4.x.
У меня такая же ситуация, кроме Android. Android поддерживает только JUnit 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 в абстрактном классе, запускаются автоматически.
На самом деле это то, что я делаю прямо сейчас, и это нормально, пока у меня есть только один или два тестовых примера, но для каждого нового тестового примера мне нужен один класс на бэкэнд, который не масштабируется (в конечном итоге количество бэкэндов будет вроде 15-20).
Возможно ли, чтобы все N тестовых примеров относились к одному и тому же методу для возврата конкретной реализации? Возможно, тогда каждая реализация могла бы просто реализовать этот единственный метод. Но я предполагаю, что вы можете тестировать и другие классы в API ... что делает его более сложным
Хорошо, вот быстрый макет того, как JUnit 4 запускает параметризованные тесты, но выполненный в JUnit 3.8.2.
В основном я создаю подклассы и плохо захватываю класс TestSuite, чтобы заполнить список тестов в соответствии с перекрестным произведением testMethods и параметров.
К сожалению, мне пришлось скопировать пару вспомогательных методов из самого TestSuite, и некоторые детали не идеальны, например, имена тестов в IDE одинаковы для всех наборов параметров (JUnit 4.x добавляет [0], [1], ...).
Тем не менее, похоже, что это нормально работает в тексте и AWT TestRunner, которые поставляются с JUnit, а также в Eclipse.
Вот ParameterizedTestSuite и ниже (глупый) пример параметризованного теста с его использованием.
(последнее замечание: я написал это с учетом Java 5, должен будет тривиально адаптироваться к 1.4, если необходимо)
ParameterizedTestSuite.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()));
}
}
ParameterizedTest.java:
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());
}
}
Примечание: переменная evilState предназначена только для того, чтобы показать, что все тестовые экземпляры разные, какими они должны быть, и что между ними нет общего состояния.
a few details are not perfect, such as the names of the tests in the IDE being the same across parameter sets (JUnit 4.x appends [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 + "]";
}
Для проектов Android мы написали библиотеку под названием Лопаться для параметризации тестов. Например
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, поэтому я надеюсь, что некоторые другие читатели сочтут это полезным.
Чтобы ответить: "Неправильно ли я подхожу к проблеме?" Было бы полезно узнать, почему вам нужен параметризованный тестовый пример ... возможно, есть другой способ решить вашу проблему.