Использование отражения для изменения статического финала File.separatorChar для модульного тестирования?
В частности, я пытаюсь создать модульный тест для метода, который требует использования File.separatorChar
строить пути на окнах и 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 ответов
Из документации для 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
брошен Это терпит неудачу, потому что 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
Просто используйте / везде при создании файлов. Я занимаюсь этим 13 лет и у меня никогда не было проблем. Ничего не проверять.
Попробуйте вызвать экземпляр файла, а не экземпляр класса File.
Например
File file = ...;
field.setChar(file,'/');
Вы также можете попробовать http://code.google.com/p/jmockit/ и смоделировать статический метод FileSystem.getFileSystem(). (не знаю, можете ли вы смоделировать статические переменные, обычно эти хаки не нужны -> пишите oo-код и используйте "только" mockito)
Я понимаю, что это не отвечает на ваш вопрос напрямую, но Apache Commons FileNameUtils выполнит кросс-платформенное создание имени файла и может сэкономить вам написание собственного класса для этого.
Здесь я собираюсь установить значение для "android.os.Build.VERSION.RELEASE", где VERSION - это имя класса, а RELEASE - окончательное значение статической строки.
Если базовое поле является окончательным, метод генерирует исключение IllegalAccessException, поэтому нам необходимо использовать setAccessible(true), при добавлении метода field.set() необходимо добавить NoSuchFieldException.
@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 будет возвращать " Зефир ".
Вы можете взять исходный код для java.io.File и изменить его так, чтобы separatorChar и separator не были окончательными, и добавить метод setSeparatorChar, который обновляет их оба, а затем включить скомпилированный класс в путь загрузки bootclass.
Вместо того, чтобы использовать File.separatorChar для объявления вашего класса обслуживания, давайте назовем его PathBuilder или что-то в этом роде. Этот класс будет иметь метод concatPaths(), который объединит два параметра (используя символ разделителя ОС). Прелесть в том, что вы пишете этот класс, так что вы можете настроить его так, как хотите, когда будете тестировать его.