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 в классы, но не из теста в классы. Это стоит того...