Java - 如何在单元测试期间交换测试文件的资源文件路径?

Mar*_*ain 8 java testng singleton unit-testing design-patterns

我有一个资源文件,其中有一些设置。我有一个 ResourceLoader 类,它从这个文件加载设置。这个类目前是一个急切实例化的单例类。这个类一加载,它就会从文件中读取设置(文件路径存储为另一个类中的常量字段)。其中一些设置不适合单元测试。例如,我在这个文件中有线程睡眠时间,对于生产代码来说可能是几个小时,但我希望单元测试是几毫秒。所以我有另一个测试资源文件,它有一组不同的值。我的问题是如何在单元测试期间用这个测试文件交换主资源文件?该项目是一个 maven 项目,我使用 testng 作为测试框架。这些是我的一些方法

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

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

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

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

Evg*_*yst 7

所有急切或懒惰地实例化的单例都是反模式的。单例的使用使单元测试变得更加困难,因为没有简单的方法来模拟单例。

模拟静态方法

一种解决方法是使用 PowerMock 来模拟返回单例实例的静态方法

使用依赖注入

更好的解决方案是使用依赖注入。如果您已经使用了依赖注入框架(例如 Spring、CDI),请重构代码以创建具有作用域 singletonResourceLoader托管 bean

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

public class MyService {

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

  public MyService(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

外部化配置

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

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

使用 Java 系统属性

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

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));
}
Run Code Online (Sandbox Code Playgroud)