使用不同的类装入器进行不同的 JUnit 测试?

2022-09-01 07:35:59

我有一个单例/工厂对象,我想为它编写一个JUnit测试。Factory 方法根据类路径上属性文件中的类名来决定要实例化的实现类。如果未找到任何属性文件,或者属性文件不包含类名键,则该类将实例化默认实现类。

由于工厂保留了 Singleton 的静态实例,以便在实例化后使用它,因此为了能够在 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;
}

答案 1

这个问题可能很老了,但由于这是我遇到这个问题时发现的最接近的答案,所以我会描述我的解决方案。

使用 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)的任何其他内容。


答案 2

当我遇到这种情况时,我更喜欢使用有点黑客的东西。我可能会公开一个受保护的方法,如reinitialize(),然后从测试中调用它,以有效地将工厂设置回其初始状态。此方法仅存在于测试用例中,我将其记录为这样。

这有点黑客攻击,但它比其他选项容易得多,你不需要第三方库来做到这一点(尽管如果你更喜欢更干净的解决方案,可能有某种第三方工具可以使用)。


推荐