项目文件结构图:

椭圆框中的Jar 包是单元测试时候需要引入的。

矩形框 MainTest 每个包下一个,为 JUnit4 的 Suite 套件,其作用是执行本包下的“测试类”和子包的 MainTest。

例如:jp.co.snjp.ht.MainTest

package jp.co.snjp.ht;import org.junit.runner.RunWith;
import org.junit.runners.Suite;@RunWith( Suite.class )
@Suite.SuiteClasses({ jp.co.snjp.ht.orderCheck.MainTest.class, jp.co.snjp.ht.outPreconcert.MainTest.class,jp.co.snjp.ht.partOut.MainTest.class,jp.co.snjp.ht.productCheck.MainTest.class,
})
public class MainTest {
}

由于 jp.co.snjp.ht 包下没有“测试类”,因而只需要引入“子包”的 MainTest 即可!

而,jp.co.snjp.ht.orderCheck.MainTest

package jp.co.snjp.ht.orderCheck;import org.junit.runner.RunWith;
import org.junit.runners.Suite;@RunWith( Suite.class )
@Suite.SuiteClasses({ CheckBarcodeTest.class, OrderConfirmTest.class
})
public class MainTest {}

由于 jp.co.snjp.ht.orderCheck 下没有“子包”,因而只需要引入“测试类”

————————————————————————————————

Strut2 提供了隔离容器对象的方法,因而在所有Action 的基类将其织入。

因为本项目很小,没有单独的Business 层和DAO 层,业务逻辑在 Action 中完成,SQL 操作在 SqlHelper 中完成。

为了实现单元测试隔离测试效果,这里提供了 setSqlHelper( SqlHelper sqlHelper ) 方法,这样就可以传入模拟的 SqlHelper 对象

package jp.co.snjp.ht.util;import java.util.Date;
import java.util.List;
import java.util.Map;import jp.co.snjp.dao.SqlHelper;import org.apache.struts2.interceptor.CookiesAware;
import org.apache.struts2.interceptor.RequestAware;
import org.apache.struts2.interceptor.SessionAware;import com.opensymphony.xwork2.ActionSupport;public class BaseAction extends ActionSupport implements RequestAware,SessionAware,CookiesAware{private static final long serialVersionUID = 1L;protected Map<String,Object> requestMap;protected Map<String,Object> sessionMap;protected Map<String,String> cookieMap;/*** 查询结果集*/protected List<Object> list;/*** SQL 执行帮助类*/protected SqlHelper sqlHelper;public void setRequest(Map<String, Object> requestMap) {this.requestMap = requestMap;}public void setSession(Map<String, Object> sessionMap) {this.sessionMap = sessionMap;}public void setCookiesMap(Map<String, String> cookieMap) {this.cookieMap = cookieMap;}public void setSqlHelper( SqlHelper sqlHelper ){this.sqlHelper = sqlHelper;}/*** 记录存储过程执行的日志信息* @param start* @param name** Date      :2012-6-7* Author :GongQiang* @throws Exception */protected void logStroeProcedure( Date start, String name ) throws Exception{String formatDateTime = Utils.formatDateTime( start );String userId = (String) sessionMap.get( "user_id" );String sql = "insert into HT_CCGC_LOG(usercode,ccgcmc,kszxsj) "+" values ('"+ userId +"','"+ name +"','"+ formatDateTime+"' );";sqlHelper.executeSQL( sql );}
}

虽然提供了 setSqlHelper( SqlHelper sqlHelper ) 方法,但是这方法在什么时候调用呢?

为了解决这个问题,就只能把实际的业务逻辑放到 doExecute() 方法下去执行,而在 execute()方法下调用 setSqlHelper()方法,只用测试 doExecute()方法。

doExecute()方法修饰为包可见,这样就只有测试代码可以访问。代码如下:

package jp.co.snjp.ht.productCheck;import java.math.BigDecimal;
import java.util.List;
import java.util.Map;import jp.co.snjp.dao.SqlHelper;
import jp.co.snjp.ht.util.BaseAction;
import jp.co.snjp.ht.util.SpotTicketBarcodeParser;
/*** 部品检查录入-Barcode扫描Action* @author GongQiang**/
public class ProductCheckBarcode extends BaseAction {private static final long serialVersionUID = 1L;private String barcode;private String backUrl;public String getBarcode() {return barcode;}public void setBarcode(String barcode) {this.barcode = barcode;}public String getBackUrl() {return backUrl;}public void setBackUrl(String backUrl) {this.backUrl = backUrl;}/*** error_0 条码不符合规则* error_1 订单在DB中不存在 或 订单已经执行完毕* error_2 订单区分错误*/@Overridepublic String execute() throws Exception {super.execute();setBackUrl( "productCheck/scanBarcode.jsp" );setSqlHelper( new SqlHelper() );return doExecute();}String doExecute()throws Exception {SpotTicketBarcodeParser parser = new SpotTicketBarcodeParser( barcode );if( ! parser.valid() ){return "error_0";}queryOrderInfo( parser.getOrderNo() );if( orderNotExist() || orderFinished() ){return "error_1";}if( !checkDistinguish() ){return "error_2";}sessionMap.put( "order_info", list.get(0) );return SUCCESS;}void queryOrderInfo( String orderNo ) throws Exception{String sql = "select top 1 * from iOrder_Check where " +" OrderNo='" + orderNo + "' ;";list = sqlHelper.executeQuery( sql );if( list == null || list.isEmpty() ){return;}queryNameCount(orderNo);}/*** 查询订单名称 和 订单残&实收数量* ** Date      :2012-6-8* Author :GongQiang* @throws Exception */private void queryNameCount( String orderNo ) throws Exception{String sql = "select sum(nqty) as usedCount from iOrder_Check "+" where orderno='" + orderNo + "' group by orderno;";List usedCountResult = sqlHelper.executeQuery( sql );BigDecimal orderCount = (BigDecimal) ((Map)list.get(0)).get( "pqty" );BigDecimal usedCount = (BigDecimal) ((Map)usedCountResult.get(0)).get( "usedcount" );BigDecimal remainCount = orderCount.subtract( usedCount );sql = "select itemname from iorder_operate where " +" OrderNo='" + orderNo + "' ;";List itemNameResult = sqlHelper.executeQuery( sql );String itemName = (String) ((Map)itemNameResult.get(0)).get( "itemname" );((Map)list.get(0)).put( "remaincount", remainCount );((Map)list.get(0)).put( "usedcount", usedCount );((Map)list.get(0)).put( "itemname", itemName );}/*** DB中没有关联的订单* @return** Date    :2012-6-7* Author :GongQiang*/boolean orderNotExist(){if( list == null || list.isEmpty() ){return true;}return false;}/*** 该订单已经执行完毕* @return** Date     :2012-6-7* Author :GongQiang*/boolean orderFinished(){BigDecimal remainCountInOrder = (BigDecimal)((Map)list.get(0)).get( "remaincount" );if( remainCountInOrder != null ){return remainCountInOrder.compareTo( new BigDecimal("0") ) <= 0 ;}return false;}/*** 检查区分是否正确* @return** Date     :2012-6-7* Author :GongQiang*/private boolean checkDistinguish(){String[] rights = { "保证" };String dist = (String) ((Map)list.get(0)).get( "chkdistinguish" );for( int i=0 ; i<rights.length ; i++ ){if( rights[i].equals( dist ) ){return true;}}return false;}
}

逻辑很简单,这里仅仅测试最基本的4 条执行路径

1、条码解析错误

2、订单在DB中不存在

3、订单已经执行完成

4、分区错误

5、OK

下面是完整的测试类:

package jp.co.snjp.ht.productCheck;import static org.junit.Assert.*;import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;import jp.co.snjp.dao.SqlHelper;import org.easymock.classextension.EasyMock;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;public class ProductCheckBarcodeTest {@BeforeClasspublic static void setUpBeforeClass() throws Exception {}@AfterClasspublic static void tearDownAfterClass() throws Exception {}/*** 错误条码* ** Date   :2012-6-18* Author :GongQiang* @throws Exception */@Testpublic void testDoExecute_errorBarcode() throws Exception {ProductCheckBarcode action = new ProductCheckBarcode();action.setBarcode( "xxx0001" );assertEquals("error_0", action.doExecute() );action = new ProductCheckBarcode();action.setBarcode( "0123456789012345678901234567890123456789555" );assertEquals("error_0", action.doExecute() );}/*** DB中没有关联的记录* ** Date      :2012-6-18* Author :GongQiang* @throws Exception */@Testpublic void testDoExecute_noRecord() throws Exception {SqlHelper mockSqlHelper = EasyMock.createMock( SqlHelper.class );// 返回结果EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() )).andReturn( new ArrayList<Map<String,Object>>() );// ReplayEasyMock.replay( mockSqlHelper );ProductCheckBarcode action = new ProductCheckBarcode();action.setBarcode( "xxx0001|bbb" );action.setSqlHelper( mockSqlHelper );assertEquals("error_1", action.doExecute() );//VerifyEasyMock.verify( mockSqlHelper );//-----------------------------------------mockSqlHelper = EasyMock.createMock( SqlHelper.class );// 返回结果EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() )).andReturn( null );// ReplayEasyMock.replay( mockSqlHelper );action = new ProductCheckBarcode();action.setBarcode( "xxx0001|bbb" );action.setSqlHelper( mockSqlHelper );assertEquals("error_1", action.doExecute() );//VerifyEasyMock.verify( mockSqlHelper );}/*** 记录已经执行完毕* ** Date    :2012-6-18* Author :GongQiang* @throws Exception */@Testpublic void testDoExecute_finished() throws Exception {//list -- 返回的结果集Map<String, Object> map = new HashMap<String,Object>();map.put("pqty", new BigDecimal("100")); //订单关联数量map.put("usedcount", new BigDecimal("100")); //已经使用数量 -->剩余数量就是0map.put("itemname", "TextOrderXXX");List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();list.add( map );SqlHelper mockSqlHelper = EasyMock.createMock( SqlHelper.class );EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() )).andReturn( list ).times(3);// ReplayEasyMock.replay( mockSqlHelper );ProductCheckBarcode action = new ProductCheckBarcode();action.setBarcode( "xxx0001|bbb" );action.setSqlHelper( mockSqlHelper );assertEquals("error_1", action.doExecute() );//VerifyEasyMock.verify( mockSqlHelper );}/*** 错误的分区* ** Date     :2012-6-18* Author :GongQiang* @throws Exception */@Testpublic void testDoExecute_errorDistinguish() throws Exception {//list -- 返回的结果集Map<String, Object> map = new HashMap<String,Object>();map.put("pqty", new BigDecimal("100")); //订单关联数量map.put("usedcount", new BigDecimal("50")); //已经使用数量 -->剩余数量就是50map.put("itemname", "TextOrderXXX");map.put( "chkdistinguish", "不存在" );List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();list.add( map );SqlHelper mockSqlHelper = EasyMock.createMock( SqlHelper.class );EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() )).andReturn( list ).times(3);// ReplayEasyMock.replay( mockSqlHelper );ProductCheckBarcode action = new ProductCheckBarcode();action.setBarcode( "xxx0001|bbb" );action.setSqlHelper( mockSqlHelper );assertEquals("error_2", action.doExecute() );//VerifyEasyMock.verify( mockSqlHelper );}/*** 正常* ** Date     :2012-6-18* Author :GongQiang* @throws Exception */@Testpublic void testDoExecute_ok() throws Exception {//list -- 返回的结果集Map<String, Object> map = new HashMap<String,Object>();map.put("pqty", new BigDecimal("100")); //订单关联数量map.put("usedcount", new BigDecimal("50")); //已经使用数量 -->剩余数量就是50map.put("itemname", "TextOrderXXX");map.put( "chkdistinguish", "保证" );List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();list.add( map );SqlHelper mockSqlHelper = EasyMock.createMock( SqlHelper.class );EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() )).andReturn( list ).times(3);// ReplayEasyMock.replay( mockSqlHelper );ProductCheckBarcode action = new ProductCheckBarcode();action.setBarcode( "xxx0001|bbb" );action.setSqlHelper( mockSqlHelper );action.setSession( new HashMap<String,Object>() );assertEquals("success", action.doExecute() );//VerifyEasyMock.verify( mockSqlHelper );}
}

下面详细讲解测试方法的写法:

1、条码解析错误

 /*** 错误条码* ** Date    :2012-6-18* Author :GongQiang* @throws Exception */@Testpublic void testDoExecute_errorBarcode() throws Exception {ProductCheckBarcode action = new ProductCheckBarcode();action.setBarcode( "xxx0001" );assertEquals("error_0", action.doExecute() );action = new ProductCheckBarcode();action.setBarcode( "0123456789012345678901234567890123456789555" );assertEquals("error_0", action.doExecute() );}

当条码解析错误时,查询没有机会执行也就没有必要传入 SqlHelper 对象。

2、订单在DB中不存在

 /*** DB中没有关联的记录* ** Date      :2012-6-18* Author :GongQiang* @throws Exception */@Testpublic void testDoExecute_noRecord() throws Exception {SqlHelper mockSqlHelper = EasyMock.createMock( SqlHelper.class );// 返回结果EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() )).andReturn( new ArrayList<Map<String,Object>>() );// ReplayEasyMock.replay( mockSqlHelper );ProductCheckBarcode action = new ProductCheckBarcode();action.setBarcode( "xxx0001|bbb" );action.setSqlHelper( mockSqlHelper );assertEquals("error_1", action.doExecute() );//VerifyEasyMock.verify( mockSqlHelper );//-----------------------------------------mockSqlHelper = EasyMock.createMock( SqlHelper.class );// 返回结果EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() )).andReturn( null );// ReplayEasyMock.replay( mockSqlHelper );action = new ProductCheckBarcode();action.setBarcode( "xxx0001|bbb" );action.setSqlHelper( mockSqlHelper );assertEquals("error_1", action.doExecute() );//VerifyEasyMock.verify( mockSqlHelper );}

为了实现单元测试的隔离性,这里使用了模拟的 SqlHelper 对象。模拟返回一个空的List 或者 null。

注意:模拟方法执行时候是严格的参数匹配的,为简易性这里直接使用 EasyMock.anyObject(),这样任何参数都能匹配执行。

3、订单已经执行完成

/*** 记录已经执行完毕* ** Date     :2012-6-18* Author :GongQiang* @throws Exception */@Testpublic void testDoExecute_finished() throws Exception {//list -- 返回的结果集Map<String, Object> map = new HashMap<String,Object>();map.put("pqty", new BigDecimal("100")); //订单关联数量map.put("usedcount", new BigDecimal("100")); //已经使用数量 -->剩余数量就是0map.put("itemname", "TextOrderXXX");List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();list.add( map );SqlHelper mockSqlHelper = EasyMock.createMock( SqlHelper.class );EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() )).andReturn( list ).times(3);// ReplayEasyMock.replay( mockSqlHelper );ProductCheckBarcode action = new ProductCheckBarcode();action.setBarcode( "xxx0001|bbb" );action.setSqlHelper( mockSqlHelper );assertEquals("error_1", action.doExecute() );//VerifyEasyMock.verify( mockSqlHelper );}

在实际代码中,当查询到记录时就要继续两个 SQL查询操作(1、查询订单名称;2、查询订单关联数量和已经检查数量)。并依次往 list 结果集中添加对象,但是在测试中为了方便起见,直接 一次性构造出完整的结果并 重复执行 3次。

4、分区错误

 /*** 错误的分区* ** Date   :2012-6-18* Author :GongQiang* @throws Exception */@Testpublic void testDoExecute_errorDistinguish() throws Exception {//list -- 返回的结果集Map<String, Object> map = new HashMap<String,Object>();map.put("pqty", new BigDecimal("100")); //订单关联数量map.put("usedcount", new BigDecimal("50")); //已经使用数量 -->剩余数量就是50map.put("itemname", "TextOrderXXX");map.put( "chkdistinguish", "不存在" );List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();list.add( map );SqlHelper mockSqlHelper = EasyMock.createMock( SqlHelper.class );EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() )).andReturn( list ).times(3);// ReplayEasyMock.replay( mockSqlHelper );ProductCheckBarcode action = new ProductCheckBarcode();action.setBarcode( "xxx0001|bbb" );action.setSqlHelper( mockSqlHelper );assertEquals("error_2", action.doExecute() );//VerifyEasyMock.verify( mockSqlHelper );}

这里就是注意构造参数,使得前面的判断都成功,到这里判断分区时错误。

5、OK

 /*** 正常* ** Date      :2012-6-18* Author :GongQiang* @throws Exception */@Testpublic void testDoExecute_ok() throws Exception {//list -- 返回的结果集Map<String, Object> map = new HashMap<String,Object>();map.put("pqty", new BigDecimal("100")); //订单关联数量map.put("usedcount", new BigDecimal("50")); //已经使用数量 -->剩余数量就是50map.put("itemname", "TextOrderXXX");map.put( "chkdistinguish", "保证" );List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();list.add( map );SqlHelper mockSqlHelper = EasyMock.createMock( SqlHelper.class );EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() )).andReturn( list ).times(3);// ReplayEasyMock.replay( mockSqlHelper );ProductCheckBarcode action = new ProductCheckBarcode();action.setBarcode( "xxx0001|bbb" );action.setSqlHelper( mockSqlHelper );action.setSession( new HashMap<String,Object>() );assertEquals("success", action.doExecute() );//VerifyEasyMock.verify( mockSqlHelper );}

这里要注意,因为实际代码中 调用了sessionMap 的put 方法,因而这里就要传入一个对象。

————————————————————————————————————

扩展:当有单独的 Business 层和 DAO 层时候。也许没有办法像 SqlHelper 简单的只需要一个接口方法即可,也许就要每个子 Action 设置相应的Business 对象。

Strut2中单元测试实例相关推荐

  1. JUnit基础及第一个单元测试实例(JUnit3.8)

    JUnit基础及第一个单元测试实例(JUnit3.8) 单元测试 单元测试(unit testing) ,是指对软件中的最小可测试单元进行检查和验证. 单元测试不是为了证明您是对的,而是为了证明您没有 ...

  2. python现有两个磁盘文件a和b_有两个磁盘文件A和B,各存放一行字母,要求把这两个文件中的信息合并(按字母顺序排列), 输出到一个新文件C中Python实例...

    有两个磁盘文件A和B,各存放一行字母,要求把这两个文件中的信息合并(按字母顺序排列), 输出到一个新文件C中Python实例 题目:有两个磁盘文件A和B,各存放一行字母,要求把这两个文件中的信息合并( ...

  3. OpenCV将GIS数据加载到OpenCV容器中的实例(附完整代码)

    OpenCV将GIS数据加载到OpenCV容器中的实例 OpenCV将GIS数据加载到OpenCV容器中的实例 OpenCV将GIS数据加载到OpenCV容器中的实例 #include "o ...

  4. 请举出OSI七层模型在实际应用中的实例

    答案1: 请举出OSI七层模型在实际应用中的实例,就是问OSI七层模型在实际操作中的例子,比如说会话层的应用例子是打开多个浏览器窗口这种. 注:不是解释这七层模型 举例: 就用QQ聊天为例子,用户在Q ...

  5. Java中定义实例变量时指定初始化值顺序

    定义实例变量时指定的初始值.初始化块中为实例变量指定的初始值.构造器中为实例变量指定的初始值,三者的作用完全类似,都用于对实例变量指定初始值.经过编译器处理之后,它们对应的赋值语句都被合并到构造器中. ...

  6. java中throws用法_java中throws实例用法详解

    在程序出现异常时,会有一个抛出异常的throw出现,这里我们要跟今天所讲的throws区分开.throws的作用是声明抛出,在名称上也跟throw有所不同.下面我们就throws对策概念.语法.实例带 ...

  7. pythonappend用法_python中append实例用法总结

    append()函数 描述:在列表ls最后(末尾)添加一个元素object 语法:ls.append(object) -> None 无返回值 例: a=[1,2,3] a.append(5) ...

  8. c#中overlord实例_具有Overlord的WildFly 8.1中的API管理

    c#中overlord实例 昨天,我简要介绍了霸王项目家族. 今天是时候进行试驾了. API管理子项目两天前发布了1.0.0.Alpha1,并根据18个月的路线图介绍了第一组功能. APIMan到底是 ...

  9. 在python中、实例变量在类的内部通过_[宜配屋]听图阁

    1.类变量.实例变量概念 类变量: 类变量就是定义在类中,但是在函数体之外的变量.通常不使用self.变量名赋值的变量.类变量通常不作为类的实例变量的,类变量对于所有实例化的对象中是公用的. 实例变量 ...

最新文章

  1. 某口腔app发现了不友善词汇(f*ckMobile)
  2. Debian/Ubuntu--blade安装与使用
  3. 003-读书笔记-Vue官网 计算属性与监听器
  4. Delphi 7 中使用IdUDPServer1和IdUDPClient1控件实现通信检测
  5. Laravel的初始化安装 1
  6. 回到地球之后,这个男人创建了Ubuntu
  7. php源码编译常见错误解决方案
  8. 工作经验总结:百万数据引发的性能瓶颈问题
  9. Linux内核那些事之连接跟踪
  10. 浏览器资源嗅探器_浏览器嗅探条件注释
  11. NVIDIA-cuda-cudnn下载地址
  12. Android开发学习总结——appcompat_v7项目说明
  13. IFRAME 元素语法
  14. 个人信贷不良资产成因和策略
  15. 卸载手机自带的系统软件
  16. 介绍会议中控系统模块化构成及功能作用
  17. 日本多城现共享单车 日网友:感受到中国式刺激
  18. 哪里买. com最便宜?
  19. 【GANs学习笔记】(十八)LAPGAN、ProGAN、SRGAN
  20. 华为OD机试题 - 找出重复代码(JavaScript)| 包含代码编写思路

热门文章

  1. 论文浅尝 | ​ADRL:一个基于注意力机制的知识图谱深度强化学习框架
  2. 论文浅尝 | 基于知识库的自然语言理解 02#
  3. mysql查询时间段内的数据
  4. MySQL 练习 创建表格2
  5. Python基本数据类型以及字符串
  6. ASP.NET MVC中在 @RenderBody() 或者 @Html.Partial()中需要使用引入外部js,css
  7. 201521123035《Java程序设计》第八周学习总结
  8. Redis学习笔记之Redis的对象
  9. [iOS]深度遍历view的subview
  10. windows下MBCS和UNICODE编码的转换