从JUnit中获取getResourceAsStream

14

我正在为一个需要在初始化期间加载配置文件的项目创建一个 JUnit 测试用例。

这个配置文件位于项目的 src/main/resources/config 文件夹中,在构建时 Maven 会将其放置到 JAR 包内的 /config 文件夹中。

初始化类使用以下语句从那里读取文件:

ClassLoader classloader = this.getClass().getClassLoader();
BufferedReader xmlSource = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream("/config/config.xml")));

我的问题是,当我将这个jar文件部署并在应用服务器中执行时,它能够按预期工作,但是在Eclipse中的JUnit测试用例中运行时,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测试中使用它呢?

更新

我想到了解决方案,只需稍微改变一下代码: 不使用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()(为了比较值而保留的调用)完全相同(相同的id)!!!

这里发生了什么?!

为什么直接调用该方法不起作用,而在其中插入一个新的方法调用起作用呢?

老实说,我对此感到非常惊讶。

顺便提一下,我正在使用JUnit版本4.10。它可能以某种方式篡改了getClassLoader调用吗?

非常感谢,

Carles


1
src/test/resources/ 是 Eclipse 中的源文件夹吗? - Sotirios Delimanolis
@SotiriosDelimanolis 如果您将此作为答案发布,我会投票支持。 - Matthew Farwell
@SotiriosDelimanolis:是的,没错。实际上,当我看到它时,我意识到Eclipse默认采用它作为Maven项目。 - Carles Sala
3个回答

18

回答你的问题:

如果我进行调试,可以清楚地看到这个getClassLoader0()返回的对象(相同的id)与之前的调用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。

实际上,classLoader的getResourceAsStream()方法旨在用于相对路径(否则您将只使用FileInputStream)。

因此,当您使用this.getClass().getClassLoader().getResourceAsStream("/config/config.xml");时,实际上是将带有开始斜杠的路径传递给它,这会导致失败。当您使用this.getClass().getResourceAsStream("/config/config.xml");时,它很友好地为您删除了起始斜杠。


很抱歉,我目前无法进行测试(我想手动删除路由中的第一个斜杠并查看它是否以同样的方式工作),但听起来很有说服力。谢谢,我接受了。 - Carles Sala

2
ClassLoader对象的getResourceAsStream函数不会删除以搜索字符串为前缀的斜杠,因为搜索将相对于类路径进行。也就是说,它会在用于加载类的搜索路径上搜索资源。

例如,如果您的类yourpackage/Test.class位于/a/b/c/d/yourpackage/Test.class下,由系统类加载器(即默认类加载器)加载,并且您的类路径必须指向/a/b/c/d才能加载该类,则搜索将在此路径上进行。

类对象中的getResourceAsStream函数会删除以搜索字符串为前缀的斜杠,因为搜索将相对于其所在的类进行。也就是说,它会在加载您的类的位置上搜索资源。

例如,如果yourpackage/Test.class/a/b/c/d/yourpackage/Test.class加载,则资源路径将是/a/b/c/d/yourpackage/config/config.xml

您可以使用以下代码片段进行测试,因为getResourcegetResourceAsStream都遵循相同的搜索算法。

System.out.println(Test.class.getClassLoader().getResource("config/config.xml"));
System.out.println(Test.class.getResource("config/config.xml"));

1
我不确定,但你可以尝试将资源文件夹放在src/main目录下,而不是src/test目录下。在某些配置中,Eclipse会从src复制“resource”文件到classes,但不会从test复制。值得一试...

两个资源文件夹都存在于src/main和src/test中,包含相同的文件...但它不起作用。 - Carles Sala

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接