Как проверить частную функцию или класс с частными методами, полями или внутренними классами?

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

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

Ответ 1

Обновить:

Примерно через 10 лет, возможно, лучший способ протестировать закрытый метод или любого недоступного члена - через @Jailbreak из @Jailbreak Manifold.

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo members
foo.privateMethod(x, y, z);
foo.privateField = value;

Таким образом, ваш код остается безопасным и читабельным. Никаких компромиссов в дизайне, никаких переэкспонированных методов и полей ради тестов.

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

Внутри мы используем помощники для получения/установки private и private static переменных, а также для вызова private и private static методов. Следующие шаблоны позволят вам делать практически все, что связано с закрытыми методами и полями. Конечно, вы не можете изменить private static final переменные с помощью отражения.

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

И для полей:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

Заметки:
1. TargetClass.getDeclaredMethod(methodName, argClasses) позволяет вам просматривать private методы. То же самое относится и к getDeclaredField.
2. setAccessible(true) требуется для игры с рядовыми.

Ответ 2

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

  • Частный метод - это мертвый код
  • У класса, который вы тестируете, есть запах дизайна.
  • Метод, который вы пытаетесь проверить, не должен быть закрытым

Ответ 3

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

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

Новый класс предоставляет эти методы как "общедоступные", поэтому они доступны для модульного тестирования. Новые и старые классы теперь и проще, чем оригинальный класс, что отлично для меня (мне нужно, чтобы все было просто, или я заблудился!).

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

Ответ 4

Я использовал reflection, чтобы сделать это для Java в прошлом, и, на мой взгляд, это была большая ошибка.

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

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

Ответ 6

Обычно unit test предназначен для реализации открытого интерфейса класса или единицы. Таким образом, частные методы - это детализация реализации, которые вы не ожидаете явно проверить.

Ответ 7

Только два примера того, где я хочу протестировать частный метод:

  • Процедуры дешифрования. Я бы не стал хотите сделать их видимыми для всех, чтобы видеть только для ради тестирования, иначе любой может использовать их для расшифровки. Но они неотъемлемой частью кода, сложным, и необходимо всегда работать (очевидным исключением является отражение, которое может использоваться для просмотра даже частных методов в большинстве случаев, когда SecurityManager не настроен для предотвращения этого).
  • Создание SDK для сообщества потребление. Здесь публика принимает совершенно другое значение, поскольку это это код, который может видеть весь мир (а не только для моего приложения). я кладу код в частные методы, если я не хотите, чтобы пользователи SDK видели это - я не воспринимайте это как запах кода, просто как работает SDK-программирование. Но из Конечно, мне все еще нужно проверить мои частные методы, и они функциональность моего SDK на самом деле жизнь.

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

Таким образом, мой компромисс включает в себя усложнение JUnits с отражением, а не компрометацию моей безопасности и SDK.

Ответ 8

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

Ответ 9

Другим подходом, который я использовал, является изменение частного метода для частного или защищенного пакета, а затем его дополнение с помощью аннотации @VisibleForTesting библиотеки Google Guava.

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

Например, если тестируемый метод находится в src/main/java/mypackage/MyClass.java, тогда ваш тестовый вызов должен быть помещен в src/test/java/mypackage/MyClassTest.java. Таким образом, вы получили доступ к методу тестирования в своем тестовом классе.

Ответ 10

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

Я использую junitx.util.PrivateAccessor -пакет для Java. Много полезных однострочных линий для доступа к частным методам и частным полям.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);

Ответ 11

Попробовав решение Cem Catikkas с использованием отражения для Java, я бы сказал, что это было более элегантное решение, чем я описал здесь. Однако, если вы ищете альтернативу использованию отражения и имеете доступ к тестируемому источнику, это все равно будет вариант.

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

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

Преимущества:

  • Может тестировать до мелкой гранулярности

Недостатки:

  • Тестовый код должен находиться в том же файле, что и исходный код, который может быть сложнее поддерживать
  • Аналогично с выходными файлами .class они должны оставаться в том же пакете, который объявлен в исходном коде.

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

Вот замысловатый пример того, как это будет работать:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

Внутренний класс будет скомпилирован в ClassToTest$StaticInnerTest.

См. Также: Java Совет 106: статические внутренние классы для удовольствия и выгоды

Ответ 12

В Spring Framework вы можете протестировать закрытые методы, используя этот метод:

ReflectionTestUtils.invokeMethod()

Например:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

Ответ 13

Как уже говорили другие... не тестируйте частные методы напрямую. Вот несколько мыслей:

  1. Все методы должны быть небольшими и целенаправленными (легко тестировать, легко находить, что не так)
  2. Используйте инструменты покрытия кода. Мне нравится Cobertura (о, счастливый день, похоже, вышла новая версия!)

Запустите покрытие кода на модульных тестах. Если вы видите, что методы не полностью протестированы, добавьте тесты, чтобы увеличить охват. Стремитесь к 100% охвату кода, но понимайте, что вы, вероятно, не получите его.

Ответ 14

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

Тестирование частных методов должно быть проверено путем отладки перед запуском ваших модульных тестов в общедоступных методах.

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

Я лично считаю, что лучше создавать классы с использованием TDD; создавая публичные куски методов, затем генерируя единичные тесты с помощью all утверждений, определенных заранее, поэтому ожидаемый результат метода определяется до того, как вы его закодируете. Таким образом, вы не ошибетесь, чтобы утверждения unit test соответствовали результатам. Затем ваш класс прочен и соответствует требованиям, когда проходят все тесты вашего устройства.

Ответ 15

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

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

Ответ 16

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

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

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

Ответ 17

Тестирование частных методов ломает инкапсуляцию вашего класса, потому что каждый раз, когда вы меняете внутреннюю реализацию, вы нарушаете клиентский код (в данном случае, тесты).

Поэтому не проверяйте частные методы.

Ответ 18

Если вы хотите протестировать частные методы устаревшего приложения, в котором вы не можете изменить код, один из вариантов для Java - jMockit, который позволит вам создавать mocks для объекта, даже если они являются закрытыми для класса.

Ответ 19

Ответ со страницы часто задаваемых вопросов JUnit.org:

Но если вы должны...

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

Если вы используете JDK 1.6 или выше и аннотируете свои тесты @Test, вы можете использовать Dp4j для добавления отражения в ваши методы тестирования. За подробнее о том, как его использовать, см. этот тестовый скрипт.

Постскриптум Я основной участник Dp4j, спросите меня, нужна ли вам помощь. :)

Ответ 20

Я стараюсь не тестировать частные методы. Там царит безумие. Лично я считаю, что вы должны тестировать только открытые интерфейсы (и которые включают защищенные и внутренние методы).

Ответ 21

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

Ответ 22

Частный метод доступен только для одного класса. Таким образом, нет способа протестировать метод "private" целевого класса из любого тестового класса. Выход состоит в том, что вы можете выполнить модульное тестирование вручную или можете изменить свой метод от "private" до "protected".

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

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

Ответ 23

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

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

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;
}

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

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

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

Ответ 24

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

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

Ответ 25

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

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);
}

Ответ 26

Сегодня я нажал библиотеку 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

Ответ 27

См. ниже пример:

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

import org.powermock.reflect.Whitebox;

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

Whitebox.invokeMethod(obj, "privateMethod", "param1");

Ответ 28

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

Если они такие большие, достаточно большие, чтобы эти частные члены были "сложной" единицей, рассмотрите возможность реорганизации таких частных членов из этого класса.

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

Ответ 29

Недавно я столкнулся с этой проблемой и написал небольшой инструмент под названием 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) - by Picklock

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; - by Picklock

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));

Ответ 30

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

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

Это неверно. Вы, безусловно, можете, как упоминалось в ответить Cem Catikkas.