<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    少年阿賓

    那些青春的歲月

      BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
      500 Posts :: 0 Stories :: 135 Comments :: 0 Trackbacks

    #

    //UserDao.java

    package com.abin.lee.jmock;

    /**
     * Created with IntelliJ IDEA.
     * User: abin
     * Date: 13-4-23
     * Time: 上午11:07
     * To change this template use File | Settings | File Templates.
     */
    public interface UserDao {
        public String getMessage(String message);
    }





    //UserService.java

    package com.abin.lee.jmock;

    /**
     * Created with IntelliJ IDEA.
     * User: abin
     * Date: 13-4-23
     * Time: 上午11:06
     * To change this template use File | Settings | File Templates.
     */
    public class UserService {
       private UserDao userDao;

       public String findMessage(String message){
           return this.userDao.getMessage(message);
       }

        public void setUserDao(UserDao userDao){
            this.userDao=userDao;
        }
    }





    //UserManageTest.java

    package com.abin.lee.jmock;

    import junit.framework.TestCase;
    import org.jmock.Expectations;
    import org.jmock.Mockery;
    import org.junit.Test;

    /**
     * Created with IntelliJ IDEA.
     * User: abin
     * Date: 13-4-23
     * Time: 上午11:08
     * To change this template use File | Settings | File Templates.
     */
    public class UserManageTest {
        @Test
        public void testUserManage(){
            // 建立一個test上下文對象。
            Mockery mockery=new Mockery();
            // 生成一個mock對象
            final UserDao userDao=mockery.mock(UserDao.class);
            // 設置期望。
            mockery.checking(new Expectations(){
                    {
                        // 當參數為"abin"的時候,userDao對象的getMessage方法被調用一次,并且返回西安。
                        oneOf(userDao).getMessage("abin");
                        will(returnValue("abin"));
                    }
            });
            UserService userService=new UserService();
            userService.setUserDao(userDao);
            String message=userService.findMessage("abin");
            System.out.println("message="+message);
            TestCase.assertEquals("abin",message);

        }
    }

    posted @ 2013-04-23 11:29 abin 閱讀(585) | 評論 (0)編輯 收藏

    手頭的項目越來越大,很多以前不會出現的問題開始浮現。

     

    比如:我修改了一個基礎的類庫,卻意外的影響了九重天外的客戶項目,直接導致一個功能無法實現。我郁悶啊!!!

     

    因此開始要有組織、有預謀、有計劃的對項目過程進行測試驅動了。最終目標是,我修改了底層某個dll的某個方法,測試框架能夠自動幫我找出來所有收到影響的類,全部執行一次回歸測試,并發送一份漂亮的報告到我手里。

     

    這個目標估計1、2個星期才能實現,不過現在先放出一個非常漂亮的MOCK核心代碼。 

     

    研究過程

    在不斷收集各種資料過程中,學習了很多,例如以下關鍵字,有興趣的兄弟可以自己搜索一下:

     

    testdriven.net, nunit,  typemock, cruiseControl.net, Confluence, JIRE, NUnitForms, WatiN, MBUnit, CSUnit, NBehave, Gallio

    ranorex,  dynamicProxy...

     

    估計各位有時間看看上面的簡介,就能夠掌握現在測試驅動的大致發展。

     

    1. nunit的核心代碼非常容易理解,大伙自己下載看看就行了。
    2. testdriven.net 的前身是:NUnitAddin, 如果要研究如何testdriven集成到vs,看看不錯。
    3. WatiN的核心代碼雖然我沒有看,不過猜也能猜到,就是調用了IE的核,然后搜索上面的html標簽操作。
    4. typeMock有點混蛋,源碼混淆了,無法拿到核心技術,不過從介紹來看是源自了castle.DynamicProxy,那么各位可以參觀一下一篇非常垃圾的文章,但是起碼讓我入門了: http://www.cublog.cn/u/23701/showart_1414436.html
    5. 最后,我來到了Moq,開始因為聽說是.net3.5,就沒有看源碼,不過剛才研究了一下頓時非常興奮。起碼Moq能讓我解決了50%的工作。

     

    接下來就說下Mock技術和測試驅動中的作用。

     

    Mock技術 

    我最不喜歡老外造名詞,所以會用自己的體會去介紹。

    mock的本質就是模擬目標對象的一個假對象。 

    這個性質在測試驅動中非常有用,例如我的代碼是:

    代碼
            public DateTime GetNextFiredDate(DateTime now, IOrmScheduleTrigger trigger, int triggeredtimes)
            {
                
    return GetNextFiredDate(now, trigger.TriggerType, trigger.TriggerExpression, triggeredtimes);
            }

     

     

     現在要測試這個代碼,就需要傳入一個IOrmScheduleTrrigger的接口對象。但是不幸的是,這個對象是個ORM,要啟動這個對象,就涉及到了數據庫。。。。

     

    老大,我只是想測試一下一輛寶馬的玻璃是否堅硬,不需要啟動我的寶馬加速到120km,然后再用手去翹翹那塊玻璃吧。

     

    所以,我希望能夠有個模擬對象,繼承了這個接口, 同時提供了我期望的返回值,讓這個方法能夠順利執行。

     

    傳統的傻逼方法,就是自己寫一個類,繼承了這個接口,然后才傳入進去。例如:

     

    public class OrmScheduleTriggerTestObject : IOrmScheduleTrigger
    {
    // some method here
    }

     

     

    這樣不就更加的傻逼了?我為了測一塊玻璃,還親自造了另外一臺簡易的寶馬出來? 于是我開始翻閱各種文獻,甚至考慮使用動態代理(DynamicProxy)。動態代理的核心思想就是在代碼運行中寫IL生成一個繼承類。這個技術很有用,但是現在我還用不上(就像martin fowler說的,typemock就等于把核武器交給了一個4歲小孩)。

     

    于是我繼續尋找,終于翻開了Moq的源碼,找到了答案。

     

    先看看以下一段代碼,是我摘自Moq源碼的核心部分,稍加改造了:

     

    復制代碼
    代碼
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.Remoting.Proxies;
    using System.Runtime.Remoting.Messaging;

    namespace Pixysoft.Framework.TestDrivens
    {
        
    public class Mock<TInterface> : RealProxy
        {
            
    public Mock()
                : 
    base(typeof(TInterface))
            {
            }

            
    public TInterface Value
            {
                
    get
                {
                    
    return (TInterface)this.GetTransparentProxy();
                }
            }

            
    public override IMessage Invoke(IMessage msg)
            {
                IMethodCallMessage methodCall 
    = msg as IMethodCallMessage;

                
    //我返回int = 1

                
    return new ReturnMessage(1null0null, methodCall);
            }
        }

        
    public interface IMock
        {
            
    int Devide(int a, int b);
        }

        
    public class testrealproxy //測試代碼在這里!!!
        {
            
    public void test()
            {
                IMock mock 
    = new Mock<IMock>().Value;

                Console.WriteLine(mock.Devide(
    12));

                
    //輸出 = 1
            }
        }
    }
    復制代碼

     

     

    這段代碼就是Moq的核心思想。

     

    大概意思是:我希望調用接口IMock的方法Devide,但是我壓根不想寫這個接口的實現。

     

    那么我先寫一個通用的模擬對象Mock<TInterface>,繼承了RealProxy。

     

    然后通過調用Value就可以返回需要的接口對象。而這個對象就是 return (TInterface)this.GetTransparentProxy();是個透明代理。

     

    最后當我調用了 int Devide(int a, int b); 方法的時候,等于調用了public override IMessage Invoke(IMessage msg)方法(有點點的AOP感覺)。

     

    后記

     

    上文就是Moq的核心思想了。非常的精彩!估計有了思路,各位就可以制造自己的原子彈了。

     

    這里插句題外話,很多人抨擊重復造輪子。我就奇怪了。如果我造一個輪子花費的時間和學習用一個輪子的時間差不遠,為什么不造一個?

    而且,用別人的輪子,經常出現的情況是:很多輪子不知道挑哪個。一旦挑上了,項目進展到一般才發現不適合、有bug,于是又重頭挑另外的輪子。

     

    這個經歷是真實的。當年讀大學,我的室友就是典型的挑輪子,他懂得很多框架(java),webwork,hibernate, spring。和人砍起來朗朗上口,但是需要深入做項目了,出現問題基本上不知所措,不是翻文獻,就是問師兄,最后整個項目組從來就沒有一個成品。

     

    我自從學電腦依賴,從來就沒有用過別人的輪子,即使是hibernate,我的確也沒有用過,不過他的核心文檔倒是看過,對比之下,和oracle的toplink相比簡直就是小孩。

     

    比我牛逼的兄弟大有人在,希望各位牛人不要浪費自己的時間去挑別人的輪子,直接自己造一個算了。 

     

    最后說說接下來的工作。

     

    基于接口的測試驅動完成了,剩下的就是面對sealed class 等頑固分子了, 必然需要動用非常規武器,DynamicProxy。下回再見。

     

     

    分享到:
    posted @ 2013-04-20 15:29 abin 閱讀(492) | 評論 (0)編輯 收藏

    1 現有的單元測試框架
           單元測試是保證程序正確性的一種有效的測試手段,對于不同的開發語言,通常都能找到相應的單元框架。



           借助于這些單測框架的幫助,能夠使得我們編寫單元測試用例的過程變得便捷而優雅。框架幫我們提供了case的管理,執行,斷言集,運行參數,全局事件工作,所有的這些使得我們只需關注:于對于特定的輸入,被測對象的返回是否正常。
           那么,這些xUnit系列的單元測試框架是如何做到這些的了?分析這些框架,發現所有的單元測試框架都是基于以下的一種體系結構設計的。

     
           如上圖所示,單測框架中通常包括TestRunner, Test, TestResult, TestCase, TestSuite, TestFixture六個組件。
    TestRuner:負責驅動單元測試用例的執行,匯報測試執行的結果,從而簡化測試
    TestFixture:以測試套件的形式提供setUp()和tearDown()方法,保證兩個test case之間的執行是相互獨立,互不影響的。
    TestResult:這個組件用于收集每個test case的執行結果
    Test:作為TestSuite和TestCase的父類暴露run()方法為TestRunner調用
    TestCase:暴露給用戶的一個類,用戶通過繼承TestCase,編寫自己的測試用例邏輯
    TestSuite:提供suite功能管理testCase
           正因為相似的體系結構,所以大多數單元測試框架都提供了類似的功能和使用方法。那么在單測中引入單元測試框架會帶來什么好處,在現有單元測試框架下還會存在什么樣不能解決的問題呢?
    2 單元測試框架的優點與一些問題
           在單元測試中引入單測框架使得編寫單測用例時,不需要再關注于如何驅動case的執行,如何收集結果,如何管理case集,只需要關注于如何寫好單個測試用例即可;同時,在一些測試框架中通過提供豐富的斷言集,公用方法,以及運行參數使得編寫單個testcase的過程得到了最大的簡化。
           那這其中會存在什么樣的疑問了?
           我在單元測試框架中寫一個TestCase,與我單獨寫一個cpp文件在main()方法里寫測試代碼有什么本質卻別嗎?用了單元測試框架,并沒有解決我在對復雜系統做單測時遇到的問題。
           沒錯,對于單個case這兩者從本質上說是沒有區別的。單元測試框架本身并沒有告訴你如何去寫TestCase,在這一點上他是沒有提供任何幫助的。所以對于一些復雜的場景,只用單元測試框架是有點多少顯得無能為力的。
           使用單元測試框架往往適用于以下場景的測試:單個函數,一個class,或者幾個功能相關class的測試,對于純函數測試,接口級別的測試尤其適用,如房貸計算器公式的測試。
           但是,對于一些復雜場景:
    ? 被測對象依賴復雜,甚至無法簡單new出這個對象
    ? 對于一些failure場景的測試
    ? 被測對象中涉及多線程合作
    ? 被測對象通過消息與外界交互的場景
    ? …
           單純依賴單測框架是無法實現單元測試的,而從某種意義上來說,這些場景反而是測試中的重點。
           以分布式系統的測試為例,class 與 function級別的單元測試對整個系統的幫助不大,當然,這種單元測試對單個程序的質量有幫助;分布式系統測試的要點是測試進程間的交互:一個進程收到客戶請求,該如何處理,然后轉發給其他進程;收到響應之后,又修改并應答客戶;同時分布式系統測試中通常更關注一些異常路徑的測試,這些場景才是測試中的重點,也是難點所在。
           Mock方法的引入通常能幫助我們解決以上場景中遇到的難題。
    3 Mock的引入帶來了什么
           在維基百科上這樣描述Mock:In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A computer programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior. of a human in vehicle impacts.
           Mock通常是指,在測試一個對象A時,我們構造一些假的對象來模擬與A之間的交互,而這些Mock對象的行為是我們事先設定且符合預期。通過這些Mock對象來測試A在正常邏輯,異常邏輯或壓力情況下工作是否正常。
           引入Mock最大的優勢在于:Mock的行為固定,它確保當你訪問該Mock的某個方法時總是能夠獲得一個沒有任何邏輯的直接就返回的預期結果。
           Mock Object的使用通常會帶來以下一些好處:
    ? 隔絕其他模塊出錯引起本模塊的測試錯誤。
    ? 隔絕其他模塊的開發狀態,只要定義好接口,不用管他們開發有沒有完成。
    ? 一些速度較慢的操作,可以用Mock Object代替,快速返回。
           對于分布式系統的測試,使用Mock Object會有另外兩項很重要的收益:
    ? 通過Mock Object可以將一些分布式測試轉化為本地的測試
    ? 將Mock用于壓力測試,可以解決測試集群無法模擬線上集群大規模下的壓力
    4 Mock的應用場景
           在使用Mock的過程中,發現Mock是有一些通用性的,對于一些應用場景,是非常適合使用Mock的:
    ? 真實對象具有不可確定的行為(產生不可預測的結果,如股票的行情)
    ? 真實對象很難被創建(比如具體的web容器)
    ? 真實對象的某些行為很難觸發(比如網絡錯誤)
    ? 真實情況令程序的運行速度很慢
    ? 真實對象有用戶界面
    ? 測試需要詢問真實對象它是如何被調用的(比如測試可能需要驗證某個回調函數是否被調用了)
    ? 真實對象實際上并不存在(當需要和其他開發小組,或者新的硬件系統打交道的時候,這是一個普遍的問題)
           當然,也有一些不得不Mock的場景:
    一些比較難構造的Object:這類Object通常有很多依賴,在單元測試中構造出這樣類通常花費的成本太大。
    ? 執行操作的時間較長Object:有一些Object的操作費時,而被測對象依賴于這一個操作的執行結果,例如大文件寫操作,數據的更新等等,出于測試的需求,通常將這類操作進行Mock。
    ? 異常邏輯:一些異常的邏輯往往在正常測試中是很難觸發的,通過Mock可以人為的控制觸發異常邏輯。
            在一些壓力測試的場景下,也不得不使用Mock,例如在分布式系統測試中,通常需要測試一些單點(如namenode,jobtracker)在壓力場景下的工作是否正常。而通常測試集群在正常邏輯下無法提供足夠的壓力(主要原因是受限于機器數量),這時候就需要應用Mock去滿足。
            在這些場景下,我們應該如何去做Mock的工作了,一些現有的Mock工具可以幫助我們進行Mock工作。
    5 Mock工具的介紹
           手動的構造 Mock 對象通常帶來額外的編碼量,而且這些為創建 Mock 對象而編寫的代碼很有可能引入錯誤。目前,有許多開源項目對動態構建 Mock 對象提供了支持,這些項目能夠根據現有的接口或類動態生成,這樣不僅能避免額外的編碼工作,同時也降低了引入錯誤的可能。
    C++:   GoogleMock   http://code.google.com/p/googlemock/

    Java:   EasyMock   http://easymock.org/

           通常Mock工具通過簡單的方法對于給定的接口生成 Mock 對象的類庫。它提供對接口的模擬,能夠通過錄制、回放、檢查三步來完成大體的測試過程,可以驗證方法的調用種類、次數、順序,可以令 Mock 對象返回指定的值或拋出指定異常。通過這些Mock工具我們可以方便的構造 Mock 對象從而使單元測試順利進行,能夠應用于更加復雜的測試場景。
           以EasyMock為例,通過 EasyMock,我們可以為指定的接口動態的創建 Mock 對象,并利用 Mock 對象來模擬協同模塊,從而使單元測試順利進行。這個過程大致可以劃分為以下幾個步驟:
    使用 EasyMock 生成 Mock 對象
    ? 設定 Mock 對象的預期行為和輸出 
    ? 將 Mock 對象切換到 Replay 狀態
    ? 調用 Mock 對象方法進行單元測試
    ? 對 Mock 對象的行為進行驗證
    EasyMock的使用和原理:  http://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/

           EasyMock 后臺處理的主要原理是利用 java.lang.reflect.Proxy 為指定的接口創建一個動態代理,這個動態代理,就是我們在編碼中用到的 Mock 對象。EasyMock 還為這個動態代理提供了一個 InvocationHandler 接口的實現,這個實現類的主要功能就是將動態代理的預期行為記錄在某個映射表中和在實際調用時從這個映射表中取出預期輸出。
           借助類似于EasyMock這樣工具,大大降低了編寫Mock對象的成本,通常來說Mock工具依賴于單元測試框架,為用戶編寫TestCase提供便利,但是本身依賴于單元測試框架去驅動,管理case,以及收集測試結果。例如EasyMock依賴于JUint,GoogleMock依賴于Gtest。
           那么有了單元測試框架和相應的Mock工具就萬事俱備了,還有什么樣的問題?正如單元測試框架沒有告訴你如何寫TestCase一樣,Mock工具也沒有告訴你如何去選擇Mock的點。
    6 如何選擇恰當的mock點
           對于Mock這里存在兩個誤區,1.是Mock的對象越多越好;2.Mock會引入巨大的工作量,通常得不償失。這都是源于不恰當的Mock點的選取。
           這里說的如何選擇恰當的mock點,是說對于一個被測對象,我們應當在外圍選擇恰當的mock對象,以及需要mock的接口。因為對于任意一個對象,任意一段代碼邏輯我們都是有辦法進行Mock的,而Mock點選擇直接決定了我們Mock的工作量以及測試效果。從另外一種意義上來說,不恰當Mock選擇反而會對我們的測試產生誤導,從而在后期的集成和系統測試中引入更多的問題。
           在mock點的選擇過程中,以下的一些點會是一些不錯的選擇
    ? 網絡交互:如果兩個被測模塊之間是通過網絡進行交互的,那么對于網絡交互進行Mock通常是比較合適的,如RPC
    ? 外部資源:比如文件系統、數據源,如果被測對象對此類外部資源依賴性非常強,而其行為的不可預測性很可能導致測試的隨機失敗,此類的外部資源也適合進行Mock。
    ? UI:因為UI很多時候都是用戶行為觸發事件,系統本身只是對這些觸發事件進行相應,對這類UI做Mock,往往能夠實現很好的收益,很多基于關鍵字驅動的框架都是基于UI進行Mock的
    ? 第三方API:當接口屬于使用者,通過Mock該接口來確定測試使用者與接口的交互。
           當然如何做Mock一定是與被系統的特性精密關聯的,一些強制性的約束和規范是不合適的。這里介紹幾個做的比較好的mock的例子。
           1. 殺毒軟件更新部署模塊的Mock
           這個例子源于一款殺毒產品的更新部署模塊的測試。對于一個殺毒軟件客戶端而言,需要通過更新檢查模塊與病毒庫Server進行交互,如果發現病毒庫有更新則觸發病毒庫部署模塊的最新病毒庫的數據請求和部署工作,要求部署完成后殺毒軟件客戶端能夠正常工作。
     

            對于這一場景的測試,當時受限于這樣一個條件,通常的病毒庫server通常最多一天只更新一次病毒庫,也就是說如果使用真實的病毒庫server,那么針對更新部署模塊的測試一天只能被觸發一次。這是測試中所不能容忍的,通過對病毒庫server進行mock可以解決這個問題。
           對于這個場景可以采取這樣一種Mock方式:用一個本地文件夾來模擬病毒庫server,選擇更新部署模塊與病毒庫server之間交互的兩個函數checkVersion(),reqData()函數進行Mock。
           checkVersion()工作原先的工作是檢查病毒庫Server的版本號,以決定是否觸發更新,將其行為Mock為檢查一個本地文件夾中病毒庫的版本號;reqData()原有的行為是從病毒庫Server拖取病毒庫文件,將其Mock為從本地文件夾中拖取病毒庫文件。通過這種方式我們用一個本地文件夾Mock病毒庫Server的行為,其帶來的產出是:我們可以隨意的觸發病毒庫更新操作以及各種異常。通過這種方式發現了一個在更新部署過程中,病毒庫Server的病毒庫版本發生改變造成出錯的嚴重bug,這個是在原有一天才觸發一次更新操作的情況下永遠也無法發現的。
           2. 分布式系統中對NameNode模塊的測試
     

           在測試NameNode模塊的過程中存在這樣一個問題,在正常邏輯無壓力條件下NameNode模塊都是工作正常的。但是線上集群在大壓力的情況下,是有可能觸發NameNode的問題的。但是原有的測試方法下,我們是無法對NameNode模擬大壓力的場景的(因為NameNode的壓力主要來源于DateNode數量,而我們測試集群是遠遠無法達到線上幾千臺機器的規模的),而NameNode單點的性能瓶頸問題恰恰是測試的重點,真實的DataNode是無法滿足測試需求的,我們必須對DataNode進行Mock。
     

           如何對DateNode進行Mock了,最直觀的想法是選擇NameNode與DataNode之間的交互接口進行Mock,也就是他們之間的RPC交互,但是由于NameNode與DataNode之間的交互信息種類很多,所以其實這并不是一種很好的選擇。
           換個角度來想,NameNode之上的壓力是源于對HDFS的讀寫操作造成的NameNode上元數據的維護,也就是說,對于NameNode而言,其實他并不關心數據到底寫到哪里去了,只關心數據是否讀寫成功。如果是這種場景Mock就可以變的簡單了,我們可以直接將DataNode上對塊的操作進行mock,比如,對一次寫請求,DataNode并不觸發真實的寫操作,而直接返回成功。通過這種方式,DataNode去除了執行功能,只保留了消息交互功能,間接的實現了我們的測試需求,且工作量比之第一種方案小很多。
           3. 開源社區提供的MRUnit測試框架
           在原有框架下,對于MapReduce程序的測試通常是無法在本地驗證的,更不用說對MapReduce程序進行單測了。而MRUnit通過一個簡單而優雅的Mock,卻實現了一個基于MapReduce程序的單測框架。

    基于MRUINT框架可以將單測寫成如下形式:

     

           在這個框架中定義了MapDriver,ReducerDriver,MapReduceDriver三個有點類似容器的driver,通過driver來驅動map,reduce或者整個mapreduce過程的執行。
           如上例,在driver中設定mapper為IdentityMapper,通過withInput方法設定輸入數據,通過withOutput方法設定預期結果,通過runTest方法來觸發執行并進行結果檢測
           他的實現原理是將outputCollector做Mock,outputCollectort中的emit方法實現的邏輯是將數據寫到文件系統中,Mock后是通過另外一個進程去收集數據并保存在內存中,從而實現最終結果的可檢驗(在自己的數據結構中比對結果)。
           實現的原理很簡單,這樣做mock就會精巧,只選擇最底層的一些簡單卻又依賴廣泛的點(依賴廣泛指模塊間的數據流通常都走這樣的點過)做mock,這樣通常效果很好且簡單
           當然這個例子中也有一些缺陷:1.因為在outputcollector層做mock的數據截取,使得無法過partition的分桶邏輯;2.這個框架是寫內存的,無法最終改成壓力性能測試工具。

    7 附錄
    1. EasyMock示例:
     

    posted @ 2013-04-20 15:21 abin 閱讀(419) | 評論 (0)編輯 收藏

    1、什么情況下會使用mock技術

      (1)需要將當前被測單元和其依賴模塊獨立開來,構造一個獨立的測試環境,不關注被測單元的依賴對象,只關注被測單元的功能邏輯

      ----------比如被測代碼中需要依賴第三方接口返回值進行邏輯處理,可能因為網絡或者其他環境因素,調用第三方經常會中斷或者失敗,無法對被測單元進行測試,這個時候就可以使用mock技術來將被測單元和依賴模塊獨立開來,使得測試可以進行下去。

      (2)被測單元依賴的模塊尚未開發完成,而被測單元需要依賴模塊的返回值進行后續處理

      ----------比如service層的代碼中,包含對Dao層的調用,但是,DAO層代碼尚未實現

      (3)被測單元依賴的對象較難模擬或者構造比較復雜

      ----------比如,支付寶支付的異常條件有很多,但是模擬這種異常條件很復雜或者無法模擬,比如,查詢聚劃算的訂單結果,無法在測試環境進行模擬

      2、Mock技術分類

      (1)手動構造mock對象

      ---------------比如,可以自己寫某個接口方法的實現,根據需要編寫返回值,測試代碼中使用該實現類對象

      缺點:會增加代碼量,在寫mock對象代碼時,有可能引入錯誤

      (2)使用開源代碼提供的構造mock方法

      --------------比如easyMock,提供了對接口類的模擬,能夠通過錄制、回放、檢查三步來完成大體的測試過程,可以驗證方法的調用種類、次數、順序,可以令Mock對象返回指定的值或拋出指定異常

      3、EasyMock使用

      (1)引入easyMock

      ------------在maven工程中,通過pom配置依賴關系

    <dependency>
        <groupId>org.easymock</groupId>
        <artifactId>easymock</artifactId>
        <version>3.0</version>
        <scope>test</scope>
    </dependency>

      ------------在普通java工程中,通過添加外部包的方式

      (2)使用easyMock過程

      1)使用EasyMock生成Mock對象;
      pingJiaDao = mockControl.createMock(IPingJiaDao.class);

      2)設定Mock對象的預期行為和輸出;
      EasyMock.expect(pingJiaDao.getGoodPingJiaRate(storeId)).andReturn(0.11);

      3)將Mock對象切換到Replay狀態;
      EasyMock.replay(pingJiaDao);

      4)調用Mock對象方法進行單元測試
      storeService.setStoredao(pingJiaDao);
      double rate = storeService.getStoreGoodRate(storeId);

      5)對Mock對象的行為進行驗證。
      EasyMock.verify(pingJiaDao);

      4、其他easyMock功能

      (1)特殊的mock對象:niceMock
      (2)參數匹配器
      (3)重置mock對象
      (4)模擬異常拋出
      (5)設置調用次數

    posted @ 2013-04-20 15:19 abin 閱讀(416) | 評論 (0)編輯 收藏

      /** 
         * HTTP DELETE方法進行刪除操作 
         * @param url 
         * @param map 
         * @return 
         * @throws ClientProtocolException 
         * @throws IOException 
         */  
        public static String remoteDelete(String url, Map<String, String> map) throws ClientProtocolException, IOException{  
            url = JETSUM_PLATFORM_SERVER+url;  
            HttpClient httpclient = new DefaultHttpClient();  
            HttpDelete httpdelete= new HttpDelete();  
            List<NameValuePair> formparams = setHttpParams(map);  
            String param = URLEncodedUtils.format(formparams, "UTF-8");  
            httpdelete.setURI(URI.create(url + "?" + param));  
            HttpResponse response = httpclient.execute(httpdelete);  
            String httpEntityContent = GetHttpEntityContent(response);  
            httpdelete.abort();  
            return httpEntityContent;     
        }       




       /** 
         * 設置請求參數 
         * @param map 
         * @return 
         */  
        private static List<NameValuePair> setHttpParams(Map<String, String> map) {  
            List<NameValuePair> formparams = new ArrayList<NameValuePair>();  
            Set<Map.Entry<String, String>> set = map.entrySet();  
            for (Map.Entry<String, String> entry : set) {  
                formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));  
            }  
            return formparams;  
        }  



     /** 
         * 獲得響應HTTP實體內容 
         * @param response 
         * @return 
         * @throws IOException 
         * @throws UnsupportedEncodingException 
         */  
        private static String GetHttpEntityContent(HttpResponse response)  
                throws IOException, UnsupportedEncodingException {  
            HttpEntity entity = response.getEntity();  
            if (entity != null) {  
                InputStream is = entity.getContent();  
                BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));  
                String line = br.readLine();  
                StringBuilder sb = new StringBuilder();  
                while (line != null) {  
                    sb.append(line + "\n");  
                    line = br.readLine();  
                }  
                return sb.toString();  
            }  
            return "";  
        }  
    posted @ 2013-04-20 00:54 abin 閱讀(824) | 評論 (0)編輯 收藏

    怎么配置Tomcat支持HTTP Delete和Put 方法

    如何配置Tomcat支持HTTP Delete和Put 方法
    在tomcat web.xml文件中配置org.apache.catalina.servlets.DefaultServlet的
    <init-param>     
    <param-name>readonly</param-name>
    <param-value>false</param-value>
    </init-param>
    readonly參數默認是true,即不允許delete和put操作,所以通過XMLHttpRequest對象的put或者delete方法訪問就會報告http 403錯誤。為REST服務起見,應該設置該屬性為false。

    posted @ 2013-04-20 00:03 abin 閱讀(1359) | 評論 (0)編輯 收藏

    邏輯:

    String url = "http://www.baidu.com";

    //將要訪問的url字符串放入HttpPost中

    HttpPost httpPost = new HttpPost(url);

    //請求頭 放置一些修改http請求頭和cookie

    httpPost.setHeader("Accept", "application/json");

    ......

    //如果是HttpPost或者HttpPut請求需要在請求里加參數,而HttpGet或者HttpDelete請求則可以直接拼接到url字符串后面

    //向HttpPost中加入參數

    List<NameValuePair> values = new ArrayList<NameValuePair>();

    values.add(new NameValuePair("id", "1"));

    values.add(new NameValuePair("name", "xiaohong"));

    httpPost.setEntity(new UrlEncodeFormEntity(values, HTTP.UTF_8));  //進行轉碼

     

    //實例HttpClient 并執行帶有HttpPost的方法,返回HttpResponse 響應,再進行操作

    HttpClient httpClient = new DefaultHttpClient();

    HttpResponse httpResponse = httpClient.execute(httpPost);

    int statusCode = httpResponse.getStatusLine().getStatusCode();  //返回狀態碼 ,用來進行識別或者判斷訪問結果

    if(statusCode == 200){

      Instream in = httpResponse.getEntity().getContent();  //要處理該數據流是否為GZIP流

    }

     

     

    示例代碼如下:

    package cn.dratek.haoyingsheng.manager.client;

    import cn.dratek.haoyingsheng.manager.util.ResourceUtil;
    import net.dratek.browser.http.Cookie;
    import net.dratek.browser.http.CookieManager;
    import net.dratek.browser.http.URL;
    import org.apache.http.*;
    import org.apache.http.client.entity.UrlEncodedFormEntity;
    import org.apache.http.client.methods.HttpDelete;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.client.methods.HttpPut;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.protocol.HTTP;

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.UnsupportedEncodingException;
    import java.util.List;


    public class HttpNetClient {
    /**
    * 所有get 請求底層調用方法
    *
    * @param url 請求url
    * @return byte[] response data
    */
    public static byte[] doGet(String url) {
    InputStream in;
    byte[] bre = null;
    HttpResponse response;
    CookieManager manager = CookieManager.getInstance();
    if (url != null && url.length() != 0) {
    URL myURL = URL.parseString(url);
    Cookie[] cookies = manager.getCookies(myURL);
    HttpGet httpGet = new HttpGet(url);
    if (cookies != null && cookies.length > 0) {
    StringBuilder sb = new StringBuilder();
    for (Cookie ck : cookies) {
    sb.append(ck.name).append('=').append(ck.value).append(";");
    }
    String sck = sb.toString();
    if (sck.length() > 0) {
    httpGet.setHeader("Cookie", sck);
    }

    }
    httpGet.setHeader("Accept-Encoding", "gzip, deflate");
    httpGet.setHeader("Accept-Language", "zh-CN");
    httpGet.setHeader("Accept", "application/json, application/xml, text/html, text/*, image/*, */*");
    try {
    response = new DefaultHttpClient().execute(httpGet);
    if (response != null) {
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == 200 || statusCode == 403) {
    Header[] headers = response.getHeaders("Set-Cookie");
    if (headers != null && headers.length > 0) {
    for (Header header : headers) {
    manager.setCookie(myURL, header.getValue());
    }
    }
    in = response.getEntity().getContent();
    if (in != null) {
    bre = ResourceUtil.readStream(in);
    }

    }
    }


    } catch (IOException e) {
    e.printStackTrace();
    }
    }


    return bre;

    }

    /**
    * 所有Post 請求底層調用方法
    *
    * @param url 請求url
    * @param values 傳遞的參數
    * @return byte[] 返回數據 or null
    */
    public static byte[] doPost(String url, List<NameValuePair> values) {
    System.out.println("url = " + url);
    byte[] bytes = null;
    HttpResponse response;
    InputStream inputStream = null;
    CookieManager manager = CookieManager.getInstance();
    if (url != null && url.length() != 0) {
    URL myurl = URL.parseString(url);
    Cookie[] cookies = manager.getCookies(myurl);
    HttpPost post = new HttpPost(url);
    if (cookies != null && cookies.length > 0) {
    StringBuilder sb = new StringBuilder();
    for (Cookie ck : cookies) {
    sb.append(ck.name).append('=').append(ck.value).append(";");
    }
    String sck = sb.toString();
    if (sck.length() > 0) {
    post.setHeader("Cookie", sck);
    }

    }
    post.setHeader("Accept-Encoding", "gzip, deflate");
    post.setHeader("Accept-Language", "zh-CN");
    post.setHeader("Accept", "application/json, application/xml, text/html, text/*, image/*, */*");
    DefaultHttpClient client = new DefaultHttpClient();
    try {
    if (values != null && values.size() > 0) {
    post.setEntity(new UrlEncodedFormEntity(values, HTTP.UTF_8));
    }
    response = client.execute(post);
    if (response != null) {
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == 200 || statusCode == 403) {
    Header[] headers = response.getHeaders("Set-Cookie");
    if (headers != null && headers.length > 0) {
    for (Header header : headers) {
    manager.setCookie(myurl, header.getValue());
    }
    }
    inputStream = response.getEntity().getContent();
    }
    }

    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    }
    if (inputStream != null) {
    bytes = ResourceUtil.readStream(inputStream);
    }
    }
    return bytes;
    }

    /**
    * PUT基礎請求
    *
    * @param url 請求地址
    * @param values 提交參數
    * @return byte[] 請求成功后的結果
    */
    public static byte[] doPut(String url, List<NameValuePair> values) {
    byte[] ret = null;

    CookieManager manager = CookieManager.getInstance();
    if (url != null && url.length() > 0) {
    URL myUrl = URL.parseString(url);
    StringBuilder sb = new StringBuilder();
    Cookie[] cookies = manager.getCookies(myUrl);
    if (cookies != null && cookies.length > 0) {
    for (Cookie cookie : cookies) {
    sb.append(cookie.name).append("=").append(cookie.value).append(";");
    }

    }
    HttpPut request = new HttpPut(url);
    String sck = sb.toString();
    if (sck.length() > 0) {
    request.setHeader("Cookie", sck);
    }
    request.setHeader("Accept-Encoding", "gzip, deflate");
    request.setHeader("Accept-Language", "zh-CN");
    request.setHeader("Accept", "application/json, application/xml, text/html, text/*, image/*, */*");

    DefaultHttpClient client = new DefaultHttpClient();
    if (values != null && values.size() > 0) {
    try {
    UrlEncodedFormEntity entity;
    entity = new UrlEncodedFormEntity(values);
    request.setEntity(entity);
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    }
    }
    try {
    HttpResponse response = client.execute(request);
    if (response != null) {
    StatusLine statusLine = response.getStatusLine();
    int statusCode = statusLine.getStatusCode();
    if (statusCode == 200 || statusCode == 403) {
    Header[] headers = response.getHeaders("Set-Cookie");
    if (headers != null && headers.length > 0) {
    for (Header header : headers) {
    manager.setCookie(myUrl, header.getValue());
    }
    }
    HttpEntity entity = response.getEntity();
    InputStream inputStream = entity.getContent();
    if (inputStream != null) {
    ret = ResourceUtil.readStream(inputStream);
    inputStream.close();
    }
    }
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    return ret;
    }

    /**
    * Delete基礎請求
    *
    * @param url 請求地址
    * @return 請求成功后的結果
    */
    public static byte[] doDelete(String url) {

    InputStream in;
    byte[] bre = null;
    HttpResponse response;
    CookieManager manager = CookieManager.getInstance();
    if (url != null && url.length() != 0) {
    URL myurl = URL.parseString(url);
    Cookie[] cookies = manager.getCookies(myurl);
    HttpDelete delete = new HttpDelete(url);
    if (cookies != null && cookies.length > 0) {
    StringBuilder sb = new StringBuilder();
    for (Cookie ck : cookies) {
    sb.append(ck.name).append('=').append(ck.value).append(";");
    }
    String sck = sb.toString();
    if (sck.length() > 0) {
    delete.setHeader("Cookie", sck);
    }

    }
    delete.setHeader("Accept-Encoding", "gzip, deflate");
    delete.setHeader("Accept-Language", "zh-CN");
    delete.setHeader("Accept", "application/json, application/xml, text/html, text/*, image/*, */*");
    try {
    response = new DefaultHttpClient().execute(delete);
    if (response != null) {
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == 200 || statusCode == 403) {
    Header[] headers = response.getHeaders("Set-Cookie");
    if (headers != null && headers.length > 0) {
    for (Header header : headers) {
    manager.setCookie(myurl, header.getValue());
    }
    }
    in = response.getEntity().getContent();
    if (in != null) {
    bre = ResourceUtil.readStream(in);
    }

    }
    }


    } catch (IOException e) {
    e.printStackTrace();
    }
    }


    return bre;
    }

     

    }










    http://www.cnblogs.com/lianghui66/archive/2013/03/06/2946495.html

    posted @ 2013-04-19 23:39 abin 閱讀(1617) | 評論 (0)編輯 收藏

    我是一個初學者. 
    我建了一個classes表和一個students表,表示班級和學生,其中學生里面有一個外鍵關聯到班級表. 
    然后學生類里面建了一個classes的屬性, 
    用session取出學生后,如果關閉session的話,就無法讀取到學生類里的classes值,沒有關閉就能讀取到. 
    請問這個session會不會影響到其他用戶的訪問呢? 
    就是說如果有兩個用戶并行操作數據庫的話,一個用戶的session不關閉影不影響另一個用戶呢?







    J2EE中最大的一個觀念就是分層.. 

    session是持久層的東東.不可竄到別的層.. 

    你的這個問題其實就是延遲加載的問題. 

    從理論的角度講,最好是用一個就關一個.防止資源消耗. 
    但由于hibernate中的延遲加載,所以出現了你的關閉session的話,就無法讀取到學生類里的classes值問題. 
    這個問題可以用Hibernate.initialize()來解決.也可就使用opensessionview的方式.spring中提供了這樣的filter 
    不知道這在使用中有沒有使用spring.用了就比較方便,也不會出現你所說的 

    引用
    一個用戶的session不關閉影不影響另一個用戶

    因為session由spring來管理,很安全,不會出現這個種并發問題. 

    如果只是使用了Hibernate的話,那得注意了.你在servlet中直接創建session就可能出現并發問題,因為session不是線程安全的,而servlet是多線程的. 
    這時可以使用ThreadLocal來解決這個問題. 

    希望對你有所幫助!



    posted @ 2013-04-19 21:28 abin 閱讀(595) | 評論 (0)編輯 收藏

    在一個應用程序中,如果DAO層使用Spring的hibernate模板,通過Spring來控制session的生命周期,則首選getCurrentSession 

    使用Hibernate的大多數應用程序需要某種形式的“上下文相關的”session,特定的session在整個特定的上下文范圍內始終有效。然而,對不同類型的應用程序而言,要給為什么是組成這種“上下文”下一個定義通常是困難的;不同的上下文對“當前”這個概念定義了不同的范圍。在3.0版本之前,使用Hibernate的程序要么采用自行編寫的基于ThreadLocal的上下文session(如下面代碼),要么采用HibernateUtil這樣的輔助類,要么采用第三方框架(比如Spring或Pico),它們提供了基于代理(proxy)或者基于攔截器(interception)的上下文相關session 

    從3.0.1版本開始,Hibernate增加了SessionFactory.getCurrentSession()方法。一開始,它假定了采用JTA事務,JTA事務 定義了當前session的范圍和上下文(scope and context)。Hibernate開發團隊堅信,因為有好幾個獨立的JTA TransactionManager實現穩定可用,不論是否被部署到一個J2EE容器中,大多數(假若不是所有的)應用程序都應該采用JTA事務管理。 基于這一點,采用JTA的上下文相關session可以滿足你一切需要。 

    更好的是,從3.1開始,SessionFactory.getCurrentSession()的后臺實現是可拔插的。因此,我們引入了新的擴展接口 (org.hibernate.context.CurrentSessionContext)和新的配置參數 (hibernate.current_session_context_class),以便對什么是“當前session”的范圍和上下文(scope and context)的定義進行拔插。 

    org.hibernate.context.JTASessionContext - 當前session根據JTA來跟蹤和界定。這和以前的僅支持JTA的方法是完全一樣的。 

    org.hibernate.context.ThreadLocalSessionContext - 當前session通過當前執行的線程來跟蹤和界定。 

    這兩種實現都提供了“每數據庫事務對應一個session”的編程模型,也稱作一請求一事務。即Hibernate的session的生命周期由數據庫事務的生存來控制。假若你采用自行編寫代碼來管理事務(比如,在純粹的J2SE,或者 JTA/UserTransaction/BMT),建議你使用Hibernate Transaction API來把底層事務實現從你的代碼中隱藏掉。如果你在支持CMT的EJB容器中執行,事務邊界是聲明式定義的,你不需要在代碼中進行任何事務或session管理操作。 

    1、getCurrentSession()與openSession()的區別 
    * 采用getCurrentSession()創建的session會綁定到當前線程中,而采用openSession() 
    創建的session則不會 
    * 采用getCurrentSession()創建的session在commit或rollback時會自動關閉,而采用openSession()創建的session必須手動關閉 

    2、使用getCurrentSession()需要在hibernate.cfg.xml文件中加入如下配置: 
    * 如果使用的是本地事務(jdbc事務) 
    <property name="hibernate.current_session_context_class">thread</property> 
    * 如果使用的是全局事務(jta事務) 
    <property name="hibernate.current_session_context_class">jta</property> 

    在SessionFactory啟動的時候,Hibernate會根據配置創建相應的CurrentSessionContext,在 getCurrentSession()被調用的時候,實際被執行的方法是CurrentSessionContext.currentSession()。在currentSession()執行時,如果當前Session 為空,currentSession 會調用SessionFactory 的openSession。所以getCurrentSession() 對于Java EE 來說是更好的獲取Session 的方法。 

    sessionFactory.getCurrentSession()可以完成一系列的工作,當調用時,hibernate將session綁定到當前線程,事務結束后,hibernate將session從當前線程中釋放,并且關閉session,當再次調用getCurrentSession()時,將得到一個新的session,并重新開始這一系列工作。 
    這樣調用方法如下: 
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    session.beginTransaction();
    Event theEvent = new Event();
    theEvent.setTitle(title);
    theEvent.setDate(theDate);
    session.save(theEvent);
    session.getTransaction().commit();



    不需要close session了 

    利于ThreadLocal模式管理Session 
       早在Java1.2推出之時,Java平臺中就引入了一個新的支持:java.lang.ThreadLocal,給我們在編寫多線程程序時提供了一種新的選擇。ThreadLocal是什么呢?其實ThreadLocal并非是一個線程的本地實現版本,它并不是一個Thread,而是thread local variable(線程局部變量)。也許把它命名為ThreadLocalVar更加合適。線程局部變量(ThreadLocal)其實的功用非常簡單,就是為每一個使用某變量的線程都提供一個該變量值的副本,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本沖突。從線程的角度看,就好像每一個線程都完全擁有一個該變量。ThreadLocal是如何做到為每一個線程維護變量的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用于存儲每一個線程的變量的副本。 


    public class HibernateUtil {
        
        private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";
        
        //創建一個局部線程變量
        private static final ThreadLocal<Session> THREAD_LOCAL = new ThreadLocal<Session>();
        
        private static final Configuration cfg = new Configuration();
        
        private static SessionFactory sessionFactory;
        
        /*
         * 取得當前session對象
         
    */
        @SuppressWarnings("deprecation")
        public static Session currentSession() throws HibernateException {
            Session session = (Session)THREAD_LOCAL.get();
            if (session == null) {
                if (sessionFactory == null) {
                    try {
                        cfg.configure(CONFIG_FILE_LOCATION);
                        sessionFactory = cfg.buildSessionFactory();
                    } catch (Exception e) {
                        System.out.println("【ERROR】創建SessionFactory對象出錯,原因是:");
                        e.printStackTrace();
                    }
                }
                
                session = sessionFactory.openSession();
                THREAD_LOCAL.set(session);
            }
            return session;
        }

        public static void closeSession() throws HibernateException {
            Session session = (Session) THREAD_LOCAL.get();
            THREAD_LOCAL.set(null);
            if(session != null){
                session.close();
            }
        }
    }











    posted @ 2013-04-19 21:22 abin 閱讀(1973) | 評論 (0)編輯 收藏

    package com.abin.lee.hack;

    import java.io.BufferedInputStream;

    import org.apache.http.HttpHost;
    import org.apache.http.HttpResponse;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.entity.StringEntity;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.junit.Test;

    public class HttpClientVisitTest {

     private static final String HttpUrl = " private static final String HttpHost = "111.111.111.111";
     @Test
     public void testHttpClientVisit() {
      HttpClient httpClient = new DefaultHttpClient();
      HttpPost httpPost = new HttpPost(HttpUrl);
      httpPost.addHeader("Accept", "*/*");
      httpPost.addHeader("Accept-Language", "zh-cn");
      httpPost.addHeader("Referer", HttpUrl);
      httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
      httpPost.addHeader("Cache-Control", "no-cache");
      httpPost.addHeader("Accept-Encoding", "gzip, deflate");
      httpPost.addHeader("User-Agent",
        "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)");
      httpPost.addHeader("Host", HttpHost);
      httpPost.addHeader("Connection", "Keep-Alive");
    //  HttpHost httpProxy = new HttpHost("222.222.222.222", 1443, "http");
    //  httpClient.getParams().setParameter(AllClientPNames.DEFAULT_PROXY,httpProxy);
      StringBuffer params = new StringBuffer();
      String userName = "abin";
      String passWord= "varyall";
      String userAge= "12345";
      String homeTown= "china beijing";
      params.append("__userName ").append("=").append(userName )
        .append("&").append(passWord").append("=")
        .append(passWord).append("&").append("userAge")
        .append("=").append(userAge).append("&")
        .append("homeTown").append("=")
        .append(homeTown);
      HttpResponse httpResponse = null;
      String result = "";
      try {
       StringEntity reqEntity = new StringEntity(params.toString());
       httpPost.setEntity(reqEntity);
       HttpHost httpTarget = new HttpHost(HttpHost, 80, "http");
       httpResponse = httpClient.execute(httpTarget, httpPost);
       System.out.println("httpResponse=" + httpResponse.getStatusLine());

       BufferedInputStream buffer = new BufferedInputStream(httpResponse
         .getEntity().getContent());
       byte[] bytes = new byte[1024];
       int line = 0;
       StringBuilder builder = new StringBuilder();
       while ((line = buffer.read(bytes)) != -1) {
        builder.append(new String(bytes, 0, line));
       }
       result = new String(builder.toString());
      } catch (Exception e) {
       e.printStackTrace();
      } finally {
       if (httpPost.isAborted()) {
        httpPost.abort();
       }
       httpClient.getConnectionManager().shutdown();
      }
    //   System.out.println("result="+result);

     }

    }


    僅列出標題
    共50頁: First 上一頁 13 14 15 16 17 18 19 20 21 下一頁 Last 
    主站蜘蛛池模板: 亚洲国产精品午夜电影| 久久精品国产亚洲av麻| 亚洲色大成WWW亚洲女子| 成人免费看黄20分钟| 亚洲首页在线观看| 99在线观看免费视频| 亚洲人成在线观看| 日本片免费观看一区二区| 亚洲免费网站在线观看| 91视频国产免费| 亚洲AV无码一区二区大桥未久| 日韩免费观看视频| 性生大片视频免费观看一级| 中文字幕亚洲图片| 久久久免费的精品| 亚洲伊人精品综合在合线| 毛片a级毛片免费播放100| 亚洲av纯肉无码精品动漫| 亚洲XX00视频| 国产日韩一区二区三免费高清| 亚洲国产成人久久综合碰碰动漫3d| 91高清免费国产自产拍2021| 亚洲资源最新版在线观看| 日本a级片免费看| 国产啪精品视频网站免费尤物 | 国内精品久久久久影院免费| 在线视频精品免费| 亚洲国产精品18久久久久久| 无码人妻丰满熟妇区免费| 亚洲国产中文在线二区三区免| 日韩免费视频观看| a级毛片毛片免费观看久潮喷| 亚洲午夜精品在线| 国产日韩成人亚洲丁香婷婷| 亚洲精品乱码久久久久久V| 亚洲无线一二三四区手机| 一级毛片全部免费播放| 亚洲精品第一国产综合亚AV| 亚洲女久久久噜噜噜熟女| 日本免费网址大全在线观看| 91在线精品亚洲一区二区|