Параметризованные классы тестовых примеров в JUnit 3.x - PullRequest
4 голосов
/ 13 октября 2008

У меня есть 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 ]

3 голосов
/ 13 октября 2008

Вместо того, чтобы создавать параметризованный тестовый набор для нескольких / разных бэкэндов, с которыми вы хотите тестировать, я бы хотел сделать мои тестовые примеры абстрактными. Каждая новая реализация вашего 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 в абстрактном классе, запускаются автоматически.

3 голосов
/ 04 февраля 2009

Хорошо, вот быстрый макет того, как JUnit 4 выполняет параметризованные тесты, но сделано в JUnit 3.8.2.

По сути, я создаю подкласс и плохо угоняю класс TestSuite, чтобы заполнить список тестов в соответствии с перекрестным произведением testMethods и параметров.

К сожалению, мне пришлось скопировать несколько вспомогательных методов из самого TestSuite, и некоторые детали не идеальны, например, имена тестов в IDE одинаковы для наборов параметров (добавляется JUnit 4.x [0], [1], ...).

Тем не менее, это выглядит нормально в тексте и на AWT TestRunner s, которые поставляются с 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 здесь только для того, чтобы показать, что все тестовые экземпляры отличаются, как и должно быть, и что между ними нет общего состояния.

2 голосов
/ 13 октября 2008

Если это Java 5 или выше, вы можете рассмотреть возможность перехода на JUnit 4, который имеет встроенную поддержку параметризованных тестовых примеров.

1 голос
/ 21 октября 2014

Для проектов 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 требует этого, поэтому я надеюсь, что некоторые другие читатели найдут это полезным.

1 голос
/ 21 февраля 2011

некоторые детали не идеальны, например, имена тестов в 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 + "]";
 }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...