Как мне проверить приватную функцию или класс, который имеет закрытые методы, поля или внутренние классы? - PullRequest
2484 голосов
/ 29 августа 2008

Как мне выполнить модульное тестирование (используя xUnit) класс, который имеет внутренние закрытые методы, поля или вложенные классы? Или функция, которая становится закрытой, имея внутреннюю связь (static в C / C ++) или находится в закрытом ( анонимном ) пространстве имен?

Кажется плохим изменить модификатор доступа для метода или функции просто для того, чтобы можно было запустить тест.

Ответы [ 48 ]

13 голосов
/ 12 сентября 2008

Если вы используете JUnit, взгляните на junit-addons . Он может игнорировать модель безопасности Java и получать доступ к закрытым методам и атрибутам.

12 голосов
/ 30 августа 2016

Я бы посоветовал вам немного реорганизовать свой код. Когда вам нужно задуматься об использовании рефлексии или чего-то другого, просто для тестирования вашего кода, с вашим кодом что-то не так.

Вы упомянули разные типы проблем. Давайте начнем с частных полей. В случае приватных полей я бы добавил новый конструктор и вставил в него поля. Вместо этого:

public class ClassToTest {

    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

Я бы использовал это:

public class ClassToTest {

    private final String first;
    private final List<String> second;

    public ClassToTest() {
        this("first", new ArrayList<>());
    }

    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

Это не будет проблемой даже с некоторым устаревшим кодом. Старый код будет использовать пустой конструктор, и, если вы спросите меня, рефакторинговый код будет выглядеть чище, и вы сможете ввести необходимые значения в тест без отражения.

Теперь о частных методах. По моему личному опыту, когда вам нужно заглушить приватный метод для тестирования, тогда этот метод не имеет ничего общего с этим классом. В этом случае обычным шаблоном будет обернуть внутри интерфейса, например Callable, и затем вы передадите этот интерфейс также в конструкторе (с помощью этого трюка с несколькими конструкторами):

public ClassToTest() {
    this(...);
}

public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

В основном все, что я написал, выглядит как шаблон внедрения зависимостей. По моему личному опыту, это действительно полезно при тестировании, и я думаю, что этот вид кода чище и его будет легче поддерживать. Я бы сказал то же самое о вложенных классах. Если вложенный класс содержит тяжелую логику, было бы лучше, если бы вы переместили его как закрытый класс пакета и внедрили его в класс, который в этом нуждается.

Есть также несколько других шаблонов проектирования, которые я использовал при рефакторинге и обслуживании устаревшего кода, но все это зависит от случаев тестирования вашего кода. Использование рефлексии по большей части не является проблемой, но когда у вас есть корпоративное приложение, которое тщательно тестируется и тесты запускаются перед каждым развертыванием, все становится очень медленным (это просто раздражает, и мне не нравятся такие вещи).

Существует также инъекция сеттера, но я бы не рекомендовал его использовать. Я лучше придерживаюсь конструктора и инициализирую все, когда это действительно необходимо, оставляя возможность для внедрения необходимых зависимостей.

12 голосов
/ 03 мая 2014

Закрытый метод доступен только в том же классе. Таким образом, нет способа протестировать «закрытый» метод целевого класса из любого тестового класса. Выход в том, что вы можете выполнить модульное тестирование вручную или изменить свой метод с «частного» на «защищенный».

И тогда защищенный метод может быть доступен только в том же пакете, где определен класс. Итак, тестирование защищенного метода целевого класса означает, что мы должны определить ваш тестовый класс в том же пакете, что и целевой класс.

Если все вышеперечисленное не соответствует вашим требованиям, используйте способ отражения для доступа к приватному методу.

11 голосов
/ 10 сентября 2008

Как указывалось выше, хороший способ - проверить их через общедоступные интерфейсы.

Если вы сделаете это, рекомендуется использовать инструмент покрытия кода (например, Эмму), чтобы увидеть, действительно ли ваши частные методы выполняются из ваших тестов.

11 голосов
/ 08 января 2014

Вот моя общая функция для проверки приватных полей:

protected <F> F getPrivateField(String fieldName, Object obj)
    throws NoSuchFieldException, IllegalAccessException {
    Field field =
        obj.getClass().getDeclaredField(fieldName);

    field.setAccessible(true);
    return (F)field.get(obj);
}
10 голосов
/ 18 сентября 2013

Сегодня я добавил библиотеку Java, чтобы помочь в тестировании частных методов и полей. Он был разработан с учетом Android, но его действительно можно использовать для любого Java-проекта.

Если у вас есть код с закрытыми методами, полями или конструкторами, вы можете использовать BoundBox . Это именно то, что вы ищете. Ниже приведен пример теста, который обращается к двум закрытым полям действия Android для его проверки:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

BoundBox позволяет легко тестировать частные / защищенные поля, методы и конструкторы. Вы даже можете получить доступ к вещам, которые скрыты по наследству. Действительно, BoundBox нарушает инкапсуляцию. Это даст вам доступ ко всему этому посредством отражения: НО все проверяется во время компиляции.

Идеально подходит для тестирования устаревшего кода. Используйте это осторожно. ;)

https://github.com/stephanenicolas/boundbox

10 голосов
/ 02 июня 2016

См. Пример ниже;

Необходимо добавить следующий оператор импорта:

import org.powermock.reflect.Whitebox;

Теперь вы можете напрямую передавать объект, у которого есть закрытый метод, имя вызываемого метода и дополнительные параметры, как показано ниже.

Whitebox.invokeMethod(obj, "privateMethod", "param1");
8 голосов
/ 18 сентября 2008

Во-первых, я выброшу этот вопрос: зачем вашим частным членам нужно изолированное тестирование? Являются ли они такими сложными, обеспечивающими такое сложное поведение, которое требует тестирования вне публичной среды? Это модульное тестирование, а не тестирование по строке кода. Не парься по мелочам.

Если они настолько велики, достаточно велики, что каждый из этих частных членов представляет собой «единицу» большой сложности - рассмотрите возможность рефакторинга таких частных членов из этого класса.

Если рефакторинг неуместен или неосуществим, можете ли вы использовать шаблон стратегии, чтобы заменить доступ к этим закрытым функциям-членам / классам-членам во время модульного тестирования? При модульном тестировании стратегия обеспечит дополнительную проверку, но в сборках релиза это будет простой проход.

8 голосов
/ 07 мая 2015

Недавно я столкнулся с этой проблемой и написал небольшой инструмент под названием Picklock , который позволяет избежать проблем явного использования API отражения Java, два примера:

Вызов методов, например, private void method(String s) - с помощью отражения Java

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

Вызов методов, например, private void method(String s) - Пиклоком

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

Настройка полей, например private BigInteger amount; - с помощью отражения Java

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

Настройка полей, например, private BigInteger amount; - от Пиклока

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));
6 голосов
/ 24 января 2018

PowerMockito сделан для этого. Использовать maven зависимость

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-mockito-release-full</artifactId>
    <version>1.6.4</version>
</dependency>

Тогда вы можете сделать

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...