软件测试与调试技术研究 
0124080 张明全 
       本文针对java语言编程中深受欢迎的极限编程(eXtreme Programming,简称XP)方法展开讨论,并结合项目对其中的软件测试与调试应用深入分析。极限编程是一种以编码为核心任务的轻量级的开发方法,以代码为中心的活动将存在于软件开发生命周期中的每个阶段中。极限编程是一种极为严格的开发方法,其重点在于代码评审,频繁测试,客户参与,快速反馈,持续重构,精练框架,持续性整合以便在开发过程的早期发现问题,时刻进行设计与再设计,还有持续计划。极限编程的四大价值与五个原则不得不提,四大价值:沟通、简单、反馈、勇气。他们是吸引人们的美酒。五个原则:提供快速反馈、简单假设、制造增量式的变化、包容变化、质保工作。这些则是获得美酒的原则。 
       本文使用JUnit、Cactus、HTTPUnit、JMeter、JUnitPer这些测试工具,并结合Ant来详细讲解极限编程中全方位的测试过程。从名字就可以看出JUnit用于单元测试。Cactus用于容器服务测试。HTTPUnit用于功能测试。JMeter用于应用程序性能测试。JUnitPerf用于进行负载测试。 
  
一、论述 
       或许你还没有在大学校园的项目中写过真正意义上的测试,测试是每一个项目必不可少的步骤,如果你不做测试,你怎么确定某个功能能够正常运行?如果你不做测试,你怎么知道代码在改变后仍然能够运行? 
先写代码还是先写测试,也许你还没有考虑这个问题。在极限编程中,先写测试是推荐的策略。如果你先写好了测试,那么写代码的工作将会是轻易的,因为你已经对自己该做什么,怎么做一清二楚了。下面我们将论述测试工具的特点。 
       JUnit的单元测试 
为了证明程序代码能工作,有些人使用System.out.println()来监视他们的程序代码。这种方法有三个问题:卷动盲目性、主观性及缺乏自动化。这种方法可能此刻有效,然而随着时间的推移,有可能失效,甚至过一段时间后,你也忘了他们的意义,成为程序中无用的部分。 
JUnit的格言是:Keep the bar green to keep the code clean,就如下图所示:当 
 
图中的条栏绿色时,表示没有错误,测试通过,代码无误。 
用户的测试类必须继承JUnit的TestCase或TestSuite,有几个方法继承于父类,那就是setUp()、tearDown(),在setUp()中初始化变量,在tearDown()中释放测试过程中使用的资源。像C++语言中的Assert一样,JUnit主要利用Assert为前缀的方法测试是否成功。其中有assertEquals()、assertFalse()、assertNotNull()、assertNotSame()、assertNull()、assertSame()、assertTrue()。 
       Cactus的容器服务测试 
       Cactus的框架工具用来测试J2EE代码,我们已经做过了单元测试,但是没有任何代码会存在于真空中,仅仅单元测试是不够的。我们已经有了用于隔离测试的模拟对象(Mock Object)和整合测试的工具,但是J2EE对于容器服务是如此严重的依赖,没有一个与部署容器交互的烟雾测试(smoke test)将会使项目背负重大的风险。 
       Cactus提供了Servlet、JSP自定义标记和Servlet Filters(过滤器)的容器内测试,支持对诸如HttpServletRequest、PageContext和FilterChain对象的访问。同时Cactus是JUnit的扩展,继承了JUnit测试类。 
       HTTPUnit的功能测试 
       HTTPUnit的功能测试,或者说是黑盒测试,并不对程序代码的片断做测试,而是在外部询问Web服务器并检查接收的响应。测试整个Web应用程序是一件令人畏惧的任务,存在很多不同的用户行为组合,而尝试去重复所有这些用户行为是困难的事,而HTTPUnit提供的自动化测试无疑将会减小程序员的工作量。 
       HTTPUnit可以看成是两个功能团: 
l        一个维持状态、提供发送请求和接收响应功能的Web客户端 
l        简化验证响应内容的各种方法 
       从本质上来说,HTTPUnit是模拟Web浏览器,并且HTTPUnit API可以模拟浏览器的许多行为,包括: 
l        表单提交  
l        JavaScript  
l        HTTP 认证  
l        Cookie 
       JMeter的性能测试 
       在项目的设计阶段,我们会得到一系列重要的性能标准要求,诸如系统运行的硬件平台、所支持的目标并发用户数量等。我们不能在项目快结束时才开始进行性能测试工作,而应该一开始就逐步为系统建立跟踪手段,以确定系统的瓶颈。 
       JMeter能够加载测试以及为HTTP、FTP和带有JAVA数据库连接支持的RDBMS做性能测试。JMeter可以用来测试不同负载下的系统性能,如大量的更新操作、大量的浏览操作、大量的事务处理或同时在不同的负载组合下工作,你可以从他的图表和齿线得到可视化的系统性能反馈。 
       JUnitPer的负载测试 
       在任何项目的计划编制阶段,你必须给出项目完成后的系统性能标准。通过使用JUnitPer对已存在的JUnit测试用例进行装饰,你便能够确定目前系统达到了性能标准。 
       JUnitPer中有两个主要的测试类型:定时测试和负载测试,分别定义在TimedTest类和LoadTest类中,后面将会用到他们。 
  
二、实践中的应用 
使用JUnit进行单元测试 
如论述中所说,用户的测试类必须继承自TestCase或者TestSuite类,其中TestCase用于单个测试,TestSuite用于多个测试(类似于Vector结构) 
 
在Eclipse中使用JUnit测试 
 
所使用文件来自于JUnit测试用例。下面是摘要: 
package junit.samples; 
import junit.framework.*; 
/** 
 * TestSuite that runs all the sample tests 
 * 
 */ 
public class AllTests { 
       public static void main (String[] args) { 
              junit.textui.TestRunner.run (suite()); 
       } 
       public static Test suite ( ) { 
              TestSuite suite= new TestSuite("All JUnit Tests"); 
              suite.addTest(VectorTest.suite()); 
              suite.addTest(new TestSuite(junit.samples.money.MoneyTest.class)); 
              suite.addTest(junit.tests.AllTests.suite()); 
           return suite; 
       } 
} 
  
使用Cactus进行容器服务测试 
用户需要继承ServletTestCase,对被测试类进行测试。如下所示: 
被测试类 SampleServlet.java import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest;   public class SampleServlet extends HttpServlet {    public void saveToSession(HttpServletRequest request)     {        String testparam = request.getParameter("testparam");        request.getSession().setAttribute("testAttribute", testparam);    } } 测试类 TestSampleServlet.java   import junit.framework.Test; import junit.framework.TestSuite; import org.apache.cactus.ServletTestCase; import org.apache.cactus.WebRequest;   public class TestSampleServlet extends ServletTestCase {    public TestSampleServlet(String theName)     {        super(theName);     }       public static Test suite()     {        return new TestSuite(TestSampleServlet.class);     }       public void beginSaveToSessionOK(WebRequest webRequest)     {        webRequest.addParameter("testparam", "it works!");    }       public void testSaveToSessionOK()     {        SampleServlet servlet = new SampleServlet();         servlet.saveToSession(request);         assertEquals("it works!", session.getAttribute("testAttribute"));    } } 测试的结果可以用XML格式显示,也可以用网页形式显示。如下图: XML格式 
 
 HTML格式 
 
   使用HTTPUnit进行功能测试   对每个Web程序都需要注意的登陆验证进行测试 测试目标 验证用户登陆是否成功 测试过程 1、输入登陆地址的页面地址,验证该页面是否可被正常访问。 2、验证被访问的页面是否是登陆页面。 3、输入非法用户名、密码,验证登陆失败。 4、输入合法用户名、密码,验证登陆成功。 首先写一个测试接口,起名为LoginTestInfo: publicinterface LoginTestInfo{public void testValidPage() throws Exception; public void testIsLoginPage() throws Exception; public void testBadLogin() throws Exception; public void testGoodLogin() throws Exception; } 实现一个Junit TestCase 同时 implements LoginTestInfo 接口: import java.net.URL; import junit.framework.TestCase; import junit.framework.TestSuite; import junit.textui.TestRunner; import com.meterware.httpunit.WebConversation; import com.meterware.httpunit.WebRequest; import com.meterware.httpunit.WebResponse; import com.meterware.httpunit.WebForm; import com.meterware.httpunit.GetMethodWebRequest; public class LoginTest extends TestCase implements LoginTestInfo{private WebConversation browser; private WebRequest request; private WebResponse response; private String url = "http://localhost:8080/index.html"; public void setUp() throws Exception{browser = new WebConversation(); request = new GetMethodWebRequest(url); response = browser.getResponse(request); } //输入登陆地址的页面地址,验证该页面是否可被正常访问 public void testValidPage() throws Exception{assertNotNull("localhost在网络上不存在!",response);} //验证被访问的页面是否是登陆页面 public void testIsLoginPage() throws Exception{URL currentUrl = response.getURL(); String currentUrlStr = currentUrl.getProtocol() + "://" +currentUrl.getHost() + currentUrl.getPath(); assertEquals("登陆页面不是localhost首页!" ,currentUrlStr,url);} //输入非法用户名、密码,验证登陆失败 public void testBadLogin() throws Exception{WebForm form = response.getForms()[0]; form.setParameter("userName","baduser");form.setParameter("passWord","bad");request = form.getRequest(); response = browser.getResponse(request); assertTrue("您的用户名和密码在localhost没有备案!",response.getText().indexOf("localhost") != -1);} //输入合法用户名、密码,验证登陆成功 public void testGoodLogin() throws Exception{WebForm form = response.getForms()[0]; form.setParameter("userName","smile_xunn");form.setParameter("passWord","*********");//此处需要填写真实密码request = form.getRequest(); response = browser.getResponse(request); assertTrue("转到'localhost'网络失败!",response.getText().indexOf("localhost") != -1);   } public static TestSuite suite(){return new TestSuite(LoginTest.class);    } public static void main(String args[]){TestRunner.run(suite());    } } 如果你的代码质量很高,这个测试就能够通过,出现如下信息: 
.... Time: 7.203 
OK (4 tests) 
       JMeter的性能测试 
 
上面是JMeter的界面的一部分,限于篇幅,具体的使用可以参考Apache网站。 
       JUnitPerf的负载测试 
       在此测试中,可以整合前述的所有方法,把所有放在一块儿进行。JUnitPerf与现有的JUnit测试共同工作。如果你对特定代码有性能要求,那么创建JUnitPerf测试来测试你的代码符合标准,并且确保在你重构代码后代码仍然符合标准。   
三、结语 
       通过以上测试,我想在你的产品交付用户时,一定可以拍着胸脯说:我们是最好的,这一切都被证明了。无论是项目的扩展还是功能的改动,有这些工具的帮助,都不将是可怕的越滚越大的雪球。 
       作为技术至上理论的忠实拥护者,我认为一个测试框架的成功与否都要在用户的使用后评定。理论要靠实践来证实,上述测试正好对我们所学的软件工程课程以及软件实践课程提供了技术说明。作为开源项目,也许上述测试框架还存在着某些弊病,相信在开源精神的鼓舞下,一定会更加尽善尽美。 
  
四、参考文献 
1.Java Tools for Extreme Programming Written By Richard Hightower  Nicholas Lesiecki Copy Right John Wiley & Sons Inc 2002。 
2.Apache组织Jakarta项目文档(包含Ant、JUnit、Cactus、HTTPUnit、JMeter、JUnitPer)。  
 
  |