Использование отражения для изменения статического финала File.separatorChar для модульного тестирования? - PullRequest
23 голосов
/ 19 марта 2010

В частности, я пытаюсь создать модульный тест для метода, который требует использования File.separatorChar для построения путей в Windows и Unix. Код должен работать на обеих платформах, и все же я получаю ошибки с JUnit при попытке изменить это статическое окончательное поле.

Кто-нибудь знает, что происходит?

Field field = java.io.File.class.getDeclaredField( "separatorChar" );
field.setAccessible(true);
field.setChar(java.io.File.class,'/');

Когда я делаю это, я получаю

IllegalAccessException: Can not set static final char field java.io.File.separatorChar to java.lang.Character

Мысли

Ответы [ 7 ]

64 голосов
/ 19 марта 2010

Из документации на Field.set:

Если базовое поле является окончательным, метод выдает IllegalAccessException, если setAccessible(true) не удалось для этого поля и это поле нестатично .

Итак, на первый взгляд кажется, что вам не повезло, поскольку File.separatorChar - это static. Удивительно, но - это способ обойти это: просто сделайте поле static больше не final через отражение.

Я адаптировал это решение от javaspecialist.eu :

static void setFinalStatic(Field field, Object newValue) throws Exception {
    field.setAccessible(true);

    // remove final modifier from field
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    field.set(null, newValue);
}

Я проверил это, и оно работает:

setFinalStatic(File.class.getField("separatorChar"), '#');
System.out.println(File.separatorChar); // prints "#"

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

setFinalStatic(Boolean.class.getField("FALSE"), true);
System.out.format("Everything is %s", false); // "Everything is true"

Важное обновление : вышеуказанное решение не работает во всех случаях. Если поле становится доступным и читается через «Отражение» до того, как оно будет сброшено, выдается IllegalAccessException. Он не работает, потому что Reflection API создает внутренние FieldAccessor объекты, которые кэшируются и используются повторно (см. Реализацию java.lang.reflect.Field # acquFieldAccessor (boolean)). Пример тестового кода, который не проходит:

Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null);
// call setFinalStatic as before: throws IllegalAccessException
2 голосов
/ 19 марта 2010

Просто используйте / везде при создании файлов. Я занимаюсь этим 13 лет и у меня никогда не было проблем. Нечего проверять.

2 голосов
/ 19 марта 2010

Попробуйте вызвать экземпляр файла, а не экземпляр класса File

* 1003 Е.Г. *

File file = ...;    
field.setChar(file,'/');

Вы также можете попробовать http://code.google.com/p/jmockit/ и смоделировать статический метод FileSystem.getFileSystem (). (не знаю, можете ли вы смоделировать статические переменные, обычно эти хаки не нужны -> пишите oo-код и используйте «только» mockito)

1 голос
/ 19 марта 2010

Я понимаю, что это не отвечает на ваш вопрос напрямую, но Apache Commons FileNameUtils сделает кросс-платформенное построение имени файла и может сэкономить вам написание собственного класса для этого.

0 голосов
/ 24 февраля 2018

здесь я собираюсь установить значение для "android.os.Build.VERSION.RELEASE", где VERSION - это имя класса, а RELEASE - окончательное значение статической строки.

Если основное поле является окончательным, метод выдает an IllegalAccessException , поэтому нам нужно использовать setAccessible (true), NoSuchFieldException необходимо добавить при использовании field.set () method

@RunWith(PowerMockRunner.class)
@PrepareForTest({Build.VERSION.class})
public class RuntimePermissionUtilsTest {
@Test
public void hasStoragePermissions() throws IllegalAccessException, NoSuchFieldException {
    Field field = Build.VERSION.class.getField("RELEASE");
    field.setAccessible(true);
    field.set(null,"Marshmallow");
 }
}

теперь значение String RELEASE вернет " Marshmallow ".

0 голосов
/ 19 марта 2010

Вы можете взять исходный код для java.io.File и изменить его так, чтобы separatorChar и separator не были окончательными, и добавить метод setSeparatorChar, который обновляет их оба, а затем включить скомпилированный класс в путь загрузки bootclasspath.

0 голосов
/ 19 марта 2010

Вместо того, чтобы использовать File.separatorChar для объявления вашего класса обслуживания, давайте назовем его PathBuilder или что-то в этом роде. Этот класс будет иметь метод concatPaths (), который объединит два параметра (используя символ разделителя ОС). Прелесть в том, что вы пишете этот класс, так что вы можете настроить его так, как вам захочется, когда вы тестируете его.

...