qileilove

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

          自動化單元測試實(shí)踐之路

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

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

          posted on 2014-06-09 10:02 順其自然EVO 閱讀(250) 評論(0)  編輯  收藏 所屬分類: 測試學(xué)習(xí)專欄selenium and watir webdrivers 自動化測試學(xué)習(xí)

          <2014年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          導(dǎo)航

          統(tǒng)計(jì)

          • 隨筆 - 3936
          • 文章 - 404
          • 評論 - 179
          • 引用 - 0

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 巴东县| 公安县| 内江市| 北流市| 雷波县| 封丘县| 静安区| 澳门| 积石山| 大兴区| 宜宾市| 盐池县| 洛宁县| 西林县| 江西省| 澳门| 卢氏县| 平安县| 涿州市| 罗源县| 胶南市| 辽阳市| 平定县| 赞皇县| 沙湾县| 思南县| 沙河市| 安多县| 翁源县| 嘉荫县| 富源县| 河源市| 同江市| 遂川县| 英吉沙县| 资阳市| 乌兰察布市| 灵武市| 连江县| 武城县| 大渡口区|