最近一階段,由于項目的需要,粗略的看了看測試用到的幾種framework和tools,胡亂寫了些東西,主要針對自己對于這幾種framework的理解,這里只起到拋磚引玉,具體細節請查看每個framework自己的官方網站
啊?“測試”有這么重要?
沒有進入TC前,在自己做過的一些小型項目中(或者根本不能稱其為項目,只不過是自己寫的一些程序)基本沒有做過比較體系的測試,更不用提應用一些framework或是一些工具來進行測試,那時候的測試往往就是在一定的位置上print出結果,與預期的結果相比較,從而知道程序的對錯。進入TC后,發現每一個開發團隊都有各自的QA,而且有些團隊QA的人數并不少于開發人員,測試的重要性在這里可見一斑。閱讀了一些關于測試的文章,尤其是X-Programming的一些材料后,才認識到測試工作如此重要。“Program a little, test little”, 貌似平淡無奇的一句話,但卻是所有項目尤其是企業級項目成功的保證。從某種程度上說,QA肩負著比開發人員更重的使命。
鑒于測試有如此重要的地位,而且我有可能(只是有可能而已)參與GTSS的測試項目,所以近一個星期以來一直在對“測試”進行研究。粗略的學習了一下用于測試的一些framework和工具,其中包括JUnit, JMeter, HttpUnit, JWebUnit, 并下Fund Connect近期完成的Performance Test進行了研究。
想把學到的東西給大家看看
我的習慣是把一個階段學習到的東西總結一下,將其變成文字,這樣才能對學到的東西加深印象,也在成文過程中發現不明白不理解的地方。往往我寫出的東西,都是給自己看的,加深個印象也就算了,但是這次為什么想和大家分享呢?并非炫耀自己有多么能干,學了多少東西,因為我深知看我這篇東西的人各個都比我能干,每一個人懂得都比我多。主要原因是:和大家不同,我不是做技術出身,只是單純對技術有興趣,想深入了解技術,由于專業知識的欠缺,肯定在學習和應用過程中會出現很多偏差,理解上也會有很多不對的地方,希望大家及時指出,我好及時更正。
好了不多羅嗦了,開始正題!(歡迎大家拍轉)
JUnit 單元測試的基礎
我可以大言不慚地說,JUnit本身是一個相當簡單的框架,想必各位也對其做過一些研究,所以直接給出一個自己寫的簡單的例子,來說明JUnit的特點。
//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);

}


}


看到這里你可能會罵我把小孩子都能寫出的東西貼到這里來丟人,其實就是這么個簡單的例子足以說明JUnit的運作原理。class Money重載了方法equals(), 就是為了進行Money對象之間的比較。這樣的比較在JUnit中是通過斷言的方式進行的,由于基礎類TestCase繼承于Assert類,從而繼承了Assert類提供的所有斷言方法。所以一句話概括,JUnit是通過斷言機制進行底層對象間的比較來判斷功能正確與否的。你可能會抬杠的說:“不僅是對象吧,JUnit也可以比較兩個int或者其他的primitive data啊!”,但在OO的理論中,Java中的primitive data也應該是對象(如SmallTalk中的實現),但Java出于對性能的考慮,對primitive data沒有采取類的實現方式,但同時也給出了各個primitive data 的wrapper class。
最初認識到JUnit的這樣的工作原理,我有些失望懷疑它能否勝任復雜的商業邏輯的測試,看到了Fund Connect的performance test中測試service部分的代碼,我這樣的疑慮被消除了。下面節選一段代碼說明:
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();

}

}

//……

}


從這個例子中可以看到,無論是多么復雜的邏輯(testGetFundProcessors)最終都能轉化成底層對象通過斷言的比較(紅色字體部分)。“Everything is object. ”, JUnit的工作原理決定了它應該是單元測試的基礎。
另外,我也看了一下JUnit的源碼,代碼并不是很多多,由于應用了一些模式,使其結構設計較好。例如:TestCase類中,在設計run()方法的繼承問題時,應用了Template Method Pattern; 對于多個test方法,要有針對性地生成相應的TestCase,應用了Adapter Pattern;等等。大家有興趣的,可以對其源碼進行研究。
強大的測試工具JMeter
我看過的所有的Apache的項目,都很成功。JMeter也不例外,說其強大,我個人認為有以下三個原因:
1、 較為友好的圖形用戶界面,易于測試人員使用,只要明白其中的原理用JMeter作測試是件愉快的事情而且它能夠方便的生成測試腳本。
2、 ThreadGroup概念的引進,這個概念在JMeter是相當重要的,之所以JMeter能夠完成對各種不同服務器的壓力測試與性能測試,也仰仗著ThreadGroup。在一個ThreadGoup中可以規定應用的線程數量(Number of Threads每一個線程代表一個用戶),也可以規定用戶行為的重復次數(loop count)。在企業級應用的測試中,模擬多個用戶同時執行操作或同時處理數據的操作,利用ThreadGroup可以輕松實現。
3、 JMeter將大量的Test Target作了非常好的封裝,用戶可以直接使用這些封裝好的部件,大大減少了測試的工作量。比如測試WebService用的WebService SOAP Request, 測試網頁的HttpRequest,測試數據庫連接的JDBC Request等等。測試工作進而簡化成了選擇組建,將各個組建有邏輯的組織在一起的過程。
對于JMeter的強大以及其應用方法,因為大家都懂,所以我在這里不多說了。下面談談個人認為JMeter的不足之處。為了更清楚的說明這個問題,我將結合Fund Connect項目中,對于Server Performance Test的一些JMeter腳本進行闡述。其中的一個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測試中有一個關鍵的組件(其余省略不說了):
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發出,其間傳遞了三個參數和一個文件,完成上傳order文件的工作。這個需求到此也就結束了,但大家有沒有想過,如果把需求改成: 15 investors log into FundConnect via GUI. Each investor submits 20 orders via web page. 即如果要求這15個投資者通過web(非上傳文件方式)遞交20不同的order,模擬這樣的測試該如何進行呢?JMeter針對這樣的Web頁面操作的測試,實現起來比較復雜。(如果誰認為不對,可以聯系我,把你的方法告訴我)。我個人認為做Web Application頁面上的功能測試,使用下面談到的兩個框架,實現起來比較簡單。
Web Application自動化測試FrameWorks:HttpUnit JWebUnit
HttpUnit本身并沒有測試功能, 說白了, 它不過包含了一些類庫, 可以用來模擬出一個瀏覽器(WebConversation類)并可以模擬用戶在網頁上的多種行為。HttpUnit沒有測試的功能,所以它要結合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主頁發出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");

//點擊提交按鈕

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

resp = wc.getResponse(req);

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

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

//模擬點擊連接的功能

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

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

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

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

}

}


你可以發現上面的一個例子, 已經完成了對于在google中查找HttpUnit關鍵字的測試。
在此基礎上,JWebUnit更近一步,它實際上是建立在HttpUnit和JUnit框架之上,將二者功能結合、重構后的產物。同時,JWebUnit提供了更加易用的API來模擬用戶對web界面的操作,同樣是上面的代碼,JWebUnit的實現如下:
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");

}

}


我相信不用加任何注釋, 大家也可以輕松的理解每一步操作。接下來我應用了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頁面上復雜應用的測試。可以在以后的項目中逐漸使用。
寫在后面
首先得感謝你,能夠耐得住性子看到這里,浪費了你寶貴的時間深表歉意。以上就是我對近期對于測試工具及FrameWork研究的小結,有不對或不妥之處還請指正。
小川
7/12/2005