【问题标题】:How to swap a resource file path for a test file during unit testing?如何在单元测试期间将资源文件路径交换为测试文件?
【发布时间】:2020-04-16 22:53:57
【问题描述】:

我有一个包含一些设置的资源文件。我有一个 ResourceLoader 类,它从这个文件加载设置。这个类目前是一个急切地实例化的单例类。一旦这个类加载,它就会从文件中读取设置(文件路径存储为另一个类中的常量字段)。其中一些设置不适合单元测试。例如,我在这个文件中有线程睡眠时间,对于生产代码来说可能是几个小时,但我希望它是几毫秒的单元测试。所以我有另一个测试资源文件,它有一组不同的值。

如何在单元测试期间将主资源文件与此测试文件交换?该项目是一个 Maven 项目,我使用 TestNG 作为测试框架。以下是我一直在考虑的一些方法,但似乎都不理想:

  1. 使用@BeforeSuite,修改FilePath常量变量指向测试文件,使用@AfterSuite指向原始文件。这似乎有效,但我认为因为 ResourceLoader 类被急切地实例化,不能保证 @BeforeSuite 方法将始终在 ResourceLoader 类加载之前执行,因此旧属性可能会在文件之前加载路径变了。尽管大多数编译器仅在需要时才加载类,但我不确定这是否是 Java 规范要求。所以理论上这可能不适用于所有 Java 编译器。

  2. 将资源文件路径作为命令行参数传递。我可以在 pom.xml 的surefire配置中添加测试资源文件路径作为命令行参数。这似乎有点过分了。

  3. 使用 1. 中的方法并使 ResourceLoader 惰性实例化。这保证了如果在第一次调用ResourceLoader.getInstance().getProperty(..) 之前调用@BeforeMethodResourceLoader 将加载正确的文件。这似乎比前两种方法要好,但我认为使单例类延迟实例化会使其变得丑陋,因为我不能使用简单的模式,例如将其设为枚举等(例如急切实例化的情况)。

这似乎是一个常见的场景,最常见的处理方式是什么?

【问题讨论】:

  • 是否可以重新设计您的应用程序以使用更好的配置库?
  • 是的,我只是想知道执行此操作的标准方法。我愿意重新设计应用程序。
  • 唯一的标准方法是使用环境和/或系统属性。这通常过于局限,因为它们很难在例如测试中从命令行提供和覆盖。我建议您在问题中添加有关您需要什么以及您希望如何使用一些用例配置您的应用程序的信息。您可能想考虑在需要它们的类的构造函数中传递配置值。

标签: java unit-testing design-patterns singleton testng


【解决方案1】:

所有热切或延迟实例化的单例都是anti-pattern。单例的使用使单元测试更加困难,因为没有简单的方法来模拟单例。

模拟静态方法

一种解决方法是使用 PowerMock 来 mock static method 返回单例实例。

使用依赖注入

更好的解决方案是使用依赖注入。如果您已经使用依赖注入框架(例如 Spring、CDI),请重构代码以使 ResourceLoader 成为 managed beanscope singleton

如果您不使用依赖注入框架,一个简单的重构将是使用单例ResourceLoader 对所有类进行更改:

public class MyService {

  public MyService() {
    this(ResourceLoader.getInstance());
  }

  public MyService(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }
}

然后在单元测试中使用Mockito模拟ResourceLoader

ResourceLoader resourceLoader = mock(ResourceLoader.class);
when(ResourceLoader.getProperty("my-property")).thenReturn("10");
MyService myService = new MyService(resourceLoader);

外部化配置

另一种方法是将带有测试设置的文件放在src/test/resources 下。 如果您将设置存储在src/main/resources/application.properties 中,文件src/test/resources/application.properties 将覆盖它。

此外,将配置外部化到未打包在 JAR 中的文件是一个好主意。这样,文件src/main/resources/application.properties 将包含默认属性,并且使用命令行参数传递的文件将覆盖这些属性。因此,具有测试属性的文件也将作为命令行参数传递。看看 Spring 如何处理externalised configuration

使用 Java 系统属性

更简单的方法是允许在方法 ResourceLoader.getInstance().getProperty() 中使用 System Properties 覆盖默认属性,并以这种方式传递测试属性

public String getProperty(String name) {
  // defaultProperties are loaded from a file on a file system:
  // defaultProperties.load(new FileInputStream(new File(filePath)));
  // or from a file in the classpath:
  // defaultProperties.load(ResourceLoader.class.getResourceAsStream(filePath));
  return System.getProperty(name, defaultProperties.get(name));
}

【讨论】:

    【解决方案2】:

    检查你是否在 jUnit 中

    您还可以在运行时检查 jUnit 是否正在运行,然后交换路径。这会像这样工作(未经测试):

    public static funktionToTest() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        List<StackTraceElement> list = Arrays.asList(stackTraceElements);
        String path = "/origin/path/to/your/file"; // here can you set the default path to your rescource
        for (StackTraceElement stackTraceElement : list) {
            if (stackTraceElement.getClassName().startsWith("org.junit.")) {
                path = "/path/only/in/jUnit/test"; // and here the normal path
            }           
        }
        //do what you want with the path
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-22
      • 2013-03-28
      • 1970-01-01
      • 2013-04-05
      • 2017-04-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多