我目前有Java单例,它在实例化时读取一些系统属性。在生产环境中,这些系统属性将是静态的,因此一旦重新启动JVM,就不需要更改系统属性。
public final class SoaJSONLogger {
private static final String SCHEMA_PROPERTY = "com.reddipped.soa.jsonlogger.schema";
private static final String SCHEMA_STRICT = "com.reddipped.soa.jsonlogger.strict";
private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
private final com.reddipped.soa.jsonlogger.JSONLogger LOGGER;
private final Pattern fieldValuePattern = Pattern.compile("(\\w+)=(.+)");
private final String schemaName;
private final Boolean strictSchema;
/**
* Logging level
*
* TRACE (least serious) DEBUG INFO WARNING ERROR (most serious)
*
*/
public static enum LEVEL {
ERROR, WARN, INFO, DEBUG, TRACE
};
private static class JSONLoggerLoader {
private static final SoaJSONLogger INSTANCE = new SoaJSONLogger();
}
private SoaJSONLogger() {
if (JSONLoggerLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
// Get schema name and strict settings
this.schemaName = System.getProperty(SoaJSONLogger.SCHEMA_PROPERTY);
this.strictSchema = (System.getProperty("com.reddipped.soa.jsonlogger.strict") != null
&& System.getProperty(SoaJSONLogger.SCHEMA_STRICT).equalsIgnoreCase("true"));
if (this.schemaName != null) {
this.LOGGER = JSONLogger.getLogger("JSONLogger", DATE_TIME_FORMAT, this.schemaName, this.strictSchema);
} else {
throw new IllegalArgumentException("Schema property " + SoaJSONLogger.SCHEMA_PROPERTY + " not set");
}
}
public static SoaJSONLogger getInstance() {
return JSONLoggerLoader.INSTANCE;
}
public void trace(String xmlLog) {
this.LOGGER.xml(xmlLog).trace();
}
public void debug(String xmlLog) {
this.LOGGER.xml(xmlLog).debug();
}
public void info(String xmlLog) {
this.LOGGER.xml(xmlLog).info();
}
public void warn(String xmlLog) {
this.LOGGER.xml(xmlLog).warn();
}
public void error(String xmlLog) {
this.LOGGER.xml(xmlLog).error();
}
}
我需要测试系统属性的不同值。在JUnit中执行此操作的最佳方法是什么,而无需修改Java类?我需要以某种方式为每个JUnit测试创建一个新的类实例。
答案 0 :(得分:3)
所有不错的答案,但不知何故都忽略了这一点,并建议过于复杂的解决方案。而您的真正问题源于您的课程混合两个职责:
换句话说;简单地修改你的设计:
创建一个表示您正在寻找的功能的界面
public interface SoaJSONLogger { ...
然后创建一个简单的实现
class SoaJSONLoggerImpl implements SoaJSONLogger {
这个impl类可以是包保护的例子;这样它就不能从它的包外部实例化(但可用于生活在同一个包中的单元测试)。
然后使用枚举模式显式提供单例:
public enum SoaJSONLoggerProvider implements SoaJSONLogger {
INSTANCE;
private final SoaJSONLogger delegatee = new SoaJSONLoggerImp();
@Override
public void trace(String xmlLog) {
delegatee.trace(xmlLog);
}
现在你得到了:
除此之外:您的代码的另一个真正的问题是您在构造函数中执行所有这些操作。我会考虑重构那个;把它拉到不同的班级。
长话短说:您的代码很难进行测试,因为您编写了难以测试的代码! (你实际上在那里做的事情可以用不同的方式完成,以更容易测试的方式)。
编辑;关于"包保护"和单元测试:典型的方法是X和XTest类都存在于相同的包中;但在不同的源文件夹中。所以,典型的结构就是你要么:
ProjectA
src/y/X.java
与
一起ProjectB based on ProjectA
src/y/XTest.java
或类似
ProjectA
src/y/X.java
test/y/X.java
这允许在没有任何喧嚣的情况下测试包受保护的功能。
答案 1 :(得分:2)
不要听那些单身人士的憎恨(顺便说一句,他们是正确的 - 单身人士是坏人,从不使用他们)......实际上是可能的。
诀窍是在不同的类加载器中隔离您的测试。你看,单身人士并不是每个进程都是独一无二的 - 他们每个类加载器都是独一无二的。
将您的测试分开,以便每个班级有一个测试方法。
将@RunWith(SeparateClassloaderTestRunner.class)
注释添加到测试类中。
这是跑步者的代码。用您自己的包前缀替换"org.mypackages."
。
public class SeparateClassloaderTestRunner extends BlockJUnit4ClassRunner {
public SeparateClassloaderTestRunner(Class<?> clazz) throws InitializationError {
super(getFromTestClassloader(clazz));
}
private static Class<?> getFromTestClassloader(Class<?> clazz) throws InitializationError {
try {
ClassLoader testClassLoader = new TestClassLoader();
return Class.forName(clazz.getName(), true, testClassLoader);
} catch (ClassNotFoundException e) {
throw new InitializationError(e);
}
}
public static class TestClassLoader extends URLClassLoader {
public TestClassLoader() {
super(((URLClassLoader)getSystemClassLoader()).getURLs());
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("org.mypackages.")) {
return super.findClass(name);
}
return super.loadClass(name);
}
}
}
和bam!每个测试一个全新的Singleton ,在同一个JVM中!
// Do this for each test
@RunWith(SeparateClassloaderTestRunner.class)
public class SingletonTest {
private SoaJSONLogger instance;
@Before
public void setUp() throws Exception {
System.setProperty("com.reddipped.soa.jsonlogger.strict", "true");
instance = SoaJSONLogger.getInstance();
}
@Test
public void singletonTest() throws Exception {
}
}
答案 2 :(得分:0)
Trick play:还有另一种实例化“Singleton”类的方法。使SoaJSONLogger实现Serializable。然后将对象保存到ObjectOutputStream。
SoaJSONLogger myInstance = SoaJSONLogger.getInstance() // 1 instance.
FileOutputStream fOut = new FileOutputStream("tempFile.txt");
ObjectOutputStream objOut = new ObjectOutputStream (fOut);
objOut.writeObject (myInstance);
// now read it in.
FileInputStream fIn = new FileInputStream("tempFile.txt");
ObjectInputStream objIn = new ObjectInputStream (fIn);
SoaJSONLogger obj2 = (SoaJSONLogger)objIn.readObject(); // 2nd instance
答案 3 :(得分:0)
为什么要使用系统属性而不是应用程序属性。如果您有一个配置良好的Maven项目,只需在捆绑配置中隔离您的属性并为开发,qa,阶段或生产配置文件添加多个Maven过滤器,就可以解决此问题。
您的项目结构应如下所示:
my-project
|_ src
| |_ main
| | |_ filters
| | | |_ development.properties
| | | |_ local.properties
| | | |_ production.properties
| | | |_ qa.properties
| | |_ java
| | | |_artifact
| | | |_ MySingleton.java
| | |_ resources
| | |_ my-configurations.properties
| |_ test
| | |_ java
| | | |_artifact
| | | |_ MySingletonTest.java
|_ pom.xml
Maven项目配置文件(pom.xml)必须声明配置文件过滤器:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>gvillacis</groupId>
<artifactId>test-units</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<profiles>
<profile>
<id>local</id>
<properties>
<deployEnvironment>local</deployEnvironment>
</properties>
</profile>
<profile>
<id>development</id>
<properties>
<deployEnvironment>development</deployEnvironment>
</properties>
</profile>
<profile>
<id>qa</id>
<properties>
<deployEnvironment>qa</deployEnvironment>
</properties>
</profile>
<profile>
<id>production</id>
<properties>
<deployEnvironment>production</deployEnvironment>
</properties>
</profile>
</profiles>
<build>
<filters>
<filter>src/main/filters/${deployEnvironment}.properties</filter>
</filters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<targetPath>${project.build.outputDirectory}</targetPath>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
MySingleton.class包含:
package artifact;
import java.util.ResourceBundle;
public class MySingleton {
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("my-configurations");
private static MySingleton instance;
private String value1 = BUNDLE.getString("value.1");
private String value2 = BUNDLE.getString("value.2");
public void doSomething() {
System.out.println("The value of 1st property is: " + value1);
System.out.println("The value of 2nd property is: " + value2);
}
public static MySingleton getInstance() {
if (instance == null) {
instance = new MySingleton();
}
return instance;
}
}
MySingletonTest.class包含:
package artifact;
import org.junit.Test;
public class MySingletonTest {
@Test
public void testDoSomething() {
MySingleton singleton = MySingleton.getInstance();
singleton.doSomething();
}
}
my-configurations.properties坚持这个:
value.1=${filter.value.1}
value.2=${filter.value.2}
value.3=${filter.value.3}
value.4=${filter.value.4}
value.5=${filter.value.5}
对于“local.properties”配置文件示例,您可以这样:
filter.value.1=1
filter.value.2=2
filter.value.3=3
filter.value.4=4
filter.value.5=5
现在,根据您要测试的环境相关的配置文件,您可以使用如下命令:
$ mvn test -P local
这是我执行本地配置文件命令时得到的结果:
$ mvn test -P local
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building test-units 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ test-units ---
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource to /home/gvillacis/Work/workspaces/testunits/target/classes
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ test-units ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ test-units ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/gvillacis/Work/workspaces/testunits/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ test-units ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ test-units ---
[INFO] Surefire report directory: /home/gvillacis/Work/workspaces/testunits/target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running MySingletonTest
The value of 1st property is: 1
The value of 2nd property is: 2
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.067 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.042 s
[INFO] Finished at: 2017-01-24T18:58:24-03:00
[INFO] Final Memory: 12M/253M
[INFO] ------------------------------------------------------------------------