最近一阶段,由于项目的需要,粗略的看了看测试用到的几种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