如何在单元测试期间为测试文件交换资源文件路径?
How to swap a resource file path for a test file during unit testing?
我有一个包含一些设置的资源文件。我有一个 ResourceLoader class 从这个文件加载设置。这个 class 当前是一个急切实例化的单例 class。一旦这个 class 加载,它就会从文件中读取设置(文件路径存储为另一个 class 中的常量字段)。其中一些设置不适合单元测试。例如,我在这个文件中有线程休眠时间,生产代码可能是几个小时,但我希望单元测试是几毫秒。所以我有另一个测试资源文件,它有一组不同的值。
如何在单元测试期间将主资源文件与此测试文件交换?该项目是一个 Maven 项目,我使用 TestNG 作为测试框架。这些是我一直在考虑的一些方法,但其中 none 看起来很理想:
使用@BeforeSuite
并修改FilePath
常量变量指向测试文件,使用@AfterSuite
指向回原文件。这似乎可行,但我认为因为 ResourceLoader
class 是急切实例化的,所以不能保证 @BeforeSuite
方法总是在 ResourceLoader
[=42] 之前执行=] 已加载,因此可能会在更改文件路径之前加载旧属性。尽管大多数编译器仅在需要时加载 class,但我不确定这是否是 Java 规范要求。所以理论上这可能不适用于所有 Java 编译器。
将资源文件路径作为命令行参数传递。我可以在 pom.xml 的 surefire 配置中添加测试资源文件路径作为命令行参数。这似乎有点过分了。
使用1.中的方法,使ResourceLoader
惰性实例化。这保证如果在第一次调用 ResourceLoader.getInstance().getProperty(..)
之前调用 @BeforeMethod
,ResourceLoader
将加载正确的文件。这似乎比前两种方法更好,但我认为使单例 class 惰性实例化会使它变得丑陋,因为我不能使用简单的模式,如将其设为枚举等(就像 eager 的情况一样实例化)。
这似乎是一个常见的场景,最常见的处理方式是什么?
所有急切或延迟实例化的单例都是 anti-pattern。使用单例会使单元测试更加困难,因为没有简单的方法来模拟单例。
模拟静态方法
解决方法是使用 PowerMock mock static method 返回单例实例。
使用依赖注入
更好的解决方案是使用依赖注入。如果您已经使用依赖注入框架(例如 Spring、CDI),请重构代码以使 ResourceLoader
成为 managed bean with scope 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));
}
检查你是否在 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
}
我有一个包含一些设置的资源文件。我有一个 ResourceLoader class 从这个文件加载设置。这个 class 当前是一个急切实例化的单例 class。一旦这个 class 加载,它就会从文件中读取设置(文件路径存储为另一个 class 中的常量字段)。其中一些设置不适合单元测试。例如,我在这个文件中有线程休眠时间,生产代码可能是几个小时,但我希望单元测试是几毫秒。所以我有另一个测试资源文件,它有一组不同的值。
如何在单元测试期间将主资源文件与此测试文件交换?该项目是一个 Maven 项目,我使用 TestNG 作为测试框架。这些是我一直在考虑的一些方法,但其中 none 看起来很理想:
使用
@BeforeSuite
并修改FilePath
常量变量指向测试文件,使用@AfterSuite
指向回原文件。这似乎可行,但我认为因为ResourceLoader
class 是急切实例化的,所以不能保证@BeforeSuite
方法总是在ResourceLoader
[=42] 之前执行=] 已加载,因此可能会在更改文件路径之前加载旧属性。尽管大多数编译器仅在需要时加载 class,但我不确定这是否是 Java 规范要求。所以理论上这可能不适用于所有 Java 编译器。将资源文件路径作为命令行参数传递。我可以在 pom.xml 的 surefire 配置中添加测试资源文件路径作为命令行参数。这似乎有点过分了。
使用1.中的方法,使
ResourceLoader
惰性实例化。这保证如果在第一次调用ResourceLoader.getInstance().getProperty(..)
之前调用@BeforeMethod
,ResourceLoader
将加载正确的文件。这似乎比前两种方法更好,但我认为使单例 class 惰性实例化会使它变得丑陋,因为我不能使用简单的模式,如将其设为枚举等(就像 eager 的情况一样实例化)。
这似乎是一个常见的场景,最常见的处理方式是什么?
所有急切或延迟实例化的单例都是 anti-pattern。使用单例会使单元测试更加困难,因为没有简单的方法来模拟单例。
模拟静态方法
解决方法是使用 PowerMock mock static method 返回单例实例。
使用依赖注入
更好的解决方案是使用依赖注入。如果您已经使用依赖注入框架(例如 Spring、CDI),请重构代码以使 ResourceLoader
成为 managed bean with scope 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));
}
检查你是否在 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
}