自作聪明的junit.swingui.TestRunner

原创 2005年04月29日 10:48:00

问题
在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环境,但是我没有发现有什么区别。不过要是没有区别,人家又干吗费那么多事呢?不解。

4 excluded.properties

以前没有注意到,junit有一个配置文件可以配置不需要用TestCaseClassLoader装载的包和类,多亏网友linux_china提醒,现在有了第四个解决方法:

在工程下建立junit.runner包,然后在包里面放 excluded.properties文件,内容象这样:

#
# The list of excluded package paths for the TestCaseClassLoader
#
excluded.0=sun.*
excluded.1=com.sun.*
excluded.2=org.omg.*
excluded.3=javax.*
excluded.4=sunw.*
excluded.5=java.*
excluded.6=org.w3c.dom.*
excluded.7=org.xml.sax.*
excluded.8=net.jini.*
excluded.9=org.apache.log4j.*
excluded.10=emu.*
......

这样junit读到这些包的时候就不会用TestCaseClassLoader去装载他们了。

参考资料

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

author: emu(黄希彤)

Junit4中怎样随心所欲的执行Test(一)

前言:使用junit4.4进行接口测试时,用来组织测试用例的测试类,我们称之为“TestCase”。TestCase中的每个测试用例用@Test来标记,在这里我们称测试用例为“Test”。在eclip...
  • rachel_luo
  • rachel_luo
  • 2012年02月15日 18:28
  • 18740

[JUnit]What are TestCase, TestSuite, BaseTestRunner

TestCase(or test case):  A class that extends the JUnit TestCase class.  It contains one or more tes...
  • formX
  • formX
  • 2007年11月05日 13:41
  • 562

log4j 多classloader重复加载配置问题解决

log4j:ERROR A "org.apache.log4j.xml.DOMConfigurator" object is not assignable to a "org.apache.log4j...
  • miqi770
  • miqi770
  • 2015年01月14日 18:03
  • 2476

在Eclipse中使用JUnit4进行单元测试(初级篇)

本文绝大部分内容引自这篇文章:http://www.devx.com/Java/Article/31983/0/page/1我们在编写大型程序的时候,需要写成千上万个方法或函数,这些函数的功能可能很...
  • andycpp
  • andycpp
  • 2006年10月09日 13:16
  • 367477

简要说JUnit的4大功能

简要说JUnit的4大功能 1. 管理测试用例。修改了哪些代码,这些代码的修改会对哪些部分有影响,通过JUnit将这次的修改做个完整测试。这也就JUnit中所谓的TestSuite。 2. 定义测...
  • lidengzhi0000
  • lidengzhi0000
  • 2014年02月25日 09:56
  • 1743

Junit核心——测试类(TestCase)、测试集(TestSuite)、测试运行器(TestRunner)

首先,把这三个定义简单的说明一下: 1、测试类(TestCase):一个包含一个或是多个测试的类,在Junit中就是指的是包含那些带有@Test注解的方法的类,同一样也被称作“测试用例”; 2、测...
  • yezis
  • yezis
  • 2015年01月25日 21:00
  • 7029

关于 “The import junit cannot be resolved”的提示错误

关于 “The import junit cannot be resolved”的提示错误 笨死了,刚刚在Eclipse里面导入一个项目,用的是Junit做单元测试,直接导入...
  • frt007
  • frt007
  • 2016年02月20日 16:03
  • 2728

android.test.InstrumentationTestRunner解析

在学习Android、JUnit的过程中,随着学习的深入,发现相关的内容越来越多,将这些类按照继承关系整理如下: Test—TestCase—AndroidTestCaseTest—TestCa...
  • zhaoweixing1989
  • zhaoweixing1989
  • 2013年01月23日 11:15
  • 8286

自作聪明的junit.swingui.TestRunner

问题在junit.swingui.TestRunner的时候发现TestRunner启动过程中报错:log4j:ERROR A "org.apache.log4j.ConsoleAppender" o...
  • emu
  • emu
  • 2005年04月29日 10:48
  • 7628

自作聪明

公司的网络一直不太好,经常发生连不上在外部的数据库服务器的问题。昨天忽然想到,我自己的机器上不是装了mysql了么?只要把hibernate的配置文件的数据库连接字符串还有方言之类的由oracle改成...
  • chenxizhiyi
  • chenxizhiyi
  • 2010年12月07日 10:10
  • 281
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:自作聪明的junit.swingui.TestRunner
举报原因:
原因补充:

(最多只允许输入30个字)