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")); } }
在此基础上,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"); } }
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.: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页面上复杂应用的测试。可以在以后的项目中逐渐使用。 |
|