最近一階段,由于項(xiàng)目的需要,粗略的看了看測試用到的幾種framework和tools,胡亂寫了些東西,主要針對自己對于這幾種framework的理解,這里只起到拋磚引玉,具體細(xì)節(jié)請查看每個framework自己的官方網(wǎng)站

?。俊皽y試”有這么重要?

       

        沒有進(jìn)入TC前,在自己做過的一些小型項(xiàng)目中(或者根本不能稱其為項(xiàng)目,只不過是自己寫的一些程序)基本沒有做過比較體系的測試,更不用提應(yīng)用一些framework或是一些工具來進(jìn)行測試,那時候的測試往往就是在一定的位置上print出結(jié)果,與預(yù)期的結(jié)果相比較,從而知道程序的對錯。進(jìn)入TC后,發(fā)現(xiàn)每一個開發(fā)團(tuán)隊(duì)都有各自的QA,而且有些團(tuán)隊(duì)QA的人數(shù)并不少于開發(fā)人員,測試的重要性在這里可見一斑。閱讀了一些關(guān)于測試的文章,尤其是X-Programming的一些材料后,才認(rèn)識到測試工作如此重要?!?/SPAN>Program a little, test little, 貌似平淡無奇的一句話,但卻是所有項(xiàng)目尤其是企業(yè)級項(xiàng)目成功的保證。從某種程度上說,QA肩負(fù)著比開發(fā)人員更重的使命。

        鑒于測試有如此重要的地位,而且我有可能(只是有可能而已)參與GTSS的測試項(xiàng)目,所以近一個星期以來一直在對“測試”進(jìn)行研究。粗略的學(xué)習(xí)了一下用于測試的一些framework和工具,其中包括JUnit, JMeter, HttpUnit, JWebUnit, 并下Fund Connect近期完成的Performance Test進(jìn)行了研究。

 

想把學(xué)到的東西給大家看看

 

        我的習(xí)慣是把一個階段學(xué)習(xí)到的東西總結(jié)一下,將其變成文字,這樣才能對學(xué)到的東西加深印象,也在成文過程中發(fā)現(xiàn)不明白不理解的地方。往往我寫出的東西,都是給自己看的,加深個印象也就算了,但是這次為什么想和大家分享呢?并非炫耀自己有多么能干,學(xué)了多少東西,因?yàn)槲疑钪次疫@篇東西的人各個都比我能干,每一個人懂得都比我多。主要原因是:和大家不同,我不是做技術(shù)出身,只是單純對技術(shù)有興趣,想深入了解技術(shù),由于專業(yè)知識的欠缺,肯定在學(xué)習(xí)和應(yīng)用過程中會出現(xiàn)很多偏差,理解上也會有很多不對的地方,希望大家及時指出,我好及時更正。

       好了不多羅嗦了,開始正題?。g迎大家拍轉(zhuǎn))

 

JUnit 單元測試的基礎(chǔ)

 

        我可以大言不慚地說,JUnit本身是一個相當(dāng)簡單的框架,想必各位也對其做過一些研究,所以直接給出一個自己寫的簡單的例子,來說明JUnit的特點(diǎn)。

 

//Money.java:

 

public class Money {

        
private int amount;

        

        
public Money(int i){

               amount 
= i;

        }


        

        
public int amount(){

               
return amount;

        }


        
public Money addMoney(Money mon){

               
return new Money(mon.amount()+this.amount());

               

        }


//The key point here

        
public boolean equals(Object o){

               
if(o instanceof Money)

                       
return ((Money)o).amount() == this.amount();

               
else 

                       
return false;

        }


}
 

 

//TestMoney.java

 

import junit.framework.
*;

 

public class TestMoney extends TestCase{

        Money m11;

        Money m12;

        Money m13;

        
protected void setUp(){

               m11 
= new Money(11);

               m12 
= new Money(12);

               m13 
= new Money(13);

        }


        

        
public void testEquals(){

               Assert.assertEquals(m11,
new Money(11));

               

               Assert.assertEquals(m11,
new Money(11));

        }


        

        
public void testAdd(){

               Money m23 
= new Money(23);

               Assert.assertEquals(m11.addMoney(m12),m23);

               Assert.assertEquals(m11.addMoney(m12),m11);

               

        }


        

        
public static void main(String args[]) {

               junit.textui.TestRunner.run(TestMoney.
class);

        }


        

}


        看到這里你可能會罵我把小孩子都能寫出的東西貼到這里來丟人,其實(shí)就是這么個簡單的例子足以說明JUnit的運(yùn)作原理。class Money重載了方法equals(), 就是為了進(jìn)行Money對象之間的比較。這樣的比較在JUnit中是通過斷言的方式進(jìn)行的,由于基礎(chǔ)類TestCase繼承于Assert類,從而繼承了Assert類提供的所有斷言方法。所以一句話概括,JUnit是通過斷言機(jī)制進(jìn)行底層對象間的比較來判斷功能正確與否的。你可能會抬杠的說:“不僅是對象吧,JUnit也可以比較兩個int或者其他的primitive data啊!”,但在OO的理論中,Java中的primitive data也應(yīng)該是對象(如SmallTalk中的實(shí)現(xiàn)),但Java出于對性能的考慮,對primitive data沒有采取類的實(shí)現(xiàn)方式,但同時也給出了各個primitive data wrapper class。

最初認(rèn)識到JUnit的這樣的工作原理,我有些失望懷疑它能否勝任復(fù)雜的商業(yè)邏輯的測試,看到了Fund Connectperformance test中測試service部分的代碼,我這樣的疑慮被消除了。下面節(jié)選一段代碼說明:

public class AdminServiceTest extends TestCase

{

        
private static Log log = LogFactory.getLog(AdminServiceTest.class);

        
private static ServiceFactory factory;

        
protected static AdminService ds;

        
private static ServiceConfigurator serviceConfig;

        
private Statement stmt;

        
private ResultSet rs;

        
private String sConnStr;

        
private String sqlStr;

        
private Connection conn=null;



    
/**

     * Constructor for AdminServiceImplTest.

     * @param arg0

     
*/


    
public AdminServiceTest(String arg0)

    
{

        super(arg0);

    }
    

             
protected void setUp() throws Exception

            
{

                 
//……

             }


             
protected void teardown() throws Exception

             
{

                 
// …..

             }


 

             
public void testGetFundProcessors()

            
{

        

sqlStr 
= "select distinct Fund_Processor_uid from                               FUND_PROCESSOR_INSTR_XREF ";

               
try {

                       rs 
= stmt.executeQuery(sqlStr);

                       List result 
= (List)ds.getFundProcessors();

                       
int i = 0;

                       
while(rs.next()){

                               i 
++;

                       }


                       assertEquals(i, result.size());

                       

               }
 catch (SQLException e) {

                       e.printStackTrace();

               }
 catch (AdminServiceException e) {

                       e.printStackTrace();

               }
       

               }


               
//……

            }



 

         從這個例子中可以看到,無論是多么復(fù)雜的邏輯(testGetFundProcessors)最終都能轉(zhuǎn)化成底層對象通過斷言的比較(紅色字體部分)?!?/SPAN>Everything is object. , JUnit的工作原理決定了它應(yīng)該是單元測試的基礎(chǔ)。

         另外,我也看了一下JUnit的源碼,代碼并不是很多多,由于應(yīng)用了一些模式,使其結(jié)構(gòu)設(shè)計較好。例如:TestCase類中,在設(shè)計run()方法的繼承問題時,應(yīng)用了Template Method Pattern; 對于多個test方法,要有針對性地生成相應(yīng)的TestCase,應(yīng)用了Adapter Pattern;等等。大家有興趣的,可以對其源碼進(jìn)行研究。

 

強(qiáng)大的測試工具JMeter

        我看過的所有的Apache的項(xiàng)目,都很成功。JMeter也不例外,說其強(qiáng)大,我個人認(rèn)為有以下三個原因:

1、 較為友好的圖形用戶界面,易于測試人員使用,只要明白其中的原理用JMeter作測試是件愉快的事情而且它能夠方便的生成測試腳本。

2、 ThreadGroup概念的引進(jìn),這個概念在JMeter是相當(dāng)重要的,之所以JMeter能夠完成對各種不同服務(wù)器的壓力測試與性能測試,也仰仗著ThreadGroup。在一個ThreadGoup中可以規(guī)定應(yīng)用的線程數(shù)量(Number of Threads每一個線程代表一個用戶),也可以規(guī)定用戶行為的重復(fù)次數(shù)(loop count)。在企業(yè)級應(yīng)用的測試中,模擬多個用戶同時執(zhí)行操作或同時處理數(shù)據(jù)的操作,利用ThreadGroup可以輕松實(shí)現(xiàn)。

3、 JMeter將大量的Test Target作了非常好的封裝,用戶可以直接使用這些封裝好的部件,大大減少了測試的工作量。比如測試WebService用的WebService SOAP Request, 測試網(wǎng)頁的HttpRequest,測試數(shù)據(jù)庫連接的JDBC Request等等。測試工作進(jìn)而簡化成了選擇組建,將各個組建有邏輯的組織在一起的過程。

        對于JMeter的強(qiáng)大以及其應(yīng)用方法,因?yàn)榇蠹叶级?,所以我在這里不多說了。下面談?wù)剛€人認(rèn)為JMeter的不足之處。為了更清楚的說明這個問題,我將結(jié)合Fund Connect項(xiàng)目中,對于Server Performance Test的一些JMeter腳本進(jìn)行闡述。其中的一個Requirement如下:

 

Investors submit trades via GUI

 

Description: 15 investors log into FundConnect via GUI. Each investor submits 20 orders via a file upload.

Precondition: 20 investor institutions and 20 investors (one investor per investor institution) are set up in FundConnect.

Step: A JMeter script will execute the following steps: Log In à Start in Investor Home à Upload Multiple Orders à Select upload file à Submit file à Return to Investor Home.

Note: Each investor should upload a different file. The script should record the average time to execute the entire loop (Investor Home to Investor Home).

 

        想必大家對這個需求再清楚不過了(畢竟剛完成測試工作)。用JMeter測試中有一個關(guān)鍵的組件(其余省略不說了):

HttpRequest

Name/fundconnect/FCUploadOrdersServlet

 

Send Parameters With the Request

Name                        value

thisURL                    /inv/upload_orders.jsp

submitURL              /inv/upload_orders_summary.jsp

cancelURL              /adm/index.jsp

 

Send a file with request

File name: D:\datatest\datatest\20order_upload\testdata\uploadorders_20_acct1.csv

Parameter Name: uploadfile

MIME Type: application/octet-stream

 

     這個Request是向FCUploadOrdersServlet發(fā)出,其間傳遞了三個參數(shù)和一個文件,完成上傳order文件的工作。這個需求到此也就結(jié)束了,但大家有沒有想過,如果把需求改成: 15 investors log into FundConnect via GUI. Each investor submits 20 orders via web page. 即如果要求這15個投資者通過web(非上傳文件方式)遞交20不同的order,模擬這樣的測試該如何進(jìn)行呢?JMeter針對這樣的Web頁面操作的測試,實(shí)現(xiàn)起來比較復(fù)雜。(如果誰認(rèn)為不對,可以聯(lián)系我,把你的方法告訴我)。我個人認(rèn)為做Web Application頁面上的功能測試,使用下面談到的兩個框架,實(shí)現(xiàn)起來比較簡單。

 

Web Application自動化測試FrameWorksHttpUnit    JWebUnit

 

HttpUnit本身并沒有測試功能, 說白了, 它不過包含了一些類庫, 可以用來模擬出一個瀏覽器(WebConversation)并可以模擬用戶在網(wǎng)頁上的多種行為。HttpUnit沒有測試的功能,所以它要結(jié)合JUnit來完成Web測試的工作,例如下面是一段簡單的代碼:

import junit.framework.TestCase;

import com.meterware.httpunit.WebResponse;

import com.meterware.httpunit.WebConversation;

import com.meterware.httpunit.WebForm;

import com.meterware.httpunit.WebRequest;

 

public class SearchExample extends TestCase {

 

   
public void testSearch() throws Exception {

      
//模擬瀏覽器

      WebConversation wc 
= new WebConversation();

      
//對google主頁發(fā)出request,并得到response

      WebResponse resp 
= wc.getResponse( "http://www.google.com");

      
//從Response中抽取出第一個table

      WebForm form 
= resp.getForms()[0];

      
//在搜索的text field called q中填寫”HttpUnit”

      form.setParameter(
"q""HttpUnit");

      
//點(diǎn)擊提交按鈕

      WebRequest req 
= form.getRequest("btnG");

      resp 
= wc.getResponse(req);

      
//通過反饋回來的response來判斷叫做HttpUnit的link是否存在

      assertNotNull(resp.getLinkWith(
"HttpUnit"));

      
//模擬點(diǎn)擊連接的功能

      resp 
= resp.getLinkWith("HttpUnit").click();

      
//通過title來判斷返回的reponse的title是否為HttpUnit

      assertEquals(resp.getTitle(), 
"HttpUnit");

      assertNotNull(resp.getLinkWith(
"User's Manual"));

   }


}



你可以發(fā)現(xiàn)上面的一個例子, 已經(jīng)完成了對于在google中查找HttpUnit關(guān)鍵字的測試。

在此基礎(chǔ)上,JWebUnit更近一步,它實(shí)際上是建立在HttpUnitJUnit框架之上,將二者功能結(jié)合、重構(gòu)后的產(chǎn)物。同時,JWebUnit提供了更加易用的API來模擬用戶對web界面的操作,同樣是上面的代碼,JWebUnit的實(shí)現(xiàn)如下:

import net.sourceforge.jwebunit.WebTestCase;

 

public class JWebUnitSearchExample extends WebTestCase {

 

   
public JWebUnitSearchExample(String name) {

      super(name);

   }


 

   
public void setUp() {

      getTestContext().setBaseUrl(
"http://www.google.com");

   }


 

   
public void testSearch() {

      beginAt(
"/");

      setFormElement(
"q""httpunit");

      submit(
"btnG");

      clickLinkWithText(
"HttpUnit");

      assertTitleEquals(
"HttpUnit");

      assertLinkPresentWithText(
"User's Manual");

   }


}




      我相信不用加任何注釋, 大家也可以輕松的理解每一步操作。接下來我應(yīng)用了JWebUnit測試了Fund Connect web頁面上的一些功能, 下面列出了以Admin身份登陸, 對增加Fund這樣一個功能的測試:

 

import net.sourceforge.jwebunit.WebTestCase;

import org.xml.sax.SAXException;

import com.meterware.httpunit.HttpUnitOptions;

import com.meterware.httpunit.WebConversation;

import com.meterware.httpunit.WebResponse;

 

public class AdminTest extends WebTestCase{

    
public static void main(String [] args){

               junit.swingui.TestRunner.run(AdminTest.
class);

        }


        
public void setUp(){

               
//get rid of Java Script check

               HttpUnitOptions.setExceptionsThrownOnScriptError(
false);

               
//set the base url for this test

               getTestContext().setBaseUrl(
"http://tcsunfire04.zdus.com:9220");

               
//set username and password to get through SiteMinder         authentication

               getTestContext().setAuthorization(
"bos.ssb.dwf""123");

               
//set the cookie required

                getTestContext().addCookie(
"SMCHALLENGE""YES");

 

        }


               

        
public void testSiteMinder(){

               
//test wether test can get through SiteMinder or not

               beginAt(
"/fundconnect/adm");

               assertTitleEquals(
"Global Link Fund Connect");

        }


        
/*

         *Test for adding a new Fund

         *Fund long name: star's Fund

         *Fund short name: starFund

         *Fund Provider: Starhero

         
*/


        

        
public void testAddFund(){

               beginAt(
"/fundconnect/adm/maintfunds_funddet.jsp?add=new");

               
//Fill in the add fund form

               setFormElement(
"fundInstrumentLongName","star's fund");

               setFormElement(
"fundInstrumentName","starFund");

               setFormElement(
"fundInstrumentCode","123567");

               selectOption(
"fundCodeType","ISO");

               selectOption(
"timeZone","(GMT+08:00) Asia/Shanghai");

               selectOption(
"fundProviderIndex","Starhero");

               selectOption(
"settlementCurrency","USD -- US Dollar");

               selectOption(
"partialShares","No");

               setFormElement(
"contactName","Brooks");

               setFormElement(
"contactPhoneNumber","13989472700");

               selectOption(
"investmentType","Short Term");

               selectOption(
"assetClass","EQUITY");

               selectOption(
"industry","DEVELOPED");

               selectOption(
"countryRegion","UNITED STATES");

               selectOption(
"benchmark","AUD LIBID");

               selectOption(
"domicileCountry","United States");

               setFormElement(
"defaultPrice","50");

               

               selectOption(
"fundInstrumentCountries","United States");

               selectOption(
"institutionSelect","lon.ssb");

                       

               setFormElement(
"cutoff","22:00");

               selectOption(
"cutoffType","Hard Cutoff");

               
//submit the form

               submit(
"sbmtSubmit");

                
//According to the fund's long name and fund's short name to assert

                
//that fund is added

                assertTextPresent(
"star's fund");

                assertTextPresent(
"starFund");

               

        }
    

}


 

        由此看出, JWebUnit可以完成Web頁面上復(fù)雜應(yīng)用的測試??梢栽谝院蟮捻?xiàng)目中逐漸使用。

 

 寫在后面     

 

        首先得感謝你,能夠耐得住性子看到這里,浪費(fèi)了你寶貴的時間深表歉意。以上就是我對近期對于測試工具及FrameWork研究的小結(jié),有不對或不妥之處還請指正。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                                                                                                                                   小川

          7/12/2005