【问题标题】:Testing code dependant on existence of class on classpath测试代码依赖于类路径上的类的存在
【发布时间】:2015-01-25 15:42:41
【问题描述】:

我有一个项目,如果 SLF4J 在类路径上,我需要提供 SLF4J 日志记录,否则直接向控制台提供日志记录。我用类似于以下的代码实例化我的记录器:

try {
    Class.forName("org.slf4j.LoggerFactory");
    return new Slf4JLogProvider();
} catch (ClassNotFoundException e) {
    System.out.println("SLF4J not on classpath, defaulting to console logging");
}
return new ConsoleLogProvider();

请注意,Slf4JLogProvider 是 SLF4J 的自定义包装器,它不是 SLF4J 本身的一部分。

我的项目是基于 Maven 的,这个模块声明了对 SLF4J 的可选依赖:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <optional>true</optional>
</dependency>

我希望能够测试此代码。类路径很棘手,正确测试它是恕我直言很重要。基本上,我希望能够在测试期间修改类加载器,以确保不存在 SLF4J 并验证控制台日志记录是否已初始化。

有什么“干净”的方法吗?任何可以为依赖类路径的测试提供支持的框架?在特定类加载器中隔离测试的任何标准方法?

正如@piotrek 所指出的,这可能使它成为一个集成测试而不是单元测试。

【问题讨论】:

    标签: java unit-testing maven classpath


    【解决方案1】:

    您可以使用自定义类加载器来执行此操作。 Class.forName() 调用加载包含 Class.forName() 调用的类的类加载器。因此,当您的测试类尝试加载 SLF4J 时,它将调用加载测试类的同一个类加载器。您可以编写一个自定义类加载器并使用该加载器来加载被测类的副本。该类的副本将使用您的自定义类加载器来加载其他类,这使您有机会隐藏您不希望被测类访问的类。

    您的自定义类加载器将继承现有的类加载器类之一(ClassLoader 或 URLClassLoader)。您将添加此行为:

    1. 当请求加载您想要隐藏的某个类时,加载器会假装找不到该类。
    2. 当请求加载被测类时,直接从存储类的位置加载该类。这将为您提供将使用此类加载器的类的副本。
    3. 对其他类(java.lang.String 等)的请求被传递给父类加载器。

    使用此自定义类加载器来加载被测类的副本。生成的类对象和从类对象创建的实例将调用您的自定义加载器来加载其他类。当被测类尝试加载 SLF4J 时,您的自定义加载器会表现得好像该类不存在一样。

    编写代码示例有点复杂,但这里有一些链接说明了如何编写自定义类加载器。

    【讨论】:

    • 加载的类没有缓存吗?他仍然需要运行两次测试/使用两个不同的类路径,对吗?或至少防止在第一次测试期间加载类
    • 使用正常的类加载机制,每个类加载器都持有对其加载的类的引用。所以类倾向于留在内存中并被重用,是的。但是您可以在正常的类加载器层次结构之外创建自己的类加载器并使用它来加载类。如果你放弃对类加载器的引用、从它加载的类以及从这些类创建的对象,那么一切都会受到垃圾回收的影响。您可以创建另一个加载程序实例并再次执行整个操作。这就是 tomcat 等处理 Web 应用程序的热部署/取消部署的方式。
    • 我希望有人已经编写了一些实用程序来做到这一点。测试代码与功能代码的比例看起来不太好 ;-)
    【解决方案2】:

    提取Class.forName("org.slf4j.LoggerFactory"); 以分离服务并在您的测试中模拟它。你会发现一条线

    另一种方法是使用不同的类路径运行一些测试。不幸的是,在 maven 或 gradle 中没有很好的支持。但是使用 ant/ivy 很容易做到

    ps:如果 new Slf4JLogProvider(); 不在类路径中,你确定你可以在你的类中声明它吗?类声明不是解析jvm吗?

    【讨论】:

    • Slf4JLogProvider 是一个自定义类,不是 SLF4J 的一部分。但我希望我的测试能够捕捉到同类错误。我们可以讨论这是否使它成为一个集成测试而不是一个单元测试......
    • Class.forName 已经被实现你的虚拟机的人测试过了。如果您真的想测试该调用,则必须使用 2 个不同的类路径运行测试
    • 问题不在于测试Class.forName() 是否有效。问题是测试如果我用作标记的类不存在,我可以正确加载我的后备类,并且我对我没想到的东西没有隐藏的依赖。
    • 如果该行在另一个服务中,则模拟该服务并使模拟抛出 Class.forName 抛出的相同异常。然后你将测试你的后备代码
    • 那将是一个单元测试。这里需要的是集成或系统测试。问题不在于Class.forName() 会抛出异常,或者在特殊情况下有特殊行为。目标是验证其余代码是否也能正常工作。例如,假设我的ConsoleLogProvider 依赖于org.slf4j.Marker,模拟Class.forName() 抛出ClassNotFoundException 将起作用,ConsoleLogProvider 将被加载,测试将是绿色的。但实际上,实现被破坏了,因为我对 SLF4J 有一个隐藏的依赖。
    猜你喜欢
    • 1970-01-01
    • 2011-04-04
    • 2011-06-16
    • 2014-11-28
    • 1970-01-01
    • 2023-03-02
    • 1970-01-01
    • 2019-06-06
    • 2016-01-20
    相关资源
    最近更新 更多