Использование отражения для изменения статического финального файла File.separatorChar для модульного тестирования?
В частности, я пытаюсь создать unit test для метода, который требует использования 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
Мысли?
Ответы
Ответ 1
Из документации для 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"
Важное обновление: приведенное выше решение не работает во всех случаях. Если поле становится доступным и считывается с помощью Reflection, прежде чем оно получит reset, будет выбрано значение IllegalAccessException
. Он терпит неудачу, потому что API Reflection создает внутренние объекты FieldAccessor
, которые кэшируются и повторно используются (см. Реализацию java.lang.reflect.Field # purchaseFieldAccessor (логическая)).
Пример тестового кода, который не выполняется:
Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null);
// call setFinalStatic as before: throws IllegalAccessException
Ответ 2
Попробуйте вызвать в экземпляре файла не экземпляр класса File
например.
File file = ...;
field.setChar(file,'/');
Вы также можете попробовать http://code.google.com/p/jmockit/ и высмеять статический метод FileSystem.getFileSystem(). (не знаю, можете ли вы издеваться над статическими переменными, обычно эти хаки не нужны → напишите код oo и используйте "только" mockito)
Ответ 3
Просто используйте/везде при создании файлов. Я занимаюсь этим 13 лет и никогда не испытывал проблем. Нечего тестировать.
Ответ 4
Я понимаю, что это не отвечает на ваш вопрос напрямую, но Apache Commons FileNameUtils будет выполнять кросс-платформенное построение имен файлов и может спасти вас написав свой собственный класс для этого.
Ответ 5
Вместо использования File.separatorChar объявите свой класс обслуживания, позвоните ему в PathBuilder или что-то в этом роде. Этот класс будет иметь метод concatPaths(), который объединит два параметра (используя разделитель ОС char). Красота заключается в том, что вы пишете этот класс, чтобы вы могли его настроить, если хотите, unit test.
Ответ 6
Вы можете взять исходный код для java.io.File и изменить его так, чтобы separatorChar и разделитель не были окончательными и добавили метод setSeparatorChar, который обновляет два из них, а затем включите скомпилированный класс в ваш путь bootclass.