GetResourceAsStream от JUnit
Я создаю JUnit TestCase для проекта, который должен загружать файл конфигурации во время инициализации.
Этот файл конфигурации находится внутри проекта в папке src/main/resources/config, а во время сборки maven помещает его в папку /config внутри JAR.
Класс инициализации читает файл с помощью этого оператора:
ClassLoader classloader = this.getClass().getClassLoader();
BufferedReader xmlSource = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream("/config/config.xml")));
Проблема заключается в том, что когда я развертываю и выполняю эту банку на сервере приложений, она работает так, как ожидалось, однако, всякий раз, когда я запускаю ее в JUnit TestCase в Eclipse, метод getResrouceAsStream возвращает null.
Учитывая, что класс является my.package.MyClassTest.java и что он живет в src/test/java/my/package/MyClassTest.java, я уже попытался поместить копию файла config.xml в следующее папки без успеха:
- src/test/resources/config
- src/test/resources/my/package/config
- src/test/java/my/package/config
Я знаю, что подобные вопросы задавались много раз здесь, в StackOverflow, но все ответы, которые я нашел, касаются изменения способа загрузки файла, и, хотя изменение кода может быть вариантом, я бы предпочел просто найти правильное место для файла, поэтому мне не нужно изменять вещи, которые уже работают в рабочей среде.
Итак, где я должен разместить этот файл, чтобы использовать его в своем тесте JUnit?
UPDATE
Я только придумал решение с небольшим изменением кода:
Вместо использования ClassLoader для получения ресурса я непосредственно использовал класс:
Class clazz = this.getClass();
BufferedReader xmlSource = new BufferedReader(new InputStreamReader(clazz.getResourceAsStream("/config/config.xml")));
И он успешно считывает файл из src/test/resources/config/config.xml.
Однако здесь есть что-то очень странное:
Метод Class.getResourceAsStream:
public InputStream getResourceAsStream(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);
}
И если я его отлаживаю, я ясно вижу, что этот getClassLoader0() возвращает точно такой же объект (тот же идентификатор), что и предыдущий вызов, this.getClass(). getResourceAsStream ( ) (который я сохранил, просто чтобы сравнить значения)!!!
Что здесь происходит?!
Почему вызов метода напрямую не работает, а вставка нового вызова метода между работами?
Честно говоря, я действительно удивлен перед этим.
Кстати, я использую JUnit версии 4.10. Может ли это подменить вызов getClassLoader?
Большое спасибо,
Карлес
Ответы
Ответ 1
Отвечая на ваш вопрос
И если я его отлаживаю, я ясно вижу, что этот getClassLoader0() возвращает точно такой же объект (тот же идентификатор), что и предыдущий вызов, this.getClass(). getResourceAsStream ( ) (который я сохранил, просто чтобы сравнить значения)!!!
Что здесь происходит?!
Почему вызов метода напрямую не работает, а вставка нового вызова метода между работами?
Разница между вызовом
this.getClass().getClassLoader().getResourceAsStream("/config/config.xml");
и вызов
this.getClass().getResourceAsStream("/config/config.xml");
Входит в точный источник, который вы показывали из Class
:
public InputStream getResourceAsStream(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);
}
Но проблема не в том, что возвращает getClassLoader0()
. В обоих случаях он возвращает то же самое. На самом деле разница в resolveName(name)
. Это частный метод в классе Class
.
private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) {
Class<?> c = this;
while (c.isArray()) {
c = c.getComponentType();
}
String baseName = c.getName();
int index = baseName.lastIndexOf('.');
if (index != -1) {
name = baseName.substring(0, index).replace('.', '/')
+"/"+name;
}
} else {
name = name.substring(1);
}
return name;
}
Итак, вы видите, что до фактического вызова classLoader getResourceAsStream()
он фактически удаляет стартовую косую черту с пути.
В общем, он попытается получить ресурс относительно this
, когда он не имеет косой черты, и передать его классу-загрузчику, если он имеет косую черту в начале.
Метод classLoader getResourceAsStream()
на самом деле предназначен для использования для относительных путей (иначе вы бы просто использовали FileInputStream
).
Итак, когда вы использовали this.getClass().getClassLoader().getResourceAsStream("/config/config.xml");
, вы фактически передавали ему путь с косой чертой в начале, который не удался. Когда вы использовали this.getClass().getResourceAsStream("/config/config.xml");
, он был достаточно любезен, чтобы удалить его для вас.
Ответ 2
getResourceAsStream
функция из объекта ClassLoader
не будет удалять слэш, добавленный в вашу строку поиска, поскольку поиск будет выполнен относительно пути к классам. i.e ищет ресурс, где путь поиска используется для загрузки классов.
Скажите, например, если ваш класс yourpackage/Test.class
, который находится под /a/b/c/d/yourpackage/Test.class
, загруженный системным загрузчиком классов (например, загрузчик классов по умолчанию), и ваш путь к классам должен указывать на /a/b/c/d
, чтобы загрузить класс. Поиск будет выполнен на этом пути.
Функция getResourceAsStream
из объекта класса удаляет слэш, добавленный в вашу строку поиска, поскольку поиск будет выполнен относительно класса, в котором он находится. i.e ищет ресурс, из которого загружается ваш класс.
Скажем, например, если yourpackage/Test.class
загружен из /a/b/c/d/yourpackage/Test.class
, тогда путь ресурса будет /a/b/c/d/yourpackage/config/config.xml
Вы можете протестировать это, используя следующий код snipet, поскольку оба getResource
и getResourceAsStream
используются для поиска того же алгоритма поиска.
System.out.println(Test.class.getClassLoader().getResource("config/config.xml"));
System.out.println(Test.class.getResource("config/config.xml"));
Ответ 3
Я не уверен в этом, но вы можете попробовать разместить свою папку ресурсов в дереве src/main вместо дерева src/test. В некоторых конфигурациях, по крайней мере, eclipse копирует "ресурсные" файлы из src в классы, но не из теста в классы. Это стоит того...