Это - плохая практика для использования Отражения в Поблочном тестировании? [дубликат]

Этот вопрос уже имеет ответ здесь:

В течение прошлых лет я всегда думал, что в Java, Отражение широко используется во время Поблочного тестирования. Так как некоторые переменные/методы, которые должны быть проверены, являются частными, так или иначе необходимо считать значения их. Я всегда думал, что API Reflection также используется с этой целью.

На прошлой неделе я должен был протестировать некоторые пакеты и поэтому записать некоторые тесты JUnit. Поскольку всегда я использовал Отражение для доступа к частным полям и методам. Но мой супервизор, кто проверил код, не был действительно доволен этим и сказал мне, что API Reflection не был предназначен для использования для такого "взламывания". Вместо этого он предложил modifiy видимость в производственном коде.

Это - действительно плохая практика для использования Отражения? Я не могу действительно верить этому -

Править: Я должен был упомянуть, что требовался, что все тесты находятся в отдельном пакете, названном тестом (настолько использующая защищенная видимость, например, не было возможное решение также),

94
задан Bozho 11 May 2010 в 14:09
поделиться

6 ответов

IMHO Reflection действительно должно быть только последним средством, зарезервированным для особого случая модульного тестирования устаревшего кода или API, который вы не можете изменить. Если вы тестируете свой собственный код, тот факт, что вам нужно использовать Reflection, означает, что ваш дизайн не поддается тестированию, поэтому вам следует исправить это, а не прибегать к Reflection.

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

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

Обновление: как отметил @tackline, это касается только использования Reflection в собственном тестовом коде, а не внутренних компонентов фреймворка тестирования. JUnit (и, вероятно, все другие подобные фреймворки) использует отражение для идентификации и вызова ваших тестовых методов - это оправданное и локализованное использование отражения. Было бы трудно или невозможно обеспечить те же возможности и удобства без использования Reflection. Кроме того, оно полностью инкапсулировано в реализацию фреймворка, поэтому не усложняет и не компрометирует наш собственный код тестирования.

75
ответ дан 24 November 2019 в 06:05
поделиться

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

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

Например, в Spring есть ReflectionTestUtils. Но его цель - установить макеты зависимостей там, где Spring должен был их инжектировать.

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

63
ответ дан 24 November 2019 в 06:05
поделиться

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

Не зная больше о вашем случае, я не могу сказать больше, но использование reflection действительно считается плохой практикой. Лично я предпочел бы сделать тест статическим внутренним классом тестируемого класса, чем прибегать к отражению (если, скажем, нетестируемая часть API не находится под моим контролем), но в некоторых местах будет больше проблем с тем, что тестовый код находится в том же пакете, что и рабочий код, чем с использованием отражения.

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

В любом случае, вы можете обойти эту проблему, используя protected (к сожалению, не package-private, который действительно идеален для этого), тестируя подкласс следующим образом:

 public class ClassUnderTest {
      protect void methodExposedForTesting() {}
 }

И внутри вашего модульного теста

class ClassUnderTestTestable extends ClassUnderTest {
     @Override public void methodExposedForTesting() { super.methodExposedForTesting() }
}

И если у вас есть защищенный конструктор:

ClassUnderTest test = new ClassUnderTest(){};

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

7
ответ дан 24 November 2019 в 06:05
поделиться

С точки зрения TDD - Test Driven Design - это плохая практика. Я знаю, что вы не помечали этот TDD и не спрашивали об этом конкретно, но TDD - хорошая практика, и это идет вразрез с ее сущностью.

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

30
ответ дан 24 November 2019 в 06:05
поделиться

Чтобы добавить к сказанному другими, рассмотрим следующее:

//in your JUnit code...
public void testSquare()
{
   Class classToTest = SomeApplicationClass.class;
   Method privateMethod = classToTest.getMethod("computeSquare", new Class[]{Integer.class});
   String result = (String)privateMethod.invoke(new Integer(3));
   assertEquals("9", result);
}

Здесь мы используем отражение для выполнения частного метода SomeApplicationClass.computeSquare (), передавая Integer и возвращая результат String. Это приводит к тесту JUnit, который будет компилироваться нормально, но завершится неудачно, если произойдет одно из следующих событий:

  • Имя метода "computeSquare" переименовано
  • Метод принимает другой тип параметра (например, изменение Integer на Long)
  • Число изменяемых параметров (например, передача другого параметра)
  • Тип возвращаемого значения метода изменяется (возможно, с String на Integer)

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

public void testSquare()
{
   String result = new NumberUtils().computeSquare(new Integer(3));
   assertEquals("9", result);
}

Теперь (особенно, когда вы используете инструменты рефакторинга, доступные в современных IDE), изменение имени метода не влияет на ваш тест (поскольку ваша IDE также будет также провели рефакторинг теста JUnit), при изменении типа параметра, количества параметров или возвращаемого типа метода будет отмечена ошибка компиляции в вашем тесте Junit, что означает, что вы не будете проверять тест JUnit, который компилируется, но не выполняется во время выполнения .

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

3
ответ дан 24 November 2019 в 06:05
поделиться

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

По крайней мере, так я думаю в процессе модульного тестирования.

4
ответ дан 24 November 2019 в 06:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: