最近一阶段,由于项目的需要,粗略的看了看测试用到的几种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 Connectperformance 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自动化测试FrameWorksHttpUnit    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更近一步,它实际上是建立在HttpUnitJUnit框架之上,将二者功能结合、重构后的产物。同时,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