emu in blogjava

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            171 隨筆 :: 103 文章 :: 1052 評論 :: 2 Trackbacks
          自作聰明的junit.swingui.TestRunner

          問題
          在junit.swingui.TestRunner的時(shí)候發(fā)現(xiàn)TestRunner啟動(dòng)過程中報(bào)錯(cuò):
          log4j:ERROR A "org.apache.log4j.ConsoleAppender" object is not assignable to a "org.apache.log4j.Appender" variable.
          同時(shí)也發(fā)現(xiàn)一個(gè)平時(shí)工作正常的類在使用junit.swingui.TestRunner進(jìn)行測試的時(shí)候報(bào)告一個(gè)奇怪的 ClassCastException,明明構(gòu)造的對象的類是實(shí)現(xiàn)了指定的接口的,可是就是無法造型到接口上。
          進(jìn)一步研究發(fā)現(xiàn),即使造型回原來的類也不行,雖然調(diào)試的時(shí)候顯示構(gòu)造的對象就是指定的類,但是就是無法造型成這個(gè)類,一度認(rèn)為是妖人作祟或者機(jī)子被落了降頭。

          研究
          求得莊老大再次出手,一下指出指出問題在于不同的類裝載器裝載的類無法相互造型的。于是進(jìn)去junit.swingui.TestRunner里面去找類裝載器,一翻折騰之后終于找到:

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

          在BaseTestRunner里面定義了這樣一個(gè)方法:
              public TestSuiteLoader getLoader() {
                  if (useReloadingTestSuiteLoader())
                      return new ReloadingTestSuiteLoader();
                  return new StandardTestSuiteLoader();
              }
          不過注意到j(luò)unit.textui.TestRunner是不會出上面的錯(cuò)誤的,因?yàn)樗约褐剌d了getLoader()方法,
              /**
               * Always use the StandardTestSuiteLoader. Overridden from
               * BaseTestRunner.
               */
              public TestSuiteLoader getLoader() {
                  return new StandardTestSuiteLoader();
              }
          但是junit.swingui.TestRunner就很自作聰明了,為了避免每次在點(diǎn)“run”按鈕的時(shí)候裝載運(yùn)行器本身,就直接使用了基類的方法去獲取裝載器),這樣基類就可以調(diào)用自己的getLoader方法來決定要啟用那個(gè)classloader:

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

          如果我們用sun的jdk的話,這個(gè)方法會返回一個(gè)TestCaseClassLoader對象,而這個(gè)對象在裝載class的時(shí)候總是調(diào)用creatLoader方法:
              protected TestCaseClassLoader createLoader() {
                  return new TestCaseClassLoader();
              }

          返回的其實(shí)是TestCaseClassLoader。這樣如果被測試類使用了log4j的話,會造成org.apache.log4j.Appender類被 sun.misc.Launcher$AppClassLoader(也就是sun.misc.Launcher類的嵌入類AppClassLoader)裝載一次(在啟動(dòng)test的過程中vm自動(dòng)裝載被引用到的類),然后在運(yùn)行的時(shí)候又被junit.runner.TestCaseClassLoader再裝載一次。由兩個(gè)裝載器裝載進(jìn)來的類不管是不是來自同一個(gè).class文件,都會被認(rèn)為是兩個(gè)不同的類。因此就造成了上面的錯(cuò)誤。
          同樣的,如果你在自己的代碼里面這樣裝載類:
          MyClass myClass = (MyClass)Thread.currentThread().getContextClassLoader().loadClass(mClassName);
          也會造成相同的問題并拋出ClassCastException。因?yàn)镸yClass是在運(yùn)行測試的過程由junit.runner.TestCaseClassLoader裝載的,而Thread.currentThread().getContextClassLoader()卻指向的是sun.misc.Launcher$AppClassLoader。

          解決方法
          1 java -Dlog4j.ignoreTCL junit.swingui.TestRunner
          我猜TCL是ThreadClassLoader的縮寫,這個(gè)參數(shù)的意思大概就是讓log4j忽略Thread自己的類裝載器(sun.misc.Launcher$AppClassLoader),改而使用當(dāng)前Class的裝載器(junit.runner.TestCaseClassLoader)來裝載。但是這個(gè)方法只能解決log4j的錯(cuò)誤報(bào)告(改變了org.apache.log4j.ConsoleAppender的裝載方式),但是對我們自己寫的代碼中的問題卻沒有作用。

          2 在我們自己的類里面寫上一段靜態(tài)代碼:
            static{
                  Thread.currentThread().setContextClassLoader(MyClassFactory.class.getClassLoader());
            }
          和方法一類似,這也是在工廠類中用加載了當(dāng)前l(fā)ass的裝載器(TestCaseClassLoader)來代替Thread的初始化裝載器sun.misc.Launcher$AppClassLoader。這個(gè)方法可以解決我們自己代碼中的問題,并且不會帶來影響原來的其他代碼。結(jié)合第一種方法可以解決上面的兩個(gè)問題。但是如果你有好幾個(gè)工廠類,或者你用的其他包里面用了這樣的裝載方式……那你還可以試試下面的偏門:

          3 注意到BaseTestRunner要進(jìn)行一個(gè)useReloadingTestSuiteLoader()判斷才決定返回哪個(gè)裝載器
          public TestSuiteLoader getLoader() {
              if (useReloadingTestSuiteLoader())
                  return new ReloadingTestSuiteLoader();
              return new StandardTestSuiteLoader();
          }
          我們來看看這個(gè)判斷過程:
          protected boolean useReloadingTestSuiteLoader() {
              return getPreference("loading").equals("true") && !inVAJava() && fLoading;
          }
          嗯,里面有個(gè)inVAJava()是什么玩意兒?
          public static boolean inVAJava() {
              try {
                  Class.forName("com.ibm.uvm.tools.DebugSupport");
              }
              catch (Exception e) {
                  return false;
              }
              return true;
          }
          原來它是想判斷如果當(dāng)前使用的是ibm的虛擬機(jī)就使用默認(rèn)裝載器,但是判斷的條件也忒簡單了點(diǎn),很容易就吧它給蒙過去了:
          在當(dāng)前工程下創(chuàng)建com.ibm.uvm.tools包,在其中創(chuàng)建DebugSupport類:
          package com.ibm.uvm.tools;
          public class DebugSupport{}
          沒有錯(cuò),就這個(gè)空白的類,這樣就可以把junit.swingui.TestRunner給蒙倒。這樣做據(jù)說的副作用是,每次點(diǎn)run按鈕的時(shí)候,都要重起gui環(huán)境,但是我沒有發(fā)現(xiàn)有什么區(qū)別。不過要是沒有區(qū)別,人家又干嗎費(fèi)那么多事呢?不解。

          參考資料

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


          [點(diǎn)擊此處收藏本文]
          發(fā)表于 2005年04月29日 10:48 AM


          maggie 發(fā)表于2005-04-29 11:43 AM  
          果然看不不懂

          emu 發(fā)表于2005-04-29 6:02 PM  
          你將來長大了就懂了呵呵

          Pingback/Trackback 發(fā)表于2005-04-29 6:36 PM  
          試一試

          linux_china 發(fā)表于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 閱讀(2506) 評論(2)  編輯  收藏 所屬分類: java技術(shù)測試技術(shù)

          評論

          # 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
          的控件的東西  回復(fù)  更多評論
            

          # re: 自作聰明的junit.swingui.TestRunner 2015-11-09 21:41 yqbjtu
          我也遇到了log4j錯(cuò)誤問題,但是通過刪除其中一個(gè)jar包中加載的log4j就解決了,不過樓主分析的很好  回復(fù)  更多評論
            

          主站蜘蛛池模板: 恩施市| 玉树县| 法库县| 工布江达县| 松原市| 健康| 镇巴县| 宁陵县| 盐池县| 沈阳市| 十堰市| 舞阳县| 邢台县| 兴义市| 东阿县| 栖霞市| 池州市| 伊金霍洛旗| 通许县| 林甸县| 泽州县| 阜新| 四会市| 乡宁县| 平邑县| 张北县| 同仁县| 靖安县| 乌兰县| 无棣县| 弥勒县| 慈溪市| 白朗县| 宝山区| 天峨县| 武川县| 唐河县| 思南县| 日喀则市| 房山区| 孝义市|