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