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

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

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

    qileilove

    blog已經(jīng)轉(zhuǎn)移至github,大家請(qǐng)?jiān)L問(wèn) http://qaseven.github.io/

    自動(dòng)化單元測(cè)試實(shí)踐之路

      自動(dòng)化單元測(cè)試并不是什么新鮮事物,它應(yīng)該是團(tuán)隊(duì)持之以恒的事情,可能有很多團(tuán)隊(duì)知道如何去做,但是還做得不夠好;還有不少團(tuán)隊(duì)不知道如何去做,甚至有一些舊系統(tǒng)還不敢去重構(gòu),還在堅(jiān)持著Java中的main方法調(diào)用的方式來(lái)執(zhí)行,在漫長(zhǎng)等待構(gòu)建結(jié)果。
      本文主要講基于Java項(xiàng)目如何做自動(dòng)化單元測(cè)試的實(shí)踐。
      1 是否值得
      關(guān)于單元測(cè)試的意義,詳細(xì)參考stackoverflow這篇文章
      http://stackoverflow.com/questions/67299/is-unit-testing-worth-the-effort,
      Martin Fowler在博客(http://martinfowler.com/bliki/TestPyramid.html)中解釋了
      TestPyramid,如下圖所示:
      
      Unit是整個(gè)金字塔的基石(在建筑行業(yè),基石是做建筑物基礎(chǔ)的石頭),如果基石不穩(wěn),Service和UI何談?dòng)袠?gòu)建意義呢?只有基石穩(wěn)如磐石,上層建筑才夠堅(jiān)固。
      本來(lái)想拿瑞士做鐘表的例子來(lái)說(shuō)明下,但同事說(shuō)的汽車?yán)痈谩R惠v汽車由許多配件組成,如果有以下兩種選擇,你會(huì)選擇哪個(gè)呢?
      所有單元配件沒(méi)有測(cè)試過(guò),在4S店,銷售人員告訴你:剛組裝好,已經(jīng)開了一天,能跑起來(lái),你可以試試;
      所有單元配件在生產(chǎn)過(guò)程已經(jīng)經(jīng)過(guò)嚴(yán)格測(cè)試,在4S點(diǎn),銷售人員告訴你,已經(jīng)通過(guò)國(guó)家認(rèn)證,出廠合格,有質(zhì)量保證,你可以試試;
      答案不言而喻了。
      實(shí)施單元測(cè)試,并不代表你的生產(chǎn)效率能提高迅猛,反而有時(shí)候阻礙了瞬間的生產(chǎn)效率(傳統(tǒng)的開發(fā)一個(gè)功能,看似就算完成的動(dòng)作,增加單元測(cè)試看起來(lái)無(wú)法是浪費(fèi)時(shí)間),但是,它最直接的是提升產(chǎn)品質(zhì)量,從而提升市場(chǎng)的形象,間接才會(huì)提升生產(chǎn)效率。
      做產(chǎn)品,到底是要數(shù)量,還是質(zhì)量呢?這個(gè)應(yīng)該留給老板們?nèi)セ卮穑雌髽I(yè)是否需要長(zhǎng)遠(yuǎn)立足。
      2 關(guān)鍵部分
      自動(dòng)化單元測(cè)試有四個(gè)關(guān)鍵組成部分要做到統(tǒng)一,如圖所示:
     
     圖-2-1-關(guān)鍵組成部分
      配置管理:使用版本控制
      版本控制系統(tǒng)(源代碼控制管理系統(tǒng))是保存文件多個(gè)版本的一種機(jī)制。一般來(lái)說(shuō),包括Subversion、Git在內(nèi)的開源工具就可以滿足絕大多數(shù)團(tuán)隊(duì)的需求。所有的版本控制系統(tǒng)都需要解決這樣一個(gè)基礎(chǔ)問(wèn)題: 怎樣讓系統(tǒng)允許用戶共享信息,而不會(huì)讓他們因意外而互相干擾?
      如果沒(méi)有版本控制工具的協(xié)助,在開發(fā)中我們經(jīng)常會(huì)遇到下面的一些問(wèn)題:
      一、 代碼管理混亂。
      二、 解決代碼沖突困難。
      三、 在代碼整合期間引入深層BUG。
      四、 無(wú)法對(duì)代碼的擁有者進(jìn)行權(quán)限控制。
      五、 項(xiàng)目不同版本發(fā)布困難。
      對(duì)所有內(nèi)容都進(jìn)行版本控制
      版本控制不僅僅針對(duì)源代碼,每個(gè)與所開發(fā)的軟件相關(guān)的產(chǎn)物都應(yīng)該被置于版本控制下,應(yīng)當(dāng)包括:源代碼、測(cè)試代碼、數(shù)據(jù)庫(kù)腳本、構(gòu)建和部署腳本、文檔、web容器(tomcat的配置)所用的配置文件等。

    保證頻繁提交可靠代碼到主干
      頻繁提交可靠、有質(zhì)量保證的代碼(編譯通過(guò)是最基本要求),能夠輕松回滾到最近可靠的版本,代碼提交之后能夠觸發(fā)持續(xù)集成構(gòu)建,及時(shí)得到反饋。
      提交有意義的注釋
      強(qiáng)制要求團(tuán)隊(duì)成員使用有意義注釋,甚至可以關(guān)聯(lián)相關(guān)開發(fā)任務(wù)的原因是:當(dāng)構(gòu)建失敗后,你知道是誰(shuí)破壞了構(gòu)建,找到可能的原因及定位缺陷位置。這些附加信息,可以縮短我們修復(fù)缺陷的時(shí)間。示例:團(tuán)隊(duì)使用了svn和redmine,注釋是:
      refs #任務(wù)id 提交說(shuō)明
      每個(gè)任務(wù)下可以看到多次提交記錄:
      
    圖-2-2-相關(guān)修訂版本
      所有的代碼文件編碼格式統(tǒng)一使用UTF-8
      上班前更新代碼,下班前提交代碼
      前一天,團(tuán)隊(duì)其他成員可能提交了許多代碼到svn,開始新的一天工作是,務(wù)必更新到最新版本,及時(shí)發(fā)現(xiàn)問(wèn)題(例如代碼沖突)并解決;
      當(dāng)日事,當(dāng)日畢,下班別把當(dāng)天的編碼成果僅保存在本地,應(yīng)當(dāng)提交到svn,次日?qǐng)F(tuán)隊(duì)更新就可以獲取到最新版本,形成良性循環(huán)。
      構(gòu)建管理:使用Maven構(gòu)建工具
      Maven是基于項(xiàng)目對(duì)象模型(POM),通過(guò)為Java項(xiàng)目的代碼組織結(jié)構(gòu)定義描述信息來(lái)管理項(xiàng)目的構(gòu)建、報(bào)告和文檔的軟件項(xiàng)目管理工具。使用“慣例勝于配置”(convention over configuration)的原則,只要項(xiàng)目按照Maven制定的方式進(jìn)行組織,它就幾乎能用一條命令執(zhí)行所有的構(gòu)建、部署、測(cè)試等任務(wù),卻不用寫很多行的XML(消除Ant文件中大量的樣板文件)。
      或許,使用Ant來(lái)構(gòu)建的團(tuán)隊(duì)要問(wèn),為什么用Maven呢?簡(jiǎn)單來(lái)說(shuō)兩點(diǎn)
      1、對(duì)第三方依賴庫(kù)進(jìn)行統(tǒng)一的版本管理
      說(shuō)實(shí)話,ant處理依賴包之間的沖突問(wèn)題,還是得靠人工解決,這個(gè)對(duì)于研發(fā)來(lái)說(shuō)是消耗時(shí)間的,倒不如把節(jié)省的時(shí)間投入到業(yè)務(wù)中去。另外再也不用每個(gè)項(xiàng)目繁瑣復(fù)制spring.jar了,通過(guò)maven自動(dòng)管理Java庫(kù)和項(xiàng)目間的依賴,打包的時(shí)候會(huì)將所有jar復(fù)制到WEB- INF/lib/目錄下。
      2、統(tǒng)一項(xiàng)目的目錄結(jié)構(gòu)。
     保證所有項(xiàng)目的目錄結(jié)構(gòu)在任何服務(wù)器上都是一樣的,每個(gè)目錄起什么作用都很清楚明了。
      3、統(tǒng)一軟件構(gòu)建階段
      http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
      Maven2把軟件開發(fā)的過(guò)程劃分成了幾個(gè)經(jīng)典階段,比如你先要生成一些java代碼,再把這些代碼復(fù)制到特定位置,然后編譯代碼,復(fù)制需要放到classpath下的資源,再進(jìn)行單元測(cè)試,單元測(cè)試都通過(guò)了才能進(jìn)行打包,發(fā)布。
      測(cè)試框架:JUnit&Mockito
      JUnit
      JUnit是一個(gè)Java語(yǔ)言的單元測(cè)試框架。
      2013年見過(guò)一個(gè)舊項(xiàng)目,測(cè)試代碼還是以main作為入口,為什么要使用JUnit?
      JUnit 的優(yōu)點(diǎn)是整個(gè)測(cè)試過(guò)程無(wú)人值守,開發(fā)無(wú)須在線參與和判斷最終結(jié)果是否正確,可以很容易地一次性運(yùn)行多個(gè)測(cè)試,使得開發(fā)更加關(guān)注測(cè)試邏輯的編寫,而不是增加構(gòu)建維護(hù)時(shí)間。
      團(tuán)隊(duì)示例代碼:
    // 功能代碼
    package com.chinacache.portal.service;
    public class ReportService {
    public boolean validateParams() {
    }
    public String sendReport(Long id) {
    }
    public String sendReport(Long id, Date time) {
    }
    }
    // 單元測(cè)試代碼
    package com.chinacache.portal.service; // 必須與功能代碼使用相同 package
    public class ReportServiceUnitTest { // 測(cè)試類名以 UnitTest (單元測(cè)試) 或 InteTest (集成測(cè)試) 結(jié)尾
    // 測(cè)試方法名以 test 開頭,然后接對(duì)應(yīng)的功能方法名稱
    @Test
    public void testValidateParams() {
    }
    // 如果功能方法存在重載,則再接上參數(shù)類型
    @Test
    public void testSendReportLong() {
    }
    // 如果一個(gè)功能方法對(duì)應(yīng)多個(gè)測(cè)試方法,不同測(cè)試方法可使用簡(jiǎn)潔而又有含義的單詞結(jié)尾,例如 success、fail 等
    @Test
    public void testSendReportLongDateSuccess() {
    }
    // 這樣通過(guò)測(cè)試方法名即可知道:測(cè)的是哪個(gè)功能方法,哪種情況
    @Test
    public void testSendReportLongDateFail() {
    }
    }
      Mockito
      Mockito是一個(gè)針對(duì)Java的mocking框架。使用它可以寫出干凈漂亮的測(cè)試用例和簡(jiǎn)單的API。它與EasyMock和jMock很相似,通過(guò)在執(zhí)行后校驗(yàn)什么已經(jīng)被調(diào)用,消除了對(duì)期望行為(expectations)的需要,改變其他mocking庫(kù)以“記錄-回放”(這會(huì)導(dǎo)致代碼丑陋)的測(cè)試流程,使得自身的語(yǔ)法更像自然語(yǔ)言。
      Mockito示例:
    List mock = mock(List.class);
    when(mock.get(0)).thenReturn("one");
    when(mock.get(1)).thenReturn("two");
    someCodeThatInteractsWithMock();
    verify(mock).clear();
    EasyMock示例:
    List mock = createNiceMock(List.class);
    expect(mock.get(0)).andStubReturn("one");
    expect(mock.get(1)).andStubReturn("two");
    mock.clear();
    replay(mock);
    someCodeThatInteractsWithMock();
    verify(mock);
      官方對(duì)比文章:http://code.google.com/p/mockito/wiki/MockitoVSEasyMock
      反饋平臺(tái):Jenkins&Sonar
      持續(xù)集成平臺(tái):Jenkins
      Jenkins 的前身是 Hudson 是一個(gè)可擴(kuò)展的持續(xù)集成引擎,主要用于:
      持續(xù)、自動(dòng)地構(gòu)建測(cè)試軟件項(xiàng)目
      監(jiān)控一些定時(shí)執(zhí)行的任務(wù)
      Jenkins將作為自動(dòng)化單元測(cè)試持續(xù)集成的平臺(tái),實(shí)現(xiàn)自動(dòng)化構(gòu)建。
    圖-2-3-Jenkins平臺(tái)
      代碼質(zhì)量管理平臺(tái):Sonar
      Sonar (SonarQube)是一個(gè)開源平臺(tái),用于管理源代碼的質(zhì)量。Sonar 不只是一個(gè)質(zhì)量數(shù)據(jù)報(bào)告工具,更是代碼質(zhì)量管理平臺(tái)。支持的語(yǔ)言包括:Java、PHP、C#、C、Cobol、PL/SQL、Flex 等。
      主要特點(diǎn):
      代碼覆蓋:通過(guò)單元測(cè)試,將會(huì)顯示哪行代碼被選中
      改善編碼規(guī)則
      搜尋編碼規(guī)則:按照名字,插件,激活級(jí)別和類別進(jìn)行查詢
      項(xiàng)目搜尋:按照項(xiàng)目的名字進(jìn)行查詢
      對(duì)比數(shù)據(jù):比較同一張表中的任何測(cè)量的趨勢(shì)
      Sonar將作為自動(dòng)化單元測(cè)試反饋報(bào)告統(tǒng)一展現(xiàn)平臺(tái),包括:
      單元測(cè)試覆蓋率、成功率、代碼注釋、代碼復(fù)雜度等度量數(shù)據(jù)的展現(xiàn)。
      
    圖-2-4 Sonar平臺(tái)
      3 原則
      自動(dòng)化測(cè)試金字塔,也稱為自動(dòng)化分層測(cè)試,Unit是整個(gè)金字塔的基石,最重要特點(diǎn)是運(yùn)行速度非常快;第二個(gè)重要特點(diǎn)是UT應(yīng)覆蓋代碼庫(kù)的大部分,能夠確定一旦UT通過(guò)后,應(yīng)用程序就能正常工作。
      Unit:70%,大部分自動(dòng)化實(shí)現(xiàn),用于驗(yàn)證一個(gè)單獨(dú)函數(shù)或獨(dú)立功能模塊的代碼;
      Service:20%,涉及兩個(gè)或兩個(gè)以上,甚至更多模塊之間交互的集成測(cè)試;
      UI:10%,覆蓋三個(gè)或以上的功能模塊,真實(shí)用戶場(chǎng)景和數(shù)據(jù)的驗(yàn)收測(cè)試;
      這里僅僅列舉了每個(gè)層次的百分比,實(shí)際要根據(jù)團(tuán)隊(duì)的方向來(lái)做調(diào)整。
      自動(dòng)化單元測(cè)試原則
      提交代碼、運(yùn)行測(cè)試的重點(diǎn)是什么?快速捕獲那些因修改向系統(tǒng)中引入的最常見錯(cuò)誤,并通知開發(fā)人員,以便他們能快速修復(fù)他們。提交階段提供反饋的價(jià)值在于,對(duì)它的投入可以讓系統(tǒng)高效且更快地工作。
      隔離UI操作
      UI應(yīng)當(dāng)作為更高層次的測(cè)試Level,需要花費(fèi)大量時(shí)間準(zhǔn)備數(shù)據(jù),業(yè)務(wù)邏輯復(fù)雜,過(guò)早進(jìn)入U(xiǎn)I階段,容易分散開發(fā)的單元測(cè)試精力。
      隔離數(shù)據(jù)庫(kù)以及文件讀寫網(wǎng)絡(luò)開銷等操作
      自動(dòng)化測(cè)試中如果需要將結(jié)果寫入數(shù)據(jù)庫(kù),然后再驗(yàn)證改結(jié)果是否被正確寫入,這種驗(yàn)證方法簡(jiǎn)單、容易理解,但是它不是一個(gè)高效的方法。這個(gè)應(yīng)當(dāng)從集成測(cè)試的Level去解決。
      首先:與數(shù)據(jù)庫(kù)的交互,是漫長(zhǎng)的,甚至有可能要投入維護(hù)數(shù)據(jù)庫(kù)的時(shí)間,那將成為快速測(cè)試的一個(gè)障礙,開發(fā)人員不能得到及時(shí)有效的反饋。假設(shè),我需要花費(fèi)一個(gè)小時(shí),才能驗(yàn)證完畢與數(shù)據(jù)庫(kù)交互的結(jié)果,這種等待是多么漫長(zhǎng)呀。
      其次,數(shù)據(jù)管理需要成本,從數(shù)據(jù)的篩選(線上數(shù)據(jù)可能是T級(jí))到測(cè)試環(huán)境的M級(jí)別,如何把篩選合適的大小,這都使得管理成本增加(當(dāng)然在集成測(cè)試中可以使用DBUnit來(lái)解決部分問(wèn)題)。
      最后,如果一定要有讀寫操作才能完成的測(cè)試,也要反思代碼的可測(cè)試性做的如何?是否需要重構(gòu)。
      單元測(cè)試決不要依賴于數(shù)據(jù)庫(kù)以及文件系統(tǒng)、網(wǎng)絡(luò)開銷等一切外部依賴。
      使用Mock替身與Spring容器隔離
      如果在單元測(cè)試中,還需要啟動(dòng)Spring容器進(jìn)行依賴注入、加載依賴的WebService等,這個(gè)過(guò)程是相當(dāng)消耗時(shí)間的。
      可以使用模擬工具集:Mockito、EasyMock、JMock等來(lái)解決,研發(fā)團(tuán)隊(duì)主要是基于Mockito的實(shí)踐。與需要組裝所有的依賴和狀態(tài)相比,使用模擬技術(shù)的測(cè)試運(yùn)行起來(lái)通常是非常快,這樣子開發(fā)人員在提交代碼之后,可以在持續(xù)集成平臺(tái)快速得到反饋。
      設(shè)計(jì)簡(jiǎn)單的測(cè)試
      明確定義方法:
      成功:public void testSendReportLongDateSuccess()
      失敗:public void testSendReportLongDateFail(),可以包括異常
      和單一的斷言,避免在一個(gè)方法內(nèi)使用多個(gè)復(fù)雜斷言,這會(huì)造成代碼結(jié)構(gòu)的復(fù)雜,使得測(cè)試的復(fù)雜性提高。
      定義測(cè)試套件的運(yùn)行時(shí)間
      使用Mock構(gòu)建的單元測(cè)試,每個(gè)方法的構(gòu)建時(shí)間應(yīng)該是毫秒級(jí)別,整個(gè)類是秒級(jí)別,理想的是整體構(gòu)建時(shí)間控制在5分鐘以內(nèi),如果超過(guò)怎么辦呢?
      首先,拆分成多個(gè)套件,在多臺(tái)機(jī)器上并行執(zhí)行這些套件;
      其次,重構(gòu)那些運(yùn)行時(shí)間比較長(zhǎng)且不經(jīng)常失敗的測(cè)試類;
      更多參考推薦閱讀:《Unit Testing Guidelines》
      http://geosoft.no/development/unittesting.html
      4 流程
      
    圖-4-1-典型工作流程
      開發(fā)人員遵循每日構(gòu)建原則,提交功能代碼、測(cè)試代碼(以UnitTest結(jié)尾的測(cè)試類)到Svn;
      Jenkins平臺(tái),根據(jù)配置原則(假設(shè)配置定時(shí)器每6分鐘檢查Svn有代碼更新則構(gòu)建)進(jìn)行:代碼更新、代碼編譯、UnitTest、持續(xù)反饋的流水線工作;
      構(gòu)建結(jié)果發(fā)送到Sonar,并且把失敗的構(gòu)建以郵件方式通知影響代碼的開發(fā)人員;
      開發(fā)人員、測(cè)試人員需要在Sonar平臺(tái)進(jìn)行review;
      5 實(shí)踐
      Jenkins配置重點(diǎn)
      構(gòu)建觸發(fā)器:推薦使用PollSCM
      Poll SCM:定時(shí)檢查源碼變更(根據(jù)SCM軟件的版本號(hào)),如果有更新就執(zhí)行checkout。
      Build periodically:周期進(jìn)行項(xiàng)目構(gòu)建(它不care源碼是否發(fā)生變化)。
      配置時(shí)間:H/6 * * * *
      Build配置
      Goals and options:emma:emma -Dtest=*UnitTest soanr:sonar
      注明:
      emma:emma,Add the "emma:emma" goal to your build to generate Emma reports;
      -Dtest=*UnitTest,參數(shù)配置,運(yùn)行以UnitTest結(jié)尾的測(cè)試類;
      sonar:sonar,來(lái)觸發(fā)靜態(tài)代碼分析。
      需要安裝Emma Plugin(https://wiki.jenkins-ci.org/display/JENKINS/Emma+Plugin)
      構(gòu)建后操作
      增加Aggregate downstream test results,勾選自動(dòng)整合所有的downstream測(cè)試;
      增加Editable Email Notification,在“高級(jí)”選項(xiàng)增加觸發(fā)器“Unstable”,
      勾選“Send To Committers”,Check this checkbox to send the email to anyone who checked in code for the last build。
      注明:Editable Email Notification插件是 https://wiki.jenkins-ci.org/display/JENKINS/Email-ext+plugin
      另外一些Jenkins的單元測(cè)試覆蓋率展現(xiàn)方式,可以查看官網(wǎng)。
      構(gòu)建管理工具(Maven)
      項(xiàng)目統(tǒng)一使用Maven進(jìn)行構(gòu)建管理,在pom.xml中進(jìn)行依賴jar包配置
      持續(xù)集成服務(wù)器上同時(shí)需要安裝Maven,setting.xml除了配置倉(cāng)庫(kù)之外,還需要配置sonar,包括sonar服務(wù)器地址、數(shù)據(jù)庫(kù)連接方式:
    <profile>
    <id>sonar</id>
    <activation>
    <activeByDefault>true</activeByDefault>
    </activation>
    <properties>
    <!-- EXAMPLE FOR MYSQL -->
    <sonar.jdbc.url>
    jdbc:mysql://127.0.0.1:3306/sonar?useUnicode=true&characterEncoding=utf8
    </sonar.jdbc.url>
    <sonar.jdbc.driverClassName>com.mysql.jdbc.Driver</sonar.jdbc.driverClassName>
    <sonar.jdbc.username>sonar</sonar.jdbc.username>
    <sonar.jdbc.password>sonar</sonar.jdbc.password>
    <!-- SERVER ON A REMOTE HOST -->
    <sonar.host.url>http:/127.0.0.1:9000</sonar.host.url>
    </properties>
    </profile>
      Mockito配置重點(diǎn)
      所有單元測(cè)試?yán)^承MockitoTestContext父類
      MockitoTestContext 父類:
    package com.chinacache.portal;
    import java.util.Locale;
    import org.junit.BeforeClass;
    import org.mockito.MockitoAnnotations;
    import org.springframework.mock.web.MockHttpServletRequest;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import com.chinacache.portal.web.util.SessionUtil;
    import com.opensymphony.xwork2.util.LocalizedTextUtil;
    /**
    * Mockito 測(cè)試環(huán)境。繼承該類后,Mockito 的相關(guān)注解 (@Mock, @InjectMocks, ...) 就能生效
    */
    public class MockitoTestContext {
    public MockitoTestContext() {
    MockitoAnnotations.initMocks(this);
    }
    }
    BillingBusinessManager 源碼:
    package com.chinacache.portal.service.billing;
    //引入包忽略...
    /**
    * 計(jì)費(fèi)業(yè)務(wù)相關(guān)的業(yè)務(wù)方法
    */
    @Transactional
    public class BillingBusinessManager {
    private static final Log log = LogFactory.getLog(BillingBusinessManager.class);
    @Autowired
    private UserDAO userDAO;
    @Autowired
    private BillingBusinessDAO billingBusinessDAO;
    @Autowired
    private BillingBusinessSubscriptionDAO billingBusinessSubscriptionDAO;
    @Autowired
    private BillingBusinessSubscriptionDetailDAO billingBusinessSubscriptionDetailDAO;
    @Autowired
    private BillingRegionSubscriptionDAO billingRegionSubscriptionDAO;
    @Autowired
    private BillingRegionDAO billingRegionDAO;
    @Autowired
    private ContractTimeManager contractTimeManager;
    /**
    * 根據(jù)id查詢業(yè)務(wù)信息
    * @return 如果參數(shù)為空或者查詢不到數(shù)據(jù),返回空列表
    * O 中的中、英文業(yè)務(wù)名來(lái)自 BILLING_BUSINESS 表
    */
    public List getBusinessesByIds(List businessIds) {         return billingBusinessDAO.getBusinessbyIds(businessIds); } }
    BillingBusinessManagerUnitTest類:
    //引入包忽略...
    public class BillingBusinessManagerUnitTest extends MockitoTestContext {
    @InjectMocks
    private BillingBusinessManager sv;
    @Mock
    private BillingBusinessDAO billingBusinessDAO;
    @Test
    public void testGetBusinessesByIds() {
    List<BusinessVO> expected = ListUtil.toList(new BusinessVO(1l, "a", "b"));
    //簡(jiǎn)潔的語(yǔ)法如下所示
    when(billingBusinessDAO.getBusinessbyIds(anyListOf(Long.class))).thenReturn(expected);
    List<Long> businessIds = ListUtil.toList(TestConstants.BUSINESS_ID_HTTP_WEB_CACHE);
    List<BusinessVO> actual = sv.getBusinessesByIds(businessIds);
    Assert.assertEquals(expected, actual);
    }
    }
      更多Mockito的使用,可以參考官網(wǎng):http://code.google.com/p/mockito/
      6 總結(jié)
      如何加強(qiáng)開發(fā)過(guò)程中的自測(cè)環(huán)節(jié),一直都是個(gè)頭痛的問(wèn)題,開發(fā)的代碼質(zhì)量究竟如何?模塊之間的質(zhì)量究竟如何?回歸測(cè)試的效率如何?重構(gòu)之后,如何快速驗(yàn)證模塊的有效性?
      這些在沒(méi)有做自動(dòng)化單元測(cè)試之前,都是難以考究的問(wèn)題。唯有通過(guò)數(shù)據(jù)去衡量,橫向?qū)Ρ榷鄠€(gè)版本的構(gòu)建分析結(jié)果,才能夠發(fā)現(xiàn)整個(gè)項(xiàng)目質(zhì)量的趨勢(shì),是提升了,還是下降了,這樣開發(fā)、測(cè)試人員才能夠有信心做出恰當(dāng)?shù)呐袛唷?/div>
      當(dāng)然,單元測(cè)試也不是銀彈,即便項(xiàng)目的覆蓋率達(dá)到100%,也不能表明產(chǎn)品質(zhì)量沒(méi)有任何問(wèn)題,不會(huì)產(chǎn)生任何缺陷。重點(diǎn)在于確保單元測(cè)試環(huán)節(jié)的實(shí)施,可以提前釋放壓力、風(fēng)險(xiǎn)、暴露問(wèn)題等多個(gè)方面,改變以往沒(méi)有單元測(cè)試,所有問(wèn)題都集中到最后爆發(fā)的弊端。
      最后,用一張圖來(lái)做個(gè)對(duì)比:
      
    圖-6-1-使用前后對(duì)比
      增加單元測(cè)試之后:
      開發(fā)效率有望提升5-20%;重構(gòu)、回歸測(cè)試效率提升10%,降低出錯(cuò)的幾率,總體代
      碼質(zhì)量提升;
      在開發(fā)過(guò)程中暴露更多問(wèn)題,將風(fēng)險(xiǎn)和壓力提前釋放,持續(xù)構(gòu)建促使開發(fā)重視代碼質(zhì)量;
      UnitTest質(zhì)量對(duì)于團(tuán)隊(duì)來(lái)說(shuō),是可視化了,交付的是有質(zhì)量的產(chǎn)品,而不是數(shù)量;

    posted @ 2014-06-09 10:02 順其自然EVO 閱讀(246) | 評(píng)論 (0)編輯 收藏

    使用異步Servlet改進(jìn)應(yīng)用性能

    Nikita Salnikov Tarnovski是plumbr的高級(jí)開發(fā)者,也是一位應(yīng)用性能調(diào)優(yōu)的專家,他擁有多年的性能調(diào)優(yōu)經(jīng)驗(yàn)。近日,Tarnovski撰文談到了如何通過(guò)異步Servlet來(lái)改進(jìn)常見的Java Web應(yīng)用的性能問(wèn)題。
      眾所周知,Servlet 3.0標(biāo)準(zhǔn)已經(jīng)發(fā)布了很長(zhǎng)一段時(shí)間,相較于之前的2.5版的標(biāo)準(zhǔn),新標(biāo)準(zhǔn)增加了很多特性,比如說(shuō)以注解形式配置Servlet、web.xml片段、異步處理支持、文件上傳支持等。雖然說(shuō)現(xiàn)在的很多Java Web項(xiàng)目并不會(huì)直接使用Servlet進(jìn)行開發(fā),而是通過(guò)如Spring MVC、Struts2等框架來(lái)實(shí)現(xiàn),不過(guò)這些Java Web框架本質(zhì)上還是基于傳統(tǒng)的JSP與Servlet進(jìn)行設(shè)計(jì)的,因此Servlet依然是最基礎(chǔ)、最重要的標(biāo)準(zhǔn)和組件。在Servlet 3.0標(biāo)準(zhǔn)新增的諸多特性中,異步處理支持是令開發(fā)者最為關(guān)注的一個(gè)特性,本文就將詳細(xì)對(duì)比傳統(tǒng)的Servlet與異步Servlet在開發(fā)上、使用上、以及最終實(shí)現(xiàn)上的差別,分析異步Servlet為何會(huì)提升Java Web應(yīng)用的性能。
      本文主要介紹的是能夠解決現(xiàn)代Web應(yīng)用常見性能問(wèn)題的一種性能優(yōu)化技術(shù)。當(dāng)今的應(yīng)用已經(jīng)不僅僅是被動(dòng)地等待瀏覽器來(lái)發(fā)起請(qǐng)求,而是由應(yīng)用自身發(fā)起通信。典型的示例有聊天應(yīng)用、拍賣系統(tǒng)等等,實(shí)際情況是大多數(shù)時(shí)間與瀏覽器的連接都是空閑的,等待著某個(gè)事件來(lái)觸發(fā)。
      這種類型的應(yīng)用自身存在著一個(gè)問(wèn)題,特別是在高負(fù)載的情況下問(wèn)題會(huì)變得更為嚴(yán)重。典型的癥狀有線程饑餓、影響用戶交互等等。根據(jù)近一段時(shí)間的經(jīng)驗(yàn),我認(rèn)為可以通過(guò)一種相對(duì)比較簡(jiǎn)單的方案來(lái)解決這個(gè)問(wèn)題。在Servlet API 3.0實(shí)現(xiàn)成為主流后,解決方案就變得更加簡(jiǎn)單、標(biāo)準(zhǔn)化且優(yōu)雅了。
      在開始介紹解決方案前,我們應(yīng)該更深入地理解問(wèn)題的細(xì)節(jié)。還有什么比看源代碼更直接的呢,下面就來(lái)看看下面這段代碼:
    @WebServlet(urlPatterns = "/BlockingServlet")
    public class BlockingServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    try {
    long start = System.currentTimeMillis();
    Thread.sleep(2000);
    String name = Thread.currentThread().getName();
    long duration = System.currentTimeMillis() - start;
    response.getWriter().printf("Thread %s completed the task in %d ms.", name, duration);
    } catch (Exception e) {
    throw new RuntimeException(e.getMessage(), e);
    }
    }
      上面這個(gè)Servlet主要完成以下事情:
      請(qǐng)求到達(dá),表示開始監(jiān)控某些事件。
      線程被阻塞,直到事件發(fā)生為止。
      在接收到事件后,編輯響應(yīng)然后將其發(fā)回給客戶端。
      為了簡(jiǎn)化,代碼中將等待部分替換為一個(gè)Thread.sleep()調(diào)用。
      現(xiàn)在,你可能會(huì)覺(jué)得這就是一個(gè)挺不錯(cuò)的Servlet。在很多情況下,你的理解都是正確的,上述代碼并沒(méi)有什么問(wèn)題,不過(guò)當(dāng)應(yīng)用的負(fù)載變大后就不是這么回事了。
      為了模擬負(fù)載,我通過(guò)JMeter創(chuàng)建了一個(gè)簡(jiǎn)單的測(cè)試,我會(huì)啟動(dòng)2,000個(gè)線程,每個(gè)線程運(yùn)行10次,每次都會(huì)向/BlockedServlet這個(gè)地址發(fā)出請(qǐng)求。將這個(gè)Servlet部署在Tomcat 7.0.42中然后運(yùn)行測(cè)試,得到如下結(jié)果:
      平均響應(yīng)時(shí)間:19,324ms
      最快響應(yīng)時(shí)間:2,000ms
      最慢響應(yīng)時(shí)間:21,869ms
      吞吐量:97個(gè)請(qǐng)求/秒
      默認(rèn)的Tomcat配置有200個(gè)工作線程,此外再加上模擬的工作由2,000ms的睡眠時(shí)間來(lái)表示,這就能比較好地解釋最快與最慢的響應(yīng)時(shí)間了,每個(gè)線程都會(huì)睡眠2秒鐘。再加上上下文切換的代價(jià),因此97個(gè)請(qǐng)求/秒的吞吐量基本上是符合我們的預(yù)期的。
      對(duì)于絕大多數(shù)的應(yīng)用來(lái)說(shuō),這個(gè)吞吐量還算是可以接受的。重點(diǎn)來(lái)看看最慢的響應(yīng)時(shí)間與平均響應(yīng)時(shí)間,問(wèn)題就變得有些嚴(yán)重了。經(jīng)過(guò)20秒而不是期待的2秒才能得到響應(yīng)顯然會(huì)讓用戶感到非常不爽。 下面我們來(lái)看看另外一種實(shí)現(xiàn),利用Servlet API 3.0的異步支持:
    @WebServlet(asyncSupported = true, value = "/AsyncServlet")
    public class AsyncServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Work.add(request.startAsync());
    }
    }
    public class Work implements ServletContextListener {
    private static final BlockingQueue queue = new LinkedBlockingQueue();
    private volatile Thread thread;
    public static void add(AsyncContext c) {
    queue.add(c);
    }
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
    thread = new Thread(new Runnable() {
    @Override
    public void run() {
    while (true) {
    try {
    Thread.sleep(2000);
    AsyncContext context;
    while ((context = queue.poll()) != null) {
    try {
    ServletResponse response = context.getResponse();
    response.setContentType("text/plain");
    PrintWriter out = response.getWriter();
    out.printf("Thread %s completed the task", Thread.currentThread().getName());
    out.flush();
    } catch (Exception e) {
    throw new RuntimeException(e.getMessage(), e);
    } finally {
    context.complete();
    }
    }
    } catch (InterruptedException e) {
    return;
    }
    }
    }
    });
    thread.start();
    }
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
    thread.interrupt();
    }
    }
      上面的代碼看起來(lái)有點(diǎn)復(fù)雜,因此在開始分析這個(gè)解決方案的細(xì)節(jié)信息之前,我先來(lái)概述一下這個(gè)方案:速度上提升了75倍,吞吐量提升了20倍。看到這個(gè)結(jié)果,你肯定迫不及待地想知道這個(gè)示例是如何做到的吧。
      這個(gè)Servlet本身是非常簡(jiǎn)單的。需要注意兩點(diǎn),首先是聲明Servlet支持異步方法調(diào)用:
      @WebServlet(asyncSupported = true, value = "/AsyncServlet")
      其次,重要的部分實(shí)際上是隱藏在下面這行代碼調(diào)用中的。
      Work.add(request.startAsync());
      整個(gè)請(qǐng)求處理都被委托給了Work類。請(qǐng)求上下文是通過(guò)AsyncContext實(shí)例來(lái)保存的,它持有容器提供的請(qǐng)求與響應(yīng)對(duì)象。
      現(xiàn)在來(lái)看看第2個(gè),也是更加復(fù)雜的類,Work類實(shí)現(xiàn)了ServletContextListener接口。進(jìn)來(lái)的請(qǐng)求會(huì)在該實(shí)現(xiàn)中排隊(duì)等待通知,通知可能是上面提到的拍賣中的競(jìng)標(biāo)價(jià),或是所有請(qǐng)求都在等待的群組聊天中的下一條消息。
      當(dāng)通知到達(dá)時(shí),我們這里依然是通過(guò)Thread.sleep()讓線程睡眠2,000ms,隊(duì)列中所有被阻塞的任務(wù)都是由一個(gè)工作線程來(lái)處理的,該線程負(fù)責(zé)編輯與發(fā)送響應(yīng)。相對(duì)于阻塞成百上千個(gè)線程以等待外部通知,我們通過(guò)一種更加簡(jiǎn)單且干凈的方式達(dá)成所愿,通過(guò)批處理在單獨(dú)的線程中處理請(qǐng)求。
      還是讓結(jié)果來(lái)說(shuō)話吧,測(cè)試配置與方才的示例一樣,依然使用Tomcat 7.0.24的默認(rèn)配置,測(cè)試結(jié)果如下所示:
      平均響應(yīng)時(shí)間:265ms
      最快響應(yīng)時(shí)間:6ms
      最慢響應(yīng)時(shí)間:2,058ms
      吞吐量:1,965個(gè)請(qǐng)求/秒
      雖然說(shuō)這個(gè)示例很簡(jiǎn)單,不過(guò)對(duì)于實(shí)際項(xiàng)目來(lái)說(shuō)通過(guò)這種方式依然能獲得類似的結(jié)果。
      在將所有的Servlet改寫為異步Servlet前,請(qǐng)容許我多說(shuō)幾句。該解決方案非常適合于某些應(yīng)用場(chǎng)景,比如說(shuō)群組通知與拍賣價(jià)格通知等。不過(guò),對(duì)于等待數(shù)據(jù)庫(kù)查詢完成的請(qǐng)求來(lái)說(shuō),這種方式就沒(méi)有什么必要了。像往常一樣,我必須得重申一下——請(qǐng)通過(guò)實(shí)驗(yàn)進(jìn)行度量,而不是瞎猜。
      對(duì)于那些不適合于這種解決方案的場(chǎng)景來(lái)說(shuō),我還是要說(shuō)一下這種方式的好處。除了在吞吐量與延遲方面帶來(lái)的顯而易見的改進(jìn)外,這種方式還可以在大負(fù)載的情況下優(yōu)雅地避免可能出現(xiàn)的線程饑餓問(wèn)題。
      另一個(gè)重要的方面,這種異步處理請(qǐng)求的方式已經(jīng)是標(biāo)準(zhǔn)化的了。它不依賴于你所使用的Servlet API 3.0,兼容于各種應(yīng)用服務(wù)器,如Tomcat 7、JBoss 6或是Jetty 8等,在這些服務(wù)器上這種方式都可以正常使用。你不必再面對(duì)各種不同的Comet實(shí)現(xiàn)或是依賴于平臺(tái)的解決方案了,比如說(shuō)Weblogic FutureResponseServlet。
      就如本文一開始所提的那樣,現(xiàn)在的Java Web項(xiàng)目很少會(huì)直接使用Servlet API進(jìn)行開發(fā)了,不過(guò)諸多的Web MVC框架都是基于Servlet與JSP標(biāo)準(zhǔn)實(shí)現(xiàn)的,那么在你的日常開發(fā)中,是否使用過(guò)出現(xiàn)多年的Servlet API 3.0,使用了它的哪些特性與API呢?

    posted @ 2014-06-09 09:59 順其自然EVO 閱讀(234) | 評(píng)論 (0)編輯 收藏

    抽離式的測(cè)試分析

     一.粗放式分析
      1.存儲(chǔ)過(guò)程的運(yùn)行是否正常:編譯成功,能運(yùn)行,且有結(jié)果。
      2.存儲(chǔ)過(guò)程的參數(shù)調(diào)用正確:能運(yùn)行成功。
      3.存儲(chǔ)過(guò)程的返回結(jié)果正確:查詢,更新或刪除的結(jié)果是否正確,且返回正常。
      4.存儲(chǔ)過(guò)程的性能是否達(dá)到要求:在可接受的時(shí)間內(nèi)能運(yùn)行完成并成功。
      二.抽離式分析
      1. 外模式:基于業(yè)務(wù)場(chǎng)景的分析
      多種業(yè)務(wù)場(chǎng)景可以映射到一個(gè)程序中的等價(jià)類邏輯上(即內(nèi)模式)。
      所以,挑選比較特殊的業(yè)務(wù)場(chǎng)景,從結(jié)果表和程序邏輯層面去印證。
      2. 概念模式:基于結(jié)果表的分析
      結(jié)果表是對(duì)業(yè)務(wù)場(chǎng)景的概念化和集合化。從結(jié)果表數(shù)據(jù)的類型分析可以延伸出各種業(yè)務(wù)場(chǎng)景(即外模式)。
      所以,從結(jié)果表數(shù)據(jù)的分類可以反推出對(duì)業(yè)務(wù)場(chǎng)景的覆蓋率。
      3. 內(nèi)模式:基于程序?qū)崿F(xiàn)的分析
      程序內(nèi)部實(shí)現(xiàn)的邏輯是各種業(yè)務(wù)場(chǎng)景的抽象,最終形成結(jié)果表(即概念模式)。
      所以,對(duì)程序邏輯分支的分析可以評(píng)估對(duì)業(yè)務(wù)場(chǎng)景的抽象覆蓋程度。

    posted @ 2014-06-09 09:57 順其自然EVO 閱讀(191) | 評(píng)論 (0)編輯 收藏

    JMeter中使用Selenium進(jìn)行測(cè)試

    JMeter是使用非常廣泛的性能測(cè)試工具,而Selenium是ThroughtWorks 公司一個(gè)強(qiáng)大的開源Web 功能測(cè)試工具。Jmeter和Selenium結(jié)合使用,就可以實(shí)現(xiàn)對(duì)網(wǎng)站頁(yè)面的自動(dòng)化性能測(cè)試。
      這里有兩種方式在Jmeter中調(diào)用Selenium測(cè)試用例。可以使用 Jmeter JUnit sampler 或者 BeanShell sampler,后一種方式包含了Selenium client 和 server,因此不需要單獨(dú)啟動(dòng)Server。
      方法一
      準(zhǔn)備工作:
      將文件selenium-server-standalone-2.*.jar拷貝到JMeter類庫(kù)安裝目錄%JMETER_HOME%/lib/下,手動(dòng)啟動(dòng)Selenium server。
      Jmeter JUnit sampler
      將Selenium測(cè)試用例打包成.jar文件,并拷貝到%JMETER_HOME%/lib/junit/目錄下,注意:測(cè)試用例應(yīng)該繼承TestCase or SeleniumTestCase類,從而允許JMeter獲取到該測(cè)試用例,且測(cè)試方法名稱應(yīng)該以test開頭。
      在Jmeter中創(chuàng)建test group和JUnit sampler測(cè)試計(jì)劃,并在JUnit sampler中選擇測(cè)試用例的名稱,選擇測(cè)試方法并運(yùn)行。當(dāng)然還可以根據(jù)需求設(shè)置其他參數(shù)。
      一個(gè)測(cè)試用例例子:
    package com.example.tests;
    import com.thoughtworks.selenium.*;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.After;
    public class selenium extends SeleneseTestCase {
    @Before
    public void setUp() throws Exception {
    . . .
    }
    @Test
    public void testSelenium_test() throws Exception {
    . . .
    }
    @After
    public void tearDown() throws Exception {
    selenium.stop();
    }
    }
      方法二
      準(zhǔn)備工作:
      將文件selenium-server-standalone-2.*.jar拷貝到JMeter類庫(kù)安裝目錄%JMETER_HOME%/lib/下,不需要啟動(dòng)Selenium server.
      Jmeter JUnit sampler
      將Selenium測(cè)試用例打包成.jar文件,并拷貝到%JMETER_HOME%/lib/junit/目錄下,注意:測(cè)試用例應(yīng)該繼承TestCase or SeleniumTestCase類,從而允許JMeter獲取到該測(cè)試用例,且測(cè)試方法名稱應(yīng)該以test開頭。
      在Jmeter中創(chuàng)建test group和JUnit sampler測(cè)試計(jì)劃,并在JUnit sampler中選擇測(cè)試用例的名稱,選擇測(cè)試方法并運(yùn)行。當(dāng)然還可以根據(jù)需求設(shè)置其他參數(shù)。
      一個(gè)測(cè)試用例例子:
    import junit.framework.TestCase;
    import org.openqa.selenium.*;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.After;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.firefox.FirefoxDriver;
    import org.openqa.selenium.firefox.FirefoxProfile;
    public class selenium extends TestCase {
    WebDriver driver;
    @Before
    public void setUp() {
    FirefoxProfile profile = new FirefoxProfile();
    . . .
    driver = new FirefoxDriver(profile);
    }
    @Test
    public void testSelenium_test() throws Exception {
    . . .
    }
    @After
    public void tearDown() {
    driver.quit();
    }
    }

    posted @ 2014-06-05 13:49 順其自然EVO 閱讀(3012) | 評(píng)論 (0)編輯 收藏

    Spring2.5+JUnit4單元測(cè)試

    要求:
      JDK1.5以上(因?yàn)镴unit4是用注解來(lái)實(shí)現(xiàn)的)
      需要的包
      spring-2.5.jar
      junit-4.4.jar
      spring-test.jar
      測(cè)試
    package user;
    import static org.junit.Assert.fail;
    import java.util.Date;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Ignore;
    import org.junit.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.annotation.Rollback;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
    import org.springframework.test.context.transaction.TransactionConfiguration;
    import com.sample.model.user.User;
    import com.sample.service.user.IUserService;
    /** 設(shè)置要加載的配置文件 */
    @ContextConfiguration(
    locations={
    "classpath:spring/persistenceContext.xml",
    "classpath:spring/aopContext.xml",
    "classpath:spring/daoContext.xml",
    "classpath:spring/serviceContext.xml"
    }
    )
    /** 設(shè)置是否回滾數(shù)據(jù) */
    @TransactionConfiguration(defaultRollback = false)
    public class UserTest extends AbstractTransactionalJUnit4SpringContextTests{
    /** 設(shè)置自動(dòng)注入的屬性 */
    @Autowired
    private IUserService userService;
    @Before
    public void setUp() throws Exception {
    }
    @After
    public void tearDown() throws Exception {
    }
    @Test
    @Rollback(false)
    public void testSaveUser() {
    User user=new User();
    user.setUsername("zhoujun");
    user.setCreatetime(new Date());
    userService.saveUser(user);
    }
    @Test
    public void testGetUserById() {
    User user=userService.getUserById("1");
    System.out.println(user.getUsername());
    System.out.println(user.getCreatetime());
    }
    }
     有關(guān)Junit4中注解的說(shuō)明如下:
      @ContextConfiguration 用來(lái)指定加載的Spring配置文件的位置,會(huì)加載默認(rèn)配置文件
      例如下例會(huì)加載:classpath:/com/example/MyTest-context.xml文件
    package com.example;
    @ContextConfiguration
    public class MyTest {
    // class body...
    }
    @ContextConfiguration 注解有以下兩個(gè)常用的屬性:
    locations:可以通過(guò)該屬性手工指定 Spring 配置文件所在的位置,可以指定一個(gè)或多個(gè) Spring 配置文件。如下所示:
    @ContextConfiguration(locations={“xx/yy/beans1.xml”,” xx/yy/beans2.xml”})
    inheritLocations:是否要繼承父測(cè)試用例類中的 Spring 配置文件,默認(rèn)為 true。如下面的例子:
    @ContextConfiguration(locations={"base-context.xml"})
    public class BaseTest {
    // ...
    }
    @ContextConfiguration(locations={"extended-context.xml"})
    public class ExtendedTest extends BaseTest {
    // ...
    }
      如果 inheritLocations 設(shè)置為 false,則 ExtendedTest 僅會(huì)使用 extended-context.xml 配置文件,否則將使用 base-context.xml 和 extended-context.xml 這兩個(gè)配置文件。
      在使用所有注釋前必須使用@RunWith(SpringJUnit4ClassRunner.class),讓測(cè)試運(yùn)行于Spring測(cè)試環(huán)境
      Spring框架在org.springframework.test.annotation 包中提供了常用的Spring特定的注解集,如果你在Java5或以上版本開發(fā),可以在測(cè)試中使用它。
      @IfProfileValue
      提示一下,注解測(cè)試只針對(duì)特定的測(cè)試環(huán)境。 如果配置的ProfileValueSource類返回對(duì)應(yīng)的提供者的名稱值, 這個(gè)測(cè)試就可以啟動(dòng)。這個(gè)注解可以應(yīng)用到一個(gè)類或者單獨(dú)的方法。
      @IfProfileValue(name=”java.vendor”, value=”Sun Microsystems Inc.”)
      public void testProcessWhichRunsOnlyOnSunJvm() {
      // some logic that should run only on Java VMs from Sun Microsystems
      }
      同時(shí)@IfProfileValue可配置一個(gè)值列表 (使用OR 語(yǔ)義) 來(lái)在JUnit環(huán)境中獲得TestNG的測(cè)試組支持。 看下面的例子:
      @IfProfileValue(name=”test-groups”, values={”unit-tests”, “integration-tests”})
      public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
      // some logic that should run only for unit and integration test groups
      }
      @ProfileValueSourceConfiguration
      類級(jí)別注解用來(lái)指定當(dāng)通過(guò)@IfProfileValue注解獲取已配置的profile值時(shí)使用何種ProfileValueSource。 如果@ProfileValueSourceConfiguration沒(méi)有在測(cè)試中聲明,將默認(rèn)使用SystemProfileValueSource。
      @ProfileValueSourceConfiguration(CustomProfileValueSource.class)
      public class CustomProfileValueSourceTests {
      // class body…
      }
      @DirtiesContext
      在測(cè)試方法上出現(xiàn)這個(gè)注解時(shí),表明底層Spring容器在該方法的執(zhí)行中被“污染”,從而必須在方法執(zhí)行結(jié)束后重新創(chuàng)建(無(wú)論該測(cè)試是否通過(guò))。
      @DirtiesContext
      public void testProcessWhichDirtiesAppCtx() {
      // some logic that results in the Spring container being dirtied
      }
      @ExpectedException
      表明被注解方法預(yù)期在執(zhí)行中拋出一個(gè)異常。預(yù)期異常的類型在注解中給定。如果該異常的實(shí)例在測(cè)試方法執(zhí)行中被拋出, 則測(cè)試通過(guò)。同樣的如果該異常實(shí)例沒(méi)有在測(cè)試方法執(zhí)行時(shí)拋出,則測(cè)試失敗。
      @ExpectedException(SomeBusinessException.class)
      public void testProcessRainyDayScenario() {
      // some logic that should result in an Exception being thrown
      }
      @Timed
      表明被注解的測(cè)試方法必須在規(guī)定的時(shí)間區(qū)間內(nèi)執(zhí)行完成(以毫秒記)。如果測(cè)試執(zhí)行時(shí)間超過(guò)了規(guī)定的時(shí)間區(qū)間,測(cè)試就失敗了。
      注意該時(shí)間區(qū)間包括測(cè)試方法本身的執(zhí)行,任何重復(fù)測(cè)試(參見 @Repeat),還有任何測(cè)試fixture的set up或tear down時(shí)間。
      Spring的@Timed注解與JUnit 4的@Test(timeout=...)支持具有不同的語(yǔ)義。 特別地,鑒于JUnit 4處理測(cè)試執(zhí)行超時(shí)(如通過(guò)在一個(gè)單獨(dú)的線程中執(zhí)行測(cè)試方法)的方式, 我們不可能在一個(gè)事務(wù)上下文中的測(cè)試方法上使用JUnit的@Test(timeout=...)配置。因此, 如果你想將一個(gè)測(cè)試方法配置成計(jì)時(shí)且具事務(wù)性的, 你就必須聯(lián)合使用Spring的@Timed及@Transactional注解。 還值得注意的是@Test(timeout=...)只管測(cè)試方法本身執(zhí)行的次數(shù),如果超出的話立刻就會(huì)失敗; 然而,@Timed關(guān)注的是測(cè)試執(zhí)行的總時(shí)間(包括建立和銷毀操作以及重復(fù)),并且不會(huì)令測(cè)試失敗。
      @Timed(millis=1000)
      public void testProcessWithOneSecondTimeout() {
      // some logic that should not take longer than 1 second to execute
      }
      @Repeat
      表明被注解的測(cè)試方法必須重復(fù)執(zhí)行。執(zhí)行的次數(shù)在注解中聲明。
      注意重復(fù)執(zhí)行范圍包括包括測(cè)試方法本身的執(zhí)行,以及任何測(cè)試fixture的set up或tear down。
      @Repeat(10)
      public void testProcessRepeatedly() {
      // …
      }
      @Rollback
      表明被注解方法的事務(wù)在完成后是否需要被回滾。 如果true,事務(wù)將被回滾,否則事務(wù)將被提交。 使用@Rollback接口來(lái)在類級(jí)別覆寫配置的默認(rèn)回滾標(biāo)志。
    @Rollback(false)
    public void testProcessWithoutRollback() {
    // …
    }
    @NotTransactional
    出現(xiàn)該注解表明測(cè)試方法必須不在事務(wù)中執(zhí)行。
    @NotTransactional
    public void testProcessWithoutTransaction() {
    // …
    }
    Spring TestContext Framework還支持下面這些非特定于測(cè)試的注解,并且保持其語(yǔ)義不變。
    @Autowired
    @Qualifier
    @Resource (javax.annotation)如果JSR-250可用
    @PersistenceContext (javax.persistence)如果JPA可用
    @PersistenceUnit (javax.persistence)如果JPA可用
    @Required
    @Transactional
    @TestExecutionListeners
     定義類級(jí)別的元數(shù)據(jù),TestExecutionListeners會(huì)使用TestContextManager進(jìn)行注冊(cè)。 通常,@TestExecutionListeners與@ContextConfiguration會(huì)搭配使用。
    @ContextConfiguration
    @TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class})
    public class CustomTestExecutionListenerTests {
    // class body...
    }
      @TransactionConfiguration
      為配置事務(wù)性測(cè)試定義了類級(jí)別的元數(shù)據(jù)。特別地,如果需要的PlatformTransactionManager不是“transactionManager”的話, 那么可以顯式配置驅(qū)動(dòng)事務(wù)的PlatformTransactionManager的bean名字。此外, 可以將defaultRollback標(biāo)志改為false。通常, @TransactionConfiguration與@ContextConfiguration搭配使用。
    @ContextConfiguration
    @TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
    public class CustomConfiguredTransactionalTests {
    // class body...
    }
    @BeforeTransaction
    表明被注解的public void方法應(yīng)該在測(cè)試方法的事務(wù)開始之前執(zhí)行, 該事務(wù)是通過(guò)@Transactional注解來(lái)配置的。
    @BeforeTransaction
    public void beforeTransaction() {
    // logic to be executed before a transaction is started
    }
    @AfterTransaction
    表明被注解的public void方法應(yīng)該在測(cè)試方法的事務(wù)結(jié)束之后執(zhí)行, 該事務(wù)是通過(guò)@Transactional注解來(lái)配置的。
    @AfterTransaction
    public void afterTransaction() {
    // logic to be executed after a transaction has ended
    }

    posted @ 2014-06-05 13:44 順其自然EVO 閱讀(321) | 評(píng)論 (0)編輯 收藏

    使用Selenium/Ant做Web應(yīng)用遠(yuǎn)程自動(dòng)化測(cè)試

      Client端主要是通過(guò)一個(gè)ant build文件來(lái)啟動(dòng)JUnit的TestCase的,進(jìn)而啟動(dòng)TestCase中的test方法,連接并激活server端進(jìn)行自動(dòng)化測(cè)試。Client端核心測(cè)試單元的代碼如下:
    package com.tail.p2test;
    import junit.framework.Test;
    import junit.framework.TestCase;
    import junit.framework.TestSuite;
    import junit.textui.TestRunner;
    import com.thoughtworks.selenium.DefaultSelenium;
    import com.thoughtworks.selenium.Selenium;
    public class DemoTest extends TestCase {
    private Selenium selenium;
    public void setUp() throws Exception {
    String url = "http://localhost:8080/";
    selenium = new DefaultSelenium("localhost", 4444, "*chrome", url);
    selenium.start();
    }
    protected void tearDown() throws Exception {
    selenium.stop();
    }
    public void testNew() throws Exception {
    selenium.setTimeout("100000");
    selenium.open("/login.action");
    selenium.type("username", "admin");
    selenium.type("password", "123");
    selenium.click("http://input[@value='Log In']");
    selenium.waitForPageToLoad("100000");
    Thread.sleep(10000);
    for (int second = 0;; second++) {
    if (second >= 60) fail("timeout");
    try { if (selenium.isElementPresent("signLabel")) break; } catch (Exception e) {}
    Thread.sleep(1000);
    }
    // omit lines
    ...
    selenium.open("/main.action");
    }
    }
     當(dāng)然,應(yīng)用可以直接在Eclipse中運(yùn)行,但是為了能更加靈活,我們考慮用ant腳本來(lái)控制client的運(yùn)行,這里使用ant腳本的一個(gè)好處就是可以很方便快捷的輸出測(cè)試報(bào)告,在本例中輸出報(bào)告的目的就是那個(gè)report目錄咯。
      ant的Build.xml的腳本詳細(xì)如下:
    <?xml version="1.0"?>
    <project name="portal" default="junit" basedir=".">
    <property name="source.dir" value="src" />
    <property name="build.dir" value="build" />
    <property name="lib.dir" value="lib" />
    <property name="classes.dir" value="${build.dir}/classes" />
    <property name="report.dir" value="report" />
    <!-- ================================================================== -->
    <!-- C L E A N                                                          -->
    <!-- ================================================================== -->
    <target name="clean">
    <delete dir="${classes.dir}" />
    <mkdir dir="${classes.dir}" />
    <delete dir="${report.dir}" />
    <mkdir dir="${report.dir}" />
    </target>
    <!-- ================================================================== -->
    <!-- C O M P I L E                                                      -->
    <!-- ================================================================== -->
    <target name="compile" depends="clean">
    <!-- local project jars -->
    <patternset id="lib.includes.compile">
    <include name="*.jar" />
    </patternset>
    <fileset dir="${lib.dir}" id="lib.compile">
    <patternset refid="lib.includes.compile" />
    </fileset>
    <pathconvert targetos="windows" property="libs.compile" refid="lib.compile" />
    <!-- compile -->
    <javac srcdir="${source.dir}" destdir="${classes.dir}" classpath="${libs.compile}" includes="**/*.java" debug="true">
    </javac>
    </target>
    <!-- ================================================================== -->
    <!-- J U N I T                                                          -->
    <!-- ================================================================== -->
    <target name="junit" depends="compile">
    <junit printsummary="on" fork="true" haltonfailure="false" failureproperty="tests.failed" showoutput="true">
    <classpath>
    <pathelement path="${classes.dir}" />
    <fileset dir="${lib.dir}">
    <include name="**/*.jar" />
    </fileset>
    </classpath>
    <formatter type="xml" />
    <batchtest todir="${report.dir}">
    <fileset dir="${classes.dir}">
    <include name="**/*Test.*" />
    </fileset>
    </batchtest>
    </junit>
    <junitreport todir="${report.dir}">
    <fileset dir="${report.dir}">
    <include name="TEST-*.xml" />
    </fileset>
    <report format="frames" todir="${report.dir}" />
    </junitreport>
    <fail if="tests.failed">
    </fail>
    </target>
    </project>
      以后,你只需要在work目錄下執(zhí)行一個(gè)簡(jiǎn)單的 ant 命令就能輕松運(yùn)行整個(gè)測(cè)試了。

    posted @ 2014-06-05 13:40 順其自然EVO 閱讀(327) | 評(píng)論 (0)編輯 收藏

    Linux靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的分析及實(shí)現(xiàn)

     1.什么是庫(kù)
      在windows平臺(tái)和linux平臺(tái)下都大量存在著庫(kù)。
      本質(zhì)上來(lái)說(shuō)庫(kù)是一種可執(zhí)行代碼的二進(jìn)制形式,可以被操作系統(tǒng)載入內(nèi)存執(zhí)行。
      由于windows和linux的本質(zhì)不同,因此二者庫(kù)的二進(jìn)制是不兼容的。
      本文僅限于介紹linux下的庫(kù)。
      2.庫(kù)的種類
      linux下的庫(kù)有兩種:靜態(tài)庫(kù)和共享庫(kù)(動(dòng)態(tài)庫(kù))。
      二者的不同點(diǎn)在于代碼被載入的時(shí)刻不同。
      靜態(tài)庫(kù)的代碼在編譯過(guò)程中已經(jīng)被載入可執(zhí)行程序,因此體積較大。
      共享庫(kù)的代碼是在可執(zhí)行程序運(yùn)行時(shí)才載入內(nèi)存的,在編譯過(guò)程中僅簡(jiǎn)單的引用,因此代碼體積較小。
      3.庫(kù)存在的意義
      庫(kù)是別人寫好的現(xiàn)有的,成熟的,可以復(fù)用的代碼,你可以使用但要記得遵守許可協(xié)議。
      現(xiàn)實(shí)中每個(gè)程序都要依賴很多基礎(chǔ)的底層庫(kù),不可能每個(gè)人的代碼都從零開始,因此庫(kù)的存在意義非同尋常。
      共享庫(kù)的好處是,不同的應(yīng)用程序如果調(diào)用相同的庫(kù),那么在內(nèi)存里只需要有一份該共享庫(kù)的實(shí)例。
      4.庫(kù)文件是如何產(chǎn)生的
      在linux下,靜態(tài)庫(kù)的后綴是.a,它的產(chǎn)生分兩步:
      Step 1.由源文件編譯生成一堆.o,每個(gè).o里都包含這個(gè)編譯單元的符號(hào)表。
      Step 2.ar命令將很多.o轉(zhuǎn)換成.a,成為靜態(tài)庫(kù)。
      動(dòng)態(tài)庫(kù)的后綴是.so,它由gcc加特定參數(shù)編譯產(chǎn)生。
      例如:
      $ gcc -fPIC -c *.c
      $ gcc -shared -Wl,-soname, libfoo.so.1 -o libfoo.so.1.0 *.
      5.庫(kù)文件是如何命名的,有沒(méi)有什么規(guī)范
      在linux下,庫(kù)文件一般放在/usr/lib 下,
      靜態(tài)庫(kù)的名字一般為libxxxx.a,其中xxxx是該lib的名稱
      動(dòng)態(tài)庫(kù)的名字一般為libxxxx.so.major.minor,xxxx是該lib的名稱,major是主版本號(hào),minor是副版本號(hào)。
      6.如何知道一個(gè)可執(zhí)行程序依賴哪些庫(kù)
      ldd命令可以查看一個(gè)可執(zhí)行程序依賴的共享庫(kù),
      例如# ldd /bin/lnlibc.so.6
      => /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2
      => /lib/ld- linux.so.2 (0×40000000)
      可以看到ln命令依賴于libc庫(kù)和ld-linux庫(kù)
      7.可執(zhí)行程序在執(zhí)行的時(shí)候如何定位共享庫(kù)文件
      當(dāng)系統(tǒng)加載可執(zhí)行代碼時(shí)候,能夠知道其所依賴的庫(kù)的名字,但是還需要知道絕對(duì)路徑
      此時(shí)就需要系統(tǒng)動(dòng)態(tài)載入器(dynamic linker/loader)。
      對(duì)于elf格式的可執(zhí)行程序,是由ld-linux.so*來(lái)完成的,它先后搜索elf文件的 DT_RPATH段—環(huán)境變量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目錄找到庫(kù)文件后將其載入內(nèi)存。第5步:由.o文件創(chuàng)建動(dòng)態(tài)庫(kù)文件
      動(dòng)態(tài)庫(kù)文件名命名規(guī)范和靜態(tài)庫(kù)文件名命名規(guī)范類似,也是在動(dòng)態(tài)庫(kù)名增加前綴lib,但其文件擴(kuò)展名為.so。例如:我們將創(chuàng)建的動(dòng)態(tài)庫(kù)名為myhello,則動(dòng)態(tài)庫(kù)文件名就是libmyhello.so。用gcc來(lái)創(chuàng)建動(dòng)態(tài)庫(kù)。
      在系統(tǒng)提示符下鍵入以下命令得到動(dòng)態(tài)庫(kù)文件libmyhello.so。
      # gcc -shared -fPCI -o libmyhello.so hello.o
      #
      我們照樣使用ls命令看看動(dòng)態(tài)庫(kù)文件是否生成。
      # ls
      hello.c hello.h hello.o libmyhello.so main.c
      #
      第6步:在程序中使用動(dòng)態(tài)庫(kù)
      在程序中使用動(dòng)態(tài)庫(kù)和使用靜態(tài)庫(kù)完全一樣,也是在使用到這些公用函數(shù)的源程序中包含這些公用函數(shù)的原型聲明,然后在用gcc命令生成目標(biāo)文件時(shí)指明動(dòng)態(tài)庫(kù)名進(jìn)行編譯。我們先運(yùn)行g(shù)cc命令生成目標(biāo)文件,再運(yùn)行它看看結(jié)果。
      # gcc -o hello main.c -L. -l myhello
      # ./hello
      ./hello: error while loading shared libraries: libmyhello.so: cannot open shared object
      file: No such file or directory
      #
      哦!出錯(cuò)了。快看看錯(cuò)誤提示,原來(lái)是找不到動(dòng)態(tài)庫(kù)文件libmyhello.so。程序在運(yùn)行時(shí),會(huì)在/usr/lib和/lib等目錄中查找需要的動(dòng)態(tài)庫(kù)文件。若找到,則載入動(dòng)態(tài)庫(kù),否則將提示類似上述錯(cuò)誤而終止程序運(yùn)行。我們將文件libmyhello.so復(fù)制到目錄/usr/lib中,再試試。
      # mv libmyhello.so /usr/lib
      # ./hello
      ./hello: error while loading shared libraries: /usr/lib/libhello.so: cannot restore segment
      prot after reloc: Permission denied
      由于SELinux引起,
      # chcon -t texrel_shlib_t /usr/lib/libhello.so
      # ./hello
      Hello everyone!
      #
      成功了。這也進(jìn)一步說(shuō)明了動(dòng)態(tài)庫(kù)在程序運(yùn)行時(shí)是需要的。
      我們回過(guò)頭看看,發(fā)現(xiàn)使用靜態(tài)庫(kù)和使用動(dòng)態(tài)庫(kù)編譯成目標(biāo)程序使用的gcc命令完全一樣,那當(dāng)靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)同名時(shí),gcc命令會(huì)使用哪個(gè)庫(kù)文件呢?抱著對(duì)問(wèn)題必究到底的心情,來(lái)試試看。
      先刪除 除.c和.h外的 所有文件,恢復(fù)成我們剛剛編輯完舉例程序狀態(tài)。
      # rm -f hello hello.o /usr/lib/libmyhello.so
      # ls
      hello.c hello.h main.c
      #
      在來(lái)創(chuàng)建靜態(tài)庫(kù)文件libmyhello.a和動(dòng)態(tài)庫(kù)文件libmyhello.so。
      # gcc -c hello.c
      # ar cr libmyhello.a hello.o
      # gcc -shared -fPCI -o libmyhello.so hello.o
      # ls
      hello.c hello.h hello.o libmyhello.a libmyhello.so main.c
      #
      通過(guò)上述最后一條ls命令,可以發(fā)現(xiàn)靜態(tài)庫(kù)文件libmyhello.a和動(dòng)態(tài)庫(kù)文件libmyhello.so都已經(jīng)生成,并都在當(dāng)前目錄中。然后,我們運(yùn)行g(shù)cc命令來(lái)使用函數(shù)庫(kù)myhello生成目標(biāo)文件hello,并運(yùn)行程序hello。
      # gcc -o hello main.c -L. -lmyhello
      # ./hello
      ./hello: error while loading shared libraries: libmyhello.so: cannot open shared object
      file: No such file or directory
      #
      從程序hello運(yùn)行的結(jié)果中很容易知道,當(dāng)靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)同名時(shí), gcc命令將優(yōu)先使用動(dòng)態(tài)庫(kù)。

    posted @ 2014-06-05 13:39 順其自然EVO 閱讀(230) | 評(píng)論 (0)編輯 收藏

    DB2分區(qū)數(shù)據(jù)庫(kù)的前滾操作

      DB2分區(qū)數(shù)據(jù)庫(kù)備份映象文件是通過(guò)聯(lián)機(jī)備份產(chǎn)生的,在DB2分區(qū)數(shù)據(jù)庫(kù)恢復(fù)操作結(jié)束后,數(shù)據(jù)庫(kù)將處于前滾暫掛狀態(tài),必須通過(guò)前滾操作前滾歸檔日志,以取消前滾暫掛狀態(tài),使數(shù)據(jù)庫(kù)最終可用。
      對(duì)DB2分區(qū)數(shù)據(jù)庫(kù)的前滾操作:
      由于新數(shù)據(jù)庫(kù) SAMPNEW 的日志路徑下不包含源 SAMPLE 數(shù)據(jù)庫(kù)日志路徑下的歸檔日志文件,所以在前滾操作之前,需要將 SAMPLE 的歸檔日志文件復(fù)制到一個(gè)特定的路徑下,然后在發(fā)出前滾命令時(shí),使用 OVERFLOW 選項(xiàng)來(lái)指定該路徑,以替代 SAMPNEW 的數(shù)據(jù)恢復(fù)日志路徑來(lái)提供前滾操作要使用的歸檔日志文件。
      為查找源 SAMPLE 數(shù)據(jù)庫(kù)歸檔日志存放的路徑,可利用如下命令:
    db2_all "db2 get db cfg for sample" | grep "Path"
    Path to log files = /home/db2inst1/db2inst1/NODE0000/SQL00001/SQLOGDIR/
    Path to log files = /home/db2inst1/db2inst1/NODE0001/SQL00001/SQLOGDIR/
    Path to log files = /home/db2inst1/db2inst1/NODE0002/SQL00001/SQLOGDIR/
    Path to log files = /home/db2inst1/db2inst1/NODE0003/SQL00001/SQLOGDIR/
      這里假設(shè)將各分區(qū)的歸檔日志文件對(duì)應(yīng)復(fù)制到 /sampnew 下,因前滾命令僅可在編目分區(qū)上執(zhí)行,所以在編目分區(qū)上前滾到日硬盤數(shù)據(jù)恢復(fù)志文件尾并結(jié)束前滾狀態(tài)的命令應(yīng)寫為:
    db2 "rollforward db sampnew to end of logs and complete overflow log path
    (/sampnew/NODE0000/SQL00001/SQLOGDIR,
    /sampnew/NODE0001/SQL00001/SQLOGDIR on dbpartitionnum 1,
    /sampnew/NODE0002/SQL00001/SQLOGDIR on dbpartitionnum 2,
    /sampnew/NODE0003/SQL00001/SQLOGDIR on dbpartitionnum 3,
    )"
      注:對(duì)于0號(hào)分區(qū),在 OVERFLOW 選項(xiàng)中不能使用“ON DBPARTITIONNUM 0”的子句,否則會(huì)遇到:
      SQL0104N An unexpected token "on" was found following "<identifier>". Expected tokens may include: ")". SQLSTATE=42601的報(bào)錯(cuò),表明命令語(yǔ)法不正確。

    posted @ 2014-06-05 13:37 順其自然EVO 閱讀(253) | 評(píng)論 (0)編輯 收藏

    Java組合與繼承之間的選擇

     組合和繼承都允許在新的類中放置子對(duì)象,組合是顯示的這樣做,而繼承則是隱式的做。
      組合技術(shù)通常用于想在新類中使用現(xiàn)有類的功能而非他的接口。在新類中嵌入某個(gè)對(duì)象,讓其實(shí)現(xiàn)所需要的功能,但新類的用戶看到的只是新類所定義的接口,而非所嵌入對(duì)象的接口。為了取得這樣效果,需要在新類中嵌入一個(gè)現(xiàn)有類的parivate對(duì)象。
      有時(shí),允許類的用戶直接訪問(wèn)新類中的組合成分是極具意義的;也就是說(shuō),將成員對(duì)象聲明為public。如果成員對(duì)象自身都隱藏了具體實(shí)現(xiàn),那么這種做法是安全的。當(dāng)用戶能夠了解到你正在組裝一組部件,會(huì)使得端口更加易于理解。Car對(duì)象就是一個(gè)很好的例子:

    posted @ 2014-06-05 13:36 順其自然EVO 閱讀(272) | 評(píng)論 (0)編輯 收藏

    JMeter工作基本原理

      JMeter可以作為Web服務(wù)器與瀏覽器之間的代理網(wǎng)關(guān),以便捕獲瀏覽器的請(qǐng)求和Web服務(wù)器的響應(yīng),這樣就很容易地生成性能測(cè)試腳本,
      有了性能測(cè)試腳本,JMeter就可以通過(guò)線程組來(lái)模擬真實(shí)用戶對(duì)Web服務(wù)器的訪問(wèn)壓力,這與LoadRunner
      工作原理基本一致。
      原理圖如下:

    posted @ 2014-06-04 10:58 順其自然EVO 閱讀(1237) | 評(píng)論 (0)編輯 收藏

    僅列出標(biāo)題
    共394頁(yè): First 上一頁(yè) 103 104 105 106 107 108 109 110 111 下一頁(yè) Last 
    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    導(dǎo)航

    統(tǒng)計(jì)

    • 隨筆 - 3936
    • 文章 - 404
    • 評(píng)論 - 179
    • 引用 - 0

    常用鏈接

    留言簿(55)

    隨筆分類

    隨筆檔案

    文章分類

    文章檔案

    搜索

    •  

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    主站蜘蛛池模板: 久久久久亚洲av无码专区蜜芽| 黄色三级三级免费看| 久久免费动漫品精老司机| 亚洲精品在线视频| 狼人大香伊蕉国产WWW亚洲| 性生交片免费无码看人| 亚洲福利电影一区二区?| 久久午夜夜伦鲁鲁片免费无码| 亚洲线精品一区二区三区| 看免费毛片天天看| 国产成人涩涩涩视频在线观看免费| 国产精品亚洲综合久久| 男人的好看免费观看在线视频| 亚洲欧洲日韩国产| 91精品成人免费国产片| 亚洲码一区二区三区| 特级无码毛片免费视频尤物| 亚洲国产精品无码专区影院| 你是我的城池营垒免费观看完整版 | 日本二区免费一片黄2019| 亚洲中文字幕无码爆乳app| 三年片在线观看免费大全| 亚洲乱码一二三四区麻豆| 免费国产成人高清在线观看网站| 亚洲最大免费视频网| 国产91色综合久久免费| 亚洲国产精品线观看不卡| 在线观看H网址免费入口| 亚洲va在线va天堂va手机| 无码日韩精品一区二区免费| 久久夜色精品国产噜噜亚洲a| 拍拍拍又黄又爽无挡视频免费| 亚洲真人无码永久在线观看| 成熟女人特级毛片www免费| 亚洲av永久中文无码精品综合| 狼友av永久网站免费观看| 国产精品亚洲精品日韩动图| 亚洲AV中文无码乱人伦在线视色 | 精品无码国产污污污免费| 亚洲AV综合永久无码精品天堂| 在线日韩av永久免费观看|