emu in blogjava

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  171 随笔 :: 103 文章 :: 1052 评论 :: 2 Trackbacks
自作聪明的junit.swingui.TestRunner

问题
在junit.swingui.TestRunner的时候发现TestRunner启动过程中报错:
log4j:ERROR A "org.apache.log4j.ConsoleAppender" object is not assignable to a "org.apache.log4j.Appender" variable.
同时也发现一个平时工作正常的类在使用junit.swingui.TestRunner进行测试的时候报告一个奇怪的 ClassCastException,明明构造的对象的类是实现了指定的接口的,可是就是无法造型到接口上。
进一步研究发现,即使造型回原来的类也不行,虽然调试的时候显示构造的对象就是指定的类,但是就是无法造型成这个类,一度认为是妖人作祟或者机子被落了降头。

研究
求得庄老大再次出手,一下指出指出问题在于不同的类装载器装载的类无法相互造型的。于是进去junit.swingui.TestRunner里面去找类装载器,一翻折腾之后终于找到:

junit.runner.BaseTestRunner
           |------junit.swingui.TestRunner
           |------junit.textui.TestRunner

在BaseTestRunner里面定义了这样一个方法:
    public TestSuiteLoader getLoader() {
        if (useReloadingTestSuiteLoader())
            return new ReloadingTestSuiteLoader();
        return new StandardTestSuiteLoader();
    }
不过注意到junit.textui.TestRunner是不会出上面的错误的,因为它自己重载了getLoader()方法,
    /**
     * Always use the StandardTestSuiteLoader. Overridden from
     * BaseTestRunner.
     */
    public TestSuiteLoader getLoader() {
        return new StandardTestSuiteLoader();
    }
但是junit.swingui.TestRunner就很自作聪明了,为了避免每次在点“run”按钮的时候装载运行器本身,就直接使用了基类的方法去获取装载器),这样基类就可以调用自己的getLoader方法来决定要启用那个classloader:

    public TestSuiteLoader getLoader() {
        if (useReloadingTestSuiteLoader())
            return new ReloadingTestSuiteLoader();
        return new StandardTestSuiteLoader();
    }

如果我们用sun的jdk的话,这个方法会返回一个TestCaseClassLoader对象,而这个对象在装载class的时候总是调用creatLoader方法:
    protected TestCaseClassLoader createLoader() {
        return new TestCaseClassLoader();
    }

返回的其实是TestCaseClassLoader。这样如果被测试类使用了log4j的话,会造成org.apache.log4j.Appender类被 sun.misc.Launcher$AppClassLoader(也就是sun.misc.Launcher类的嵌入类AppClassLoader)装载一次(在启动test的过程中vm自动装载被引用到的类),然后在运行的时候又被junit.runner.TestCaseClassLoader再装载一次。由两个装载器装载进来的类不管是不是来自同一个.class文件,都会被认为是两个不同的类。因此就造成了上面的错误。
同样的,如果你在自己的代码里面这样装载类:
MyClass myClass = (MyClass)Thread.currentThread().getContextClassLoader().loadClass(mClassName);
也会造成相同的问题并抛出ClassCastException。因为MyClass是在运行测试的过程由junit.runner.TestCaseClassLoader装载的,而Thread.currentThread().getContextClassLoader()却指向的是sun.misc.Launcher$AppClassLoader。

解决方法
1 java -Dlog4j.ignoreTCL junit.swingui.TestRunner
我猜TCL是ThreadClassLoader的缩写,这个参数的意思大概就是让log4j忽略Thread自己的类装载器(sun.misc.Launcher$AppClassLoader),改而使用当前Class的装载器(junit.runner.TestCaseClassLoader)来装载。但是这个方法只能解决log4j的错误报告(改变了org.apache.log4j.ConsoleAppender的装载方式),但是对我们自己写的代码中的问题却没有作用。

2 在我们自己的类里面写上一段静态代码:
  static{
        Thread.currentThread().setContextClassLoader(MyClassFactory.class.getClassLoader());
  }
和方法一类似,这也是在工厂类中用加载了当前lass的装载器(TestCaseClassLoader)来代替Thread的初始化装载器sun.misc.Launcher$AppClassLoader。这个方法可以解决我们自己代码中的问题,并且不会带来影响原来的其他代码。结合第一种方法可以解决上面的两个问题。但是如果你有好几个工厂类,或者你用的其他包里面用了这样的装载方式……那你还可以试试下面的偏门:

3 注意到BaseTestRunner要进行一个useReloadingTestSuiteLoader()判断才决定返回哪个装载器
public TestSuiteLoader getLoader() {
    if (useReloadingTestSuiteLoader())
        return new ReloadingTestSuiteLoader();
    return new StandardTestSuiteLoader();
}
我们来看看这个判断过程:
protected boolean useReloadingTestSuiteLoader() {
    return getPreference("loading").equals("true") && !inVAJava() && fLoading;
}
嗯,里面有个inVAJava()是什么玩意儿?
public static boolean inVAJava() {
    try {
        Class.forName("com.ibm.uvm.tools.DebugSupport");
    }
    catch (Exception e) {
        return false;
    }
    return true;
}
原来它是想判断如果当前使用的是ibm的虚拟机就使用默认装载器,但是判断的条件也忒简单了点,很容易就吧它给蒙过去了:
在当前工程下创建com.ibm.uvm.tools包,在其中创建DebugSupport类:
package com.ibm.uvm.tools;
public class DebugSupport{}
没有错,就这个空白的类,这样就可以把junit.swingui.TestRunner给蒙倒。这样做据说的副作用是,每次点run按钮的时候,都要重起gui环境,但是我没有发现有什么区别。不过要是没有区别,人家又干吗费那么多事呢?不解。

参考资料

http://mail-archives.apache.org/mod_mbox/logging-log4j-user/200301.mbox/%3C3E1F1A31.2000605@attbi.com%3E


[点击此处收藏本文]
发表于 2005年04月29日 10:48 AM


maggie 发表于2005-04-29 11:43 AM  
果然看不不懂

emu 发表于2005-04-29 6:02 PM  
你将来长大了就懂了呵呵

Pingback/Trackback 发表于2005-04-29 6:36 PM  
试一试

linux_china 发表于2005-05-12 8:13 PM  
please modify excluded.properties in junit.jar,and execude some package will be all right.
posted on 2005-05-18 14:08 emu 阅读(2446) 评论(2)  编辑  收藏 所属分类: java技术测试技术

评论

# re: 自作聪明的junit.swingui.TestRunner 2005-12-05 10:41 wuchengding
testrunner 哪里有下载?能不能识别带有1. Infragistics

2. Log4net

3. Crownwood MagicLocalLibrary

4. Microsoft AppUpdater

5. Microsoft Application Blocks

6. Microsoft mshtml

7. Microsoft Commerce Server 2002

8. Crystal Decisions
的控件的东西  回复  更多评论
  

# re: 自作聪明的junit.swingui.TestRunner 2015-11-09 21:41 yqbjtu
我也遇到了log4j错误问题,但是通过删除其中一个jar包中加载的log4j就解决了,不过楼主分析的很好  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: