对不同的JUnit测试使用不同的类加载器吗?
我有一个单例/工厂对象,我想写一个JUnit测试。 Factory方法根据类路径上的属性文件中的类名决定要实例化哪个实现类。如果没有找到属性文件,或者属性文件不包含classname键,那么该类将实例化一个默认实现类。对不同的JUnit测试使用不同的类加载器吗?
由于工厂保留了一个实例化的单例的静态实例,为了能够在Factory方法中测试“故障转移”逻辑,我需要在不同的类加载器中运行每个测试方法。
是否有任何方式与JUnit(或与另一个单元测试包)来做到这一点?
编辑:这里是一些工厂代码,在使用中:
private static MyClass myClassImpl = instantiateMyClass();
private static MyClass instantiateMyClass() {
MyClass newMyClass = null;
String className = null;
try {
Properties props = getProperties();
className = props.getProperty(PROPERTY_CLASSNAME_KEY);
if (className == null) {
log.warn("instantiateMyClass: Property [" + PROPERTY_CLASSNAME_KEY
+ "] not found in properties, using default MyClass class [" + DEFAULT_CLASSNAME + "]");
className = DEFAULT_CLASSNAME;
}
Class MyClassClass = Class.forName(className);
Object MyClassObj = MyClassClass.newInstance();
if (MyClassObj instanceof MyClass) {
newMyClass = (MyClass) MyClassObj;
}
}
catch (...) {
...
}
return newMyClass;
}
private static Properties getProperties() throws IOException {
Properties props = new Properties();
InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(PROPERTIES_FILENAME);
if (stream != null) {
props.load(stream);
}
else {
log.error("getProperties: could not load properties file [" + PROPERTIES_FILENAME + "] from classpath, file not found");
}
return props;
}
当我碰到这些排序我更喜欢使用的是一个黑客位的情况。我可能会公开一个受保护的方法,比如reinitialize(),然后从测试中调用它来有效地将工厂设置回初始状态。这个方法只存在于测试用例中,我将它记录下来。
这是一种黑客攻击,但它比其他选项容易得多,并且您不需要第三方库来完成它(但如果您更喜欢更清洁的解决方案,那么可能会有某种第三方你可以使用的工具)。
这个问题可能会很老,但是因为这是我遇到这个问题时发现的最近的答案,我尽管我会描述我的解决方案。
使用JUnit 4
分割你的测试,因此有每类中的一个测试方法(该解决方案只改变类之间的类加载器,而不是方法作为父亚军之间收集每类一旦所有的方法)
将@RunWith(SeparateClassloaderTestRunner.class)
注释添加到您的测试类。
创建SeparateClassloaderTestRunner
看起来像这样:
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);
}
}
}
注意,我不得不这样做是为了测试在传统的框架,我无法改变运行的代码。考虑到选择,我会减少使用静态和/或把测试挂钩,以允许系统重置。它可能不是很漂亮,但它允许我测试一大堆代码,否则会很困难。
此解决方案还打破了其他任何依赖类加载技巧(如Mockito)的其他解决方案。
而不是寻找“org.mypackages”。在loadClass()中,你也可以做这样的事情:return name.startsWith(“java”)|| name.startsWith(“org.junit”)? super.loadClass(name):super.findClass(name); – Gilead 2012-10-02 10:30:12
如果通过Ant task执行Junit,您可以设置fork=true
在其自己的JVM中执行每一类测试。同时将每个测试方法放在自己的类中,并且它们将分别加载并初始化它们自己的版本MyClass
。这是极端的,但非常有效。
下面您可以找到一个不需要单独的JUnit测试运行器的示例,也可以与类加载技巧(如Mockito)一起使用。
package com.mycompany.app;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.net.URLClassLoader;
import org.junit.Test;
public class ApplicationInSeparateClassLoaderTest {
@Test
public void testApplicationInSeparateClassLoader1() throws Exception {
testApplicationInSeparateClassLoader();
}
@Test
public void testApplicationInSeparateClassLoader2() throws Exception {
testApplicationInSeparateClassLoader();
}
private void testApplicationInSeparateClassLoader() throws Exception {
//run application code in separate class loader in order to isolate static state between test runs
Runnable runnable = mock(Runnable.class);
//set up your mock object expectations here, if needed
InterfaceToApplicationDependentCode tester = makeCodeToRunInSeparateClassLoader(
"com.mycompany.app", InterfaceToApplicationDependentCode.class, CodeToRunInApplicationClassLoader.class);
//if you want to try the code without class loader isolation, comment out above line and comment in the line below
//CodeToRunInApplicationClassLoader tester = new CodeToRunInApplicationClassLoaderImpl();
tester.testTheCode(runnable);
verify(runnable).run();
assertEquals("should be one invocation!", 1, tester.getNumOfInvocations());
}
/**
* Create a new class loader for loading application-dependent code and return an instance of that.
*/
@SuppressWarnings("unchecked")
private <I, T> I makeCodeToRunInSeparateClassLoader(
String packageName, Class<I> testCodeInterfaceClass, Class<T> testCodeImplClass) throws Exception {
TestApplicationClassLoader cl = new TestApplicationClassLoader(
packageName, getClass(), testCodeInterfaceClass);
Class<?> testerClass = cl.loadClass(testCodeImplClass.getName());
return (I) testerClass.newInstance();
}
/**
* Bridge interface, implemented by code that should be run in application class loader.
* This interface is loaded by the same class loader as the unit test class, so
* we can call the application-dependent code without need for reflection.
*/
public static interface InterfaceToApplicationDependentCode {
void testTheCode(Runnable run);
int getNumOfInvocations();
}
/**
* Test-specific code to call application-dependent code. This class is loaded by
* the same class loader as the application code.
*/
public static class CodeToRunInApplicationClassLoader implements InterfaceToApplicationDependentCode {
private static int numOfInvocations = 0;
@Override
public void testTheCode(Runnable runnable) {
numOfInvocations++;
runnable.run();
}
@Override
public int getNumOfInvocations() {
return numOfInvocations;
}
}
/**
* Loads application classes in separate class loader from test classes.
*/
private static class TestApplicationClassLoader extends URLClassLoader {
private final String appPackage;
private final String mainTestClassName;
private final String[] testSupportClassNames;
public TestApplicationClassLoader(String appPackage, Class<?> mainTestClass, Class<?>... testSupportClasses) {
super(((URLClassLoader) getSystemClassLoader()).getURLs());
this.appPackage = appPackage;
this.mainTestClassName = mainTestClass.getName();
this.testSupportClassNames = convertClassesToStrings(testSupportClasses);
}
private String[] convertClassesToStrings(Class<?>[] classes) {
String[] results = new String[classes.length];
for (int i = 0; i < classes.length; i++) {
results[i] = classes[i].getName();
}
return results;
}
@Override
public Class<?> loadClass(String className) throws ClassNotFoundException {
if (isApplicationClass(className)) {
//look for class only in local class loader
return super.findClass(className);
}
//look for class in parent class loader first and only then in local class loader
return super.loadClass(className);
}
private boolean isApplicationClass(String className) {
if (mainTestClassName.equals(className)) {
return false;
}
for (int i = 0; i < testSupportClassNames.length; i++) {
if (testSupportClassNames[i].equals(className)) {
return false;
}
}
return className.startsWith(appPackage);
}
}
}
单身人士导致整个世界受到伤害。避免单身人士,你的代码变得更容易测试,而且更好。 – 2008-09-05 15:42:11