Strut2中单元测试实例

项目文件结构图:

Strut2中单元测试实例_第1张图片

椭圆框中的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 订单区分错误
	 */
	@Override
	public 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 {

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
	}

	@AfterClass
	public static void tearDownAfterClass() throws Exception {
	}

	/**
	 * 错误条码
	 * 
	 *
	 * Date	  :2012-6-18
	 * Author :GongQiang
	 * @throws Exception 
	 */
	@Test
	public 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 
	 */
	@Test
	public void testDoExecute_noRecord() throws Exception {
		SqlHelper mockSqlHelper = EasyMock.createMock( SqlHelper.class );
		// 返回结果
		EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() ))
				.andReturn( new ArrayList<Map<String,Object>>() );
		// Replay
		EasyMock.replay( mockSqlHelper );
		
		ProductCheckBarcode action = new ProductCheckBarcode();
		action.setBarcode( "xxx0001|bbb" );
		action.setSqlHelper( mockSqlHelper );
		assertEquals("error_1", action.doExecute() );
		//Verify
		EasyMock.verify( mockSqlHelper );
		
		//-----------------------------------------
		mockSqlHelper = EasyMock.createMock( SqlHelper.class );
		// 返回结果
		EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() ))
				.andReturn( null );
		// Replay
		EasyMock.replay( mockSqlHelper );
		
		action = new ProductCheckBarcode();
		action.setBarcode( "xxx0001|bbb" );
		action.setSqlHelper( mockSqlHelper );
		assertEquals("error_1", action.doExecute() );
		//Verify
		EasyMock.verify( mockSqlHelper );
	}

	
	/**
	 * 记录已经执行完毕
	 * 
	 *
	 * Date	  :2012-6-18
	 * Author :GongQiang
	 * @throws Exception 
	 */
	@Test
	public 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")); //已经使用数量 -->剩余数量就是0
		map.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);
		// Replay
		EasyMock.replay( mockSqlHelper );
		
		ProductCheckBarcode action = new ProductCheckBarcode();
		action.setBarcode( "xxx0001|bbb" );
		action.setSqlHelper( mockSqlHelper );
		assertEquals("error_1", action.doExecute() );
		//Verify
		EasyMock.verify( mockSqlHelper );
		
	}
	
	/**
	 * 错误的分区
	 * 
	 *
	 * Date	  :2012-6-18
	 * Author :GongQiang
	 * @throws Exception 
	 */
	@Test
	public 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")); //已经使用数量 -->剩余数量就是50
		map.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);
		// Replay
		EasyMock.replay( mockSqlHelper );
		
		ProductCheckBarcode action = new ProductCheckBarcode();
		action.setBarcode( "xxx0001|bbb" );
		action.setSqlHelper( mockSqlHelper );
		assertEquals("error_2", action.doExecute() );
		//Verify
		EasyMock.verify( mockSqlHelper );
		
	}
	
	/**
	 * 正常
	 * 
	 *
	 * Date	  :2012-6-18
	 * Author :GongQiang
	 * @throws Exception 
	 */
	@Test
	public 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")); //已经使用数量 -->剩余数量就是50
		map.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);
		// Replay
		EasyMock.replay( mockSqlHelper );
		
		ProductCheckBarcode action = new ProductCheckBarcode();
		action.setBarcode( "xxx0001|bbb" );
		action.setSqlHelper( mockSqlHelper );
		action.setSession( new HashMap<String,Object>() );
		assertEquals("success", action.doExecute() );
		//Verify
		EasyMock.verify( mockSqlHelper );
		
	}
}
下面详细讲解测试方法的写法:

1、条码解析错误

	/**
	 * 错误条码
	 * 
	 *
	 * Date	  :2012-6-18
	 * Author :GongQiang
	 * @throws Exception 
	 */
	@Test
	public 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 
	 */
	@Test
	public void testDoExecute_noRecord() throws Exception {
		SqlHelper mockSqlHelper = EasyMock.createMock( SqlHelper.class );
		// 返回结果
		EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() ))
				.andReturn( new ArrayList<Map<String,Object>>() );
		// Replay
		EasyMock.replay( mockSqlHelper );
		
		ProductCheckBarcode action = new ProductCheckBarcode();
		action.setBarcode( "xxx0001|bbb" );
		action.setSqlHelper( mockSqlHelper );
		assertEquals("error_1", action.doExecute() );
		//Verify
		EasyMock.verify( mockSqlHelper );
		
		//-----------------------------------------
		mockSqlHelper = EasyMock.createMock( SqlHelper.class );
		// 返回结果
		EasyMock.expect( mockSqlHelper.executeQuery( (String)EasyMock.anyObject() ))
				.andReturn( null );
		// Replay
		EasyMock.replay( mockSqlHelper );
		
		action = new ProductCheckBarcode();
		action.setBarcode( "xxx0001|bbb" );
		action.setSqlHelper( mockSqlHelper );
		assertEquals("error_1", action.doExecute() );
		//Verify
		EasyMock.verify( mockSqlHelper );
	}
为了实现单元测试的隔离性,这里使用了模拟的 SqlHelper 对象。模拟返回一个空的List 或者 null。

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


3、订单已经执行完成

/**
	 * 记录已经执行完毕
	 * 
	 *
	 * Date	  :2012-6-18
	 * Author :GongQiang
	 * @throws Exception 
	 */
	@Test
	public 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")); //已经使用数量 -->剩余数量就是0
		map.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);
		// Replay
		EasyMock.replay( mockSqlHelper );
		
		ProductCheckBarcode action = new ProductCheckBarcode();
		action.setBarcode( "xxx0001|bbb" );
		action.setSqlHelper( mockSqlHelper );
		assertEquals("error_1", action.doExecute() );
		//Verify
		EasyMock.verify( mockSqlHelper );
		
	}
在实际代码中,当查询到记录时就要继续两个 SQL查询操作(1、查询订单名称;2、查询订单关联数量和已经检查数量)。并依次往 list 结果集中添加对象,但是在测试中为了方便起见,直接 一次性构造出完整的结果重复执行 3次


4、分区错误

	/**
	 * 错误的分区
	 * 
	 *
	 * Date	  :2012-6-18
	 * Author :GongQiang
	 * @throws Exception 
	 */
	@Test
	public 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")); //已经使用数量 -->剩余数量就是50
		map.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);
		// Replay
		EasyMock.replay( mockSqlHelper );
		
		ProductCheckBarcode action = new ProductCheckBarcode();
		action.setBarcode( "xxx0001|bbb" );
		action.setSqlHelper( mockSqlHelper );
		assertEquals("error_2", action.doExecute() );
		//Verify
		EasyMock.verify( mockSqlHelper );
		
	}
这里就是注意构造参数,使得前面的判断都成功,到这里判断分区时错误。


5、OK

	/**
	 * 正常
	 * 
	 *
	 * Date	  :2012-6-18
	 * Author :GongQiang
	 * @throws Exception 
	 */
	@Test
	public 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")); //已经使用数量 -->剩余数量就是50
		map.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);
		// Replay
		EasyMock.replay( mockSqlHelper );
		
		ProductCheckBarcode action = new ProductCheckBarcode();
		action.setBarcode( "xxx0001|bbb" );
		action.setSqlHelper( mockSqlHelper );
		action.setSession( new HashMap<String,Object>() );
		assertEquals("success", action.doExecute() );
		//Verify
		EasyMock.verify( mockSqlHelper );
		
	}
这里要注意,因为实际代码中 调用了sessionMap 的put 方法,因而这里就要传入一个对象。


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

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


你可能感兴趣的:(exception,Date,object,String,单元测试,action)