Java使用TestNG结合Spring框架运行测试


        TestNG是一个功能强大的测试框架,是Junit的一个增强版本,项目中使用TestNG来运行自动化测试。Spring框架提供了与TestNG集成的类,方便开发人员进行自动化测试。本文主要介绍:” Spring测试框架与TestNG结合”、“BaseMapperTest(测试Mapper接口的公共类)”、“BaseModelTest(测试Model方法的公共类)”、“BaseActionTest(测试Action接口的公共类)”、“ModelMapperTest(测试Mapper接口)”、“ModelActionTest(测试Action接口)”、“SITTest(集成测试)”、“syncSITTest(同步集成测试)”、“TestNG.xml(测试类分组运行)”、“测试文档”。

1、Spring测试框架与TestNG结合。

AbstractTestNGSpringContextTests抽象基本测试类,它将Spring TestContext Framework与TestNG环境中的显式ApplicationContext测试支持集成在一起。将为测试类设置spring应用程序上下文。测试类继承AbstractTestNGSpringContextTests进行spring单元测试时:

@ContextConfiguration(locations = { "classpath:ApplicationContext.xml" })
public class BaseTestNGSpringContextTest extends AbstractTestNGSpringContextTests {
	/** 用于生成指定日期的报表,数据库中已经准备好了这天的数据。传递2号,会生成1号的零售单的报表 */
	public static final String REPORT_DATE = "2030-01-02";
	/** 用于生成指定月报的时间。SP会生成2030-01-01~2030-01-31的报表,这里只需要传递某月最后一天的日期时间 */
	public static final String REPORT_DATE_END = "2030-01-31 00:00:00";

	public static final int STAFF_ID3 = 3;
	public static final int STAFF_ID4 = 4;
	public static final int STAFF_ID1 = 1;
	public static final int STAFF_ID2 = 2;

	@BeforeClass
	public void setup() {
		Shared.printTestClassStartInfo();

	}

	@AfterClass
	public void tearDown() {
		Shared.printTestClassEndInfo();

	}

BaseTestNGSpringContextTest继承了AbstractTestNGSpringContextTests,并且指定了Spring的配置文件ApplicationContext.xml,可以在运行测试的时候启动Spring,获取Spring上下文,如可以获取、使用ApplicationContext对象:

@TestExecutionListeners({ ServletTestExecutionListener.class, DirtiesContextBeforeModesTestExecutionListener.class,
	DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
public abstract class AbstractTestNGSpringContextTests implements IHookable, ApplicationContextAware {

	/** Logger available to subclasses */
	protected final Log logger = LogFactory.getLog(getClass());

	/**
	 * The {@link ApplicationContext} that was injected into this test instance
	 * via {@link #setApplicationContext(ApplicationContext)}.
	 */
	protected ApplicationContext applicationContext;

	private final TestContextManager testContextManager;

	private Throwable testException;

2、BaseMapperTest(测试Mapper接口的公共类)。

BaseMapperTest继承了BaseTestNGSpringContextTest,给它的子类提供数据访问层Mapper类的对象,以便测试Mapper层的接口:

@WebAppConfiguration
public class BaseMapperTest extends BaseTestNGSpringContextTest {

	/** 为测试提供Mapper */
	protected static CommodityMapper commodityMapper;
	protected static CommodityShopInfoMapper commodityShopInfoMapper;
	protected static BarcodesMapper barcodesMapper;
	protected static BrandMapper brandMapper;
	protected static ProviderCommodityMapper providerCommodityMapper;
	protected static PurchasingOrderCommodityMapper purchasingOrderCommodityMapper;
	protected static RetailTradeMapper retailTradeMapper;
	protected static RetailTradeCommodityMapper retailTradeCommodityMapper;
	protected static SubCommodityMapper subCommodityMapper;
	protected static CommodityHistoryMapper commodityHistoryMapper;
	protected static CommoditySyncCacheMapper commoditySyncCacheMapper;
	protected static BarcodesSyncCacheMapper barcodesSyncCacheMapper;
……

	@BeforeClass
	public void setUp() {
		super.setup();

		if (!bMapperIsInitialized) {
			initMapper();
			// initMapMapper();
			bMapperIsInitialized = true;
		}
	}

	private void initMapper() {
		commodityMapper = (CommodityMapper) applicationContext.getBean("commodityMapper");
		commodityShopInfoMapper = (CommodityShopInfoMapper) applicationContext.getBean("commodityShopInfoMapper");
		barcodesMapper = (BarcodesMapper) applicationContext.getBean("barcodesMapper");
		brandMapper = (BrandMapper) applicationContext.getBean("brandMapper");
		providerCommodityMapper = (ProviderCommodityMapper) applicationContext.getBean("providerCommodityMapper");
		purchasingOrderCommodityMapper = (PurchasingOrderCommodityMapper) applicationContext.getBean("purchasingOrderCommodityMapper");
		retailTradeMapper = (RetailTradeMapper) applicationContext.getBean("retailTradeMapper");
		retailTradeCommodityMapper = (RetailTradeCommodityMapper) applicationContext.getBean("retailTradeCommodityMapper");
		subCommodityMapper = (SubCommodityMapper) applicationContext.getBean("subCommodityMapper");
		commodityHistoryMapper = (CommodityHistoryMapper) applicationContext.getBean("commodityHistoryMapper");
		promotionMapper = (PromotionMapper) applicationContext.getBean("promotionMapper");
		...... 

3、BaseModelTest(测试Model方法的公共类)。

BaseModelTest继承了BaseMapperTest。

为了方便管理、维护,每个Model对象都创建了对应的BaseModelTest类,如Staff对应BaseStaffTest,封装了创建Staff对象和关于Staff相关的方法,供其它测试类调用:

@WebAppConfiguration
public class BaseStaffTest extends BaseMapperTest{
	public final static String PASSWORD_123456 = "123456";
	public final static String Key_NewPasword = "sPasswordEncryptedNew";
	public final static String Key_OldPasword = "sPasswordEncryptedOld";
	public final static String TEST_RESULT_ErrorMsgNotAsExpected = "返回的错误信息不是预期的";

	@BeforeClass
	public void setup() {
		Shared.printTestClassStartInfo();

		// Doctor_checkICID();
		// Doctor_checkIsFirstTimeLogin();
		// Doctor_checkName();
		// Doctor_checkOpenID();
		// Doctor_checkPhone();
		// Doctor_checkStatus();
		// Doctor_checkUnionid();
		// Doctor_checkWeChat();
		// Doctor_checkCreate();
	}


	public static class DataInput {
		private static Staff staffInput = new Staff();
		private static Promotion promotionInput = null;
		private static Random r = new Random();
		private static BxStaff bxStaffInput;

		public static final Staff getStaff() throws Exception {
			staffInput.setPhone(Shared.getValidStaffPhone());
			Thread.sleep(1);
			staffInput.setName("店员" + Shared.generateCompanyName(6));
			Thread.sleep(1);
			Thread.sleep(1);
			staffInput.setICID(Shared.getValidICID()); //
			Thread.sleep(1);
			staffInput.setWeChat("rr1" + Shared.generateStringByTime(6));//
			Thread.sleep(1);
			// staffInput.setSalt(MD5Util.MD5("123456" + BaseAction.SHADOW));
			staffInput.setSalt(Shared.getFakedSalt());
			SimpleDateFormat sdf = new SimpleDateFormat(BaseAction.DATETIME_FORMAT_Default3);//
			staffInput.setPasswordExpireDate(sdf.parse("2018/12/22 13:11:00")); // 该功能未启用
			staffInput.setIsFirstTimeLogin(EnumBoolean.EB_Yes.getIndex());
			staffInput.setShopID(1);
			staffInput.setDepartmentID(1);
			staffInput.setRoleID(EnumTypeRole.ETR_Boss.getIndex());
			staffInput.setReturnSalt(EnumBoolean.EB_Yes.getIndex());
			staffInput.setStatus(EnumStatusStaff.ESS_Incumbent.getIndex());
			staffInput.setInvolvedResigned(Staff.INVOLVE_RESIGNED);
			staffInput.setOperator(EnumBoolean.EB_NO.getIndex());
			return (Staff) staffInput.clone();
		}
......

通过调用Mapper接口向数据库创建插入一条数据,返回插入的对象:

	public static Staff createStaffViaMapper(int iUseCaseID, Staff staff, ErrorInfo.EnumErrorCode enumErrorCode) {
		String err = staff.checkCreate(iUseCaseID);
		Assert.assertEquals(err, "");
		Map<String, Object> params = staff.getCreateParam(BaseBO.INVALID_CASE_ID, staff);
		//
		DataSourceContextHolder.setDbName(Shared.DBName_Test);
		Staff staffCreate = (Staff) staffMapper.create(params);
		//
		Assert.assertTrue(EnumErrorCode.values()[Integer.parseInt(params.get(BaseAction.SP_OUT_PARAM_iErrorCode).toString())] == enumErrorCode, params.get(BaseAction.SP_OUT_PARAM_sErrorMsg).toString());
		//
		if (staffCreate != null) { // 条件合法,创建成功
			staffCreate.setSalt(staff.getSalt());
			staffCreate.setRoleID(staff.getRoleID());
			err = staffCreate.checkCreate(BaseBO.INVALID_CASE_ID);
			Assert.assertEquals(err, "");
			//
			staff.setIgnoreIDInComparision(true);
			if (staff.compareTo(staffCreate) != 0) {
				Assert.assertTrue(false, "创建的对象的字段与DB读出的不相等");
			}
			//
			System.out.println("测试成功,创建店员成功: " + staffCreate);
		} else { // 条件不合法,创建失败
			System.out.println("测试成功,创建失败:" + params.get(BaseAction.SP_OUT_PARAM_sErrorMsg));
		}
		//
		return staffCreate;
	}

4、BaseActionTest(测试Action接口的公共类)。

BaseActionTest也是继承了BaseMapperTest,该类主要是给它的子类提供了业务(BO)层对象和MockHttpSession(HttpSession 接口的模拟实现,用于测试web框架)、WebApplicationContext(可以访问spring上下文)、MockMvc(对Http请求的模拟)等对象,以便测试控制层(Action)层的接口。

5、ModelMapperTest(测试Mapper接口)。

每一个Model都对应一个ModelMapperTest类用来测试关于该Model的Mapper层接口,如StaffMapperTest:

public class StaffMapperTest extends BaseMapperTest {

	/** 检查staff手机号唯一字段是否已存在 */
	public static final int CASE_CHECK_UNIQUE_STAFFPHONE = 1;


单元测试中的每一个测试用例都用一个独立的测试方法来测试:
	@Test
	public void createTest_CASE2() throws Exception {
		Shared.printTestMethodStartInfo();

		Shared.caseLog("case2 重复添加该用户(业务上不允许的) 已有的身份证进行注册 ");

		Staff staff = BaseStaffTest.DataInput.getStaff();
		// 首次添加
		Staff staffCreate = BaseStaffTest.createStaff(BaseBO.INVALID_CASE_ID, staff, EnumErrorCode.EC_NoError, staffMapper);
		// 重复添加
		Staff staff2 = BaseStaffTest.DataInput.getStaff();
		staff2.setICID(staffCreate.getICID());
		BaseStaffTest.createStaff(BaseBO.INVALID_CASE_ID, staff2, EnumErrorCode.EC_BusinessLogicNotDefined, staffMapper);

		// 把测试数据改为离职,以免随机数重复
		BaseStaffTest.deleteStaff(staffCreate, EnumErrorCode.EC_NoError, staffMapper);
	}

测试方法简单组成如下:

	@Test
	public void createTest_CASE1() throws Exception {
		Shared.printTestMethodStartInfo();

		Shared.caseLog("case1 首次添加该用户 ");

		Staff staff = BaseStaffTest.DataInput.getStaff();
		Staff staffCreate = BaseStaffTest.createStaff(BaseBO.INVALID_CASE_ID, staff, EnumErrorCode.EC_NoError, staffMapper);

		// 把测试数据改为离职,以免随机数重复
		BaseStaffTest.deleteStaff(staffCreate, EnumErrorCode.EC_NoError, staffMapper);
	}

@Test,引入了org.testng.annotations.Test的测试类。

测试方法的开头,调用了Shared.printTestMethodStartInfo(),打印出测试方法的信息:

	public static void printTestMethodStartInfo() {
		StackTraceElement ste = new Exception().getStackTrace()[1];
		System.out.println(new SimpleDateFormat(BaseAction.DATETIME_FORMAT_Default2).format(new Date()) + "\t\t开始运行测试:" + ste.getClassName() + "." + ste.getMethodName() + "()...");
	}

Shared.caseLog打印测试用例的名称,方便知道测试的是什么功能。

获取一个用于测试的通用Staff对象,可以调用BaseStaffTest.DataInput.getStaff(),统一在一个类里面获取。(原型模式的使用,创建重复的对象,同时又能保证性能,是一种创建对象的最佳方式)。

BaseStaffTest.createStaff()调用了Mapper接口,向数据库中新建一个Staff。需要新建Staff对象的测试都可以调用它。

为了不影响其它测试,测试方法的最后还会把自己产生的数据尽量删除掉。

如在数据库创建了Staff对象,最后需要把该对象从数据库删除BaseStaffTest.deleteStaff()。

调用Mapper接口创建对象,需要判断错误码(从数据库返回),对象是否为空,创建出来的对象是否是根据给定的参数创建出来的:

	public static Staff createStaffViaMapper(int iUseCaseID, Staff staff, ErrorInfo.EnumErrorCode enumErrorCode) {
		String err = staff.checkCreate(iUseCaseID);
		Assert.assertEquals(err, "");
		Map<String, Object> params = staff.getCreateParam(BaseBO.INVALID_CASE_ID, staff);
		//
		DataSourceContextHolder.setDbName(Shared.DBName_Test);
		Staff staffCreate = (Staff) staffMapper.create(params);
		//
		Assert.assertTrue(EnumErrorCode.values()[Integer.parseInt(params.get(BaseAction.SP_OUT_PARAM_iErrorCode).toString())] == enumErrorCode, params.get(BaseAction.SP_OUT_PARAM_sErrorMsg).toString());
		//
		if (staffCreate != null) { // 条件合法,创建成功
			staffCreate.setSalt(staff.getSalt());
			staffCreate.setRoleID(staff.getRoleID());
			err = staffCreate.checkCreate(BaseBO.INVALID_CASE_ID);
			Assert.assertEquals(err, "");
			//
			staff.setIgnoreIDInComparision(true);
			if (staff.compareTo(staffCreate) != 0) {
				Assert.assertTrue(false, "创建的对象的字段与DB读出的不相等");
			}
			//
			System.out.println("测试成功,创建店员成功: " + staffCreate);
		} else { // 条件不合法,创建失败
			System.out.println("测试成功,创建失败:" + params.get(BaseAction.SP_OUT_PARAM_sErrorMsg));
		}
		//
		return staffCreate;
	}

staffCreate是调用mapper接口创建出来的对象,需要判断是否为null,与我们传进来的staff进行比较compareTo。

6、ModelActionTest(测试Action接口)。

ModelActionTest测试类用来测试Model对应的ModelAction接口。如StaffActionTest测试StaffAction接口:

@WebAppConfiguration
public class StaffActionTest extends BaseActionTest {
	private HttpSession session1;
	private Staff staff;
	…… 

调用StaffAction接口的方法抽取到了BaseStaffTest类:

		Staff staffCreate = BaseStaffTest.createStaffViaAction(staff, sessionBoss, mvc, mapBO, Shared.DBName_Test);

使用了MockMvc、HttpSession来模拟用户的请求和保存用户会话信息,MvcResult接收请求返回结果信息:

	public static Staff createStaffViaAction(Staff staff, HttpSession session, MockMvc mvc, Map<String, BaseBO> BOsMap, String dbName) throws Exception {
		MvcResult mr = mvc.perform(//
				DataInput.getBuilder("/staff/createEx.bx", MediaType.APPLICATION_JSON, staff)//
						.session((MockHttpSession) session)//
		)//
				.andExpect(status().isOk())//
				.andDo(print())//
				.andReturn();//
		// 结果验证:检查错误码
		Shared.checkJSONErrorCode(mr);
		StaffRoleBO staffRoleBO = (StaffRoleBO) BOsMap.get(StaffRoleBO.class.getSimpleName());
		StaffCP.verifyCreate(mr, staff, staffRoleBO, dbName);
		//
		JSONObject object = JSONObject.fromObject(mr.getResponse().getContentAsString());
		Staff staff2 = new Staff();
		staff2 = (Staff) staff2.parse1(object.getString(BaseAction.KEY_Object));

		return staff2;
	}

对接口返回的信息检查错误码Shared.checkJSONErrorCode(mr):

	public static JSONObject checkJSONErrorCode(MvcResult mr) throws UnsupportedEncodingException {
		String json = mr.getResponse().getContentAsString();
		JSONObject o = JSONObject.fromObject(json);
		String err = JsonPath.read(o, "$." + BaseAction.JSON_ERROR_KEY);
		String msg = "没有错误信息";
		try {
			msg = JsonPath.read(o, "$." + BaseAction.KEY_HTMLTable_Parameter_msg);
		} catch (Exception e) {
		}

		if (err.compareTo(EnumErrorCode.EC_NoError.toString()) != 0) {
			Assert.fail("错误码不正确!" + err + "(" + msg + ")");
		}

		return o;
	}

接口返回的数据,我们需要检查的地方比较多的时候,会新建一个类来做这个工作:

StaffCP.verifyCreate(mr, staff, staffRoleBO, dbName);
@WebAppConfiguration
public class StaffCP {
	/** 1、检查员工A的普通缓存是否创建。 2、检查数据库T_Staff,查看员工A是否正常创建。
	 * 3、检查数据库T_StaffRole,查看是否创建了员工A关联的角色数据。 */
	public static void verifyCreate(MvcResult mr, Staff staff, StaffRoleBO staffRoleBO, String dbName) throws Exception {
		Staff staffClone = (Staff) staff.clone();
		JSONObject object = JSONObject.fromObject(mr.getResponse().getContentAsString());
		Staff staff2 = new Staff();
		staff2 = (Staff) staff2.parse1(object.getString(BaseAction.KEY_Object));
		Assert.assertTrue(staff2 != null, "解析异常");
		// 1、检查员工A的普通缓存是否创建。
		ErrorInfo ecOut = new ErrorInfo();
		BaseModel bm = CacheManager.getCache(dbName, EnumCacheType.ECT_Staff).read1(staff2.getID(), BaseBO.SYSTEM, ecOut, dbName);
		Assert.assertTrue(bm != null, "普通缓存不存在创建出来的员工");
		// 2、检查数据库T_Staff,查看员工A是否正常创建。
		staff2.setIgnoreIDInComparision(true);
		staffClone.setIsFirstTimeLogin(1); // ... 创建成功的staff应当isFirsTimeLogin为1
		Assert.assertTrue(staff2.compareTo(staffClone) == 0, "DB不存在修改的员工");
		// 3、检查数据库T_StaffRole,查看是否创建了员工A关联的角色数据。
		StaffRole sr = new StaffRole();
		sr.setStaffID(staff2.getID());
		DataSourceContextHolder.setDbName(dbName);
		StaffRole retrieve1StaffRole = (StaffRole) staffRoleBO.retrieve1Object(BaseBO.SYSTEM, BaseBO.INVALID_CASE_ID, sr);
		if (staffRoleBO.getLastErrorCode() != EnumErrorCode.EC_NoError) {
			Assert.assertTrue(false, "查询一个员工角色失败,错误码=" + staffRoleBO.getLastErrorCode() + ",错误信息=" + staffRoleBO.getLastErrorMessage());
		}
		Assert.assertTrue(retrieve1StaffRole != null && staffClone.getRoleID() == retrieve1StaffRole.getRoleID(), "DB不存在创建出来的员工");
	}
	……

使用net.sf.json.JSONObject来解析获取返回信息:

		JSONObject object = JSONObject.fromObject(mr.getResponse().getContentAsString());

ModelTest主要是用来测试Model类的checkCRUD方法。

用StaffTest为例:

public class StaffTest extends BaseTestNGSpringContextTest {

	@BeforeClass
	public void setUp() {
		Shared.printTestClassStartInfo();
	}

	@AfterClass
	public void tearDown() {
		Shared.printTestClassEndInfo();
	}

	@Test
	public void checkCreate() throws ParseException {
		Shared.printTestMethodStartInfo();

		// SimpleDateFormat sdf4 = new
		// SimpleDateFormat(BaseAction.DATETIME_FORMAT_Default4);
		// SimpleDateFormat sdf = new
		// SimpleDateFormat(BaseAction.DATETIME_FORMAT_Default_Chinese);
		Staff s = new Staff();
		s.setPhone("12345678910");
		s.setName("收银员");
		s.setICID("44152219940925821X");
		s.setWeChat("weixin");
		// s.setPasswordExpireDate(sdf4.parse("2019/12/30 23:59:59"));
		s.setSalt("12345678901234567890123456789012");
		s.setIsFirstTimeLogin(1);
		s.setShopID(1);
		s.setDepartmentID(1);
		s.setRoleID(1);
		s.setStatus(0);
		s.setReturnSalt(Staff.RETURN_SALT);
		s.setNewPassword("123456");
		String err = s.checkCreate(BaseBO.INVALID_CASE_ID);
		Assert.assertEquals(err, "");
		//
		Shared.caseLog("测试phone");
		s.setPhone("12345678910aa");
		err = s.checkCreate(BaseBO.INVALID_CASE_ID);
		Assert.assertEquals(err, Staff.FIELD_ERROR_phone);
		s.setPhone("");
		err = s.checkCreate(BaseBO.INVALID_CASE_ID);
		Assert.assertEquals(err, Staff.FIELD_ERROR_phone);
		s.setPhone("12345678910");
		…… 

Staff的checkCreate方法:

	@Override
	public String checkCreate(int iUseCaseID) {
		StringBuilder sbError = new StringBuilder();
		switch (iUseCaseID) {
		// ...以下代码待简化
		case BaseBO.CASE_SpecialResultVerification:
			if (printCheckField(field.getFIELD_NAME_name(), FIELD_ERROR_name, sbError) && !FieldFormat.checkHumanName(name)) {
				return sbError.toString();
			}

			if (printCheckField(field.getFIELD_NAME_phone(), FIELD_ERROR_phone, sbError) && !FieldFormat.checkMobile(phone)) {
				return sbError.toString();
			}
			//
			if (printCheckField(field.getFIELD_NAME_ICID(), FIELD_ERROR_ICID, sbError) && !StringUtils.isEmpty(ICID) && !FieldFormat.checkICID(ICID)) {
				return sbError.toString();
			}
			//
			if (printCheckField(field.getFIELD_NAME_weChat(), FIELD_ERROR_weChat, sbError) && !StringUtils.isEmpty(weChat) && !FieldFormat.checkWeChat(weChat)) {// 接受空值
				return sbError.toString();
			}
			//
			SimpleDateFormat sdf = new SimpleDateFormat(BaseAction.DATETIME_FORMAT_Default3);
			try {
				passwordExpireDate = sdf.parse("9999/12/30 23:59:59");
			} catch (ParseException e) {
				e.printStackTrace();
			}
			……

7、SITTest(集成测试)

SIT集成测试,联合多个单元进行测试功能的稳定性:

/** C 创建一个单品商品A <br />
	 * U 对商品A添加2个副单位2,3 (2,3)<br />
	 * U 对商品A删除副单位3,增加副单位4,5 (2,4,5)<br />
	 * U 对商品A修改副单位2的倍数,4修改条码,5修改零售价 (2,4,5)<br />
	 * R1 RN<br />
	 * D 删除单品A(检查多包装商品是否删除)<br />
	 * D 删除一个被使用中的商品<br />
	 */
	@Test
	public void test() throws Exception {
		Shared.printTestMethodStartInfo();

		Shared.resetPOS(mvc, 1);
		HttpSession session = Shared.getPosLoginSession(mvc, 1);

		System.out.println("------------------创建商品A------------------------");

		Commodity commA = BaseCommodityTest.DataInput.getCommodity();
		commA.setOperatorStaffID(staff.getID());
		;
		Map<String, Object> params = commA.getCreateParamEx(BaseBO.INVALID_CASE_ID, commA);

		DataSourceContextHolder.setDbName(Shared.DBName_Test);
		List<List<BaseModel>> bmList = commodityMapper.createSimpleEx(params);
		//
		Assert.assertTrue(bmList != null && EnumErrorCode.values()[Integer.parseInt(params.get(BaseAction.SP_OUT_PARAM_iErrorCode).toString())] == EnumErrorCode.EC_NoError, "创建对象成功");
		//
		Commodity commCreated = (Commodity) bmList.get(0).get(0);

		commA.setIgnoreIDInComparision(true);
		if (commA.compareTo(commCreated) != 0) {
			Assert.assertTrue(false, "创建的对象的字段与DB读出的不相等");
		}

		Barcodes b = new Barcodes();
		b.setCommodityID(commCreated.getID());
		b.setBarcode(barcode1);
		b.setOperatorStaffID(staff.getID());
		barcodesBO.createObject(staff.getID(), BaseBO.INVALID_CASE_ID, b);

		if (barcodesBO.getLastErrorCode() != EnumErrorCode.EC_NoError) {
			assertTrue(false);
		}

		System.out.println("------------------对商品A添加2个副单位2,3 ------------------------");
		commCreated.setMultiPackagingInfo(barcode1 + "," + barcode2 + "," + barcode3 + ";" //
				+ packageUnit1 + "," + packageUnit2 + "," + packageUnit3 + ";" //
				+ refCommodityMultiple1 + "," + refCommodityMultiple2 + "," + refCommodityMultiple3 + ";" //
				+ priceRetail1 + "," + priceRetail2 + "," + priceRetail3 + ";" //
				+ priceVIP1 + "," + priceVIP2 + "," + priceVIP3 + ";" //
				+ priceWholesale1 + "," + priceWholesale2 + "," + priceWholesale3 + ";" //
				+ name1 + "," + name2 + "," + name3 + ";");
		commCreated.setProviderIDs("7,2,3");
		MvcResult mrUpdate1 = mvc.perform(//
				BaseCommodityTest.DataInput.getBuilder("/commoditySync/updateEx.bx", MediaType.APPLICATION_JSON, commCreated, session))//
				.andExpect(status().isOk()).andDo(print()).andReturn(); //
		Shared.checkJSONErrorCode(mrUpdate1);

		DataSourceContextHolder.setDbName(Shared.DBName_Test);
		List<?> commList = commodityBO.retrieveNObject(BaseBO.SYSTEM, BaseBO.CASE_RetrieveNMultiPackageCommodity, commCreated);

		assertTrue(commList.size() == 2);// ...测试结果验证不足

		System.out.println("------------------对商品A删除副单位3,增加副单位4,5 ------------------------");

		commCreated.setMultiPackagingInfo(barcode1 + "," + barcode2 + "," + barcode4 + "," + barcode5 + ";" //
				+ packageUnit1 + "," + packageUnit2 + "," + packageUnit4 + "," + packageUnit5 + ";" //
				+ refCommodityMultiple1 + "," + refCommodityMultiple2 + "," + refCommodityMultiple4 + "," + refCommodityMultiple5 + ";" //
				+ priceRetail1 + "," + priceRetail2 + "," + priceRetail4 + "," + priceRetail5 + ";" //
				+ priceVIP1 + "," + priceVIP2 + "," + priceVIP4 + "," + priceVIP5 + ";" //
				+ priceWholesale1 + "," + priceWholesale2 + "," + priceWholesale4 + "," + priceWholesale5 + ";" + name1 + "," + name2 + "," + name4 + "," + name5 + ";");
		commCreated.setProviderIDs("7");
		MvcResult mrUpdate2 = mvc.perform(BaseCommodityTest.DataInput.getBuilder("/commoditySync/updateEx.bx", MediaType.APPLICATION_JSON, commCreated, session)) //
				.andExpect(status().isOk()).andDo(print()).andReturn(); //
		Shared.checkJSONErrorCode(mrUpdate2);

		DataSourceContextHolder.setDbName(Shared.DBName_Test);
		commList = commodityBO.retrieveNObject(BaseBO.SYSTEM, BaseBO.CASE_RetrieveNMultiPackageCommodity, commCreated);
		assertTrue(commList.size() == 3);// ...测试结果验证不足

		System.out.println("------------------对商品A修改副单位2的倍数,4修改条码,5修改零售价 ------------------------");

		commCreated.setMultiPackagingInfo(barcode1 + "," + barcode2 + "," + "45454" + System.currentTimeMillis() % 1000000 + "," + barcode5 + ";" //
				+ packageUnit1 + "," + packageUnit2 + "," + packageUnit4 + "," + packageUnit5 + ";" //
				+ refCommodityMultiple1 + "," + "1" + System.currentTimeMillis() % 10 + "," + refCommodityMultiple4 + "," + refCommodityMultiple5 + ";" //
				+ priceRetail1 + "," + priceRetail2 + "," + priceRetail4 + "," + "1" + System.currentTimeMillis() % 100000 + ";" //
				+ priceVIP1 + "," + priceVIP2 + "," + priceVIP4 + "," + priceVIP5 + ";" //
				+ priceWholesale1 + "," + priceWholesale2 + "," + priceWholesale4 + "," + priceWholesale5 + ";" + "修改1" + name1 + "," + "修改2" + name2 + "," + "修改4" + name4 + "," + "修改5" + name5 + ";");
		commCreated.setProviderIDs("7");
		MvcResult mrUpdate3 = mvc.perform(BaseCommodityTest.DataInput.getBuilder("/commoditySync/updateEx.bx", MediaType.APPLICATION_JSON, commCreated, session)) //
				.andExpect(status().isOk()).andDo(print()).andReturn(); //
		Shared.checkJSONErrorCode(mrUpdate3);

		DataSourceContextHolder.setDbName(Shared.DBName_Test);
		commList = commodityBO.retrieveNObject(BaseBO.SYSTEM, BaseBO.CASE_RetrieveNMultiPackageCommodity, commCreated);
		assertTrue(commList.size() == 3);// ...测试结果验证不足

		System.out.println("------------------D 删除单品A(检查多包装商品是否删除) ------------------------");
		// 删除掉创建出来的商品
		// 查询商品的创建商品的多包装商品
		Map<String, Object> params2 = commCreated.getRetrieveNParam(BaseBO.CASE_RetrieveNMultiPackageCommodity, commCreated);
		DataSourceContextHolder.setDbName(Shared.DBName_Test);
		List<BaseModel> retrieveNMultiPackageCommodity = commodityMapper.retrieveNMultiPackageCommodity(params2);
		Assert.assertTrue(EnumErrorCode.values()[Integer.parseInt(params.get(BaseAction.SP_OUT_PARAM_iErrorCode).toString())] == EnumErrorCode.EC_NoError, params.get(BaseAction.SP_OUT_PARAM_sErrorMsg).toString());
		Assert.assertTrue(retrieveNMultiPackageCommodity.size() != 0, "查询不到数据");
		// 删除创建商品的多包装商品
		for (int i = 0; i < retrieveNMultiPackageCommodity.size(); i++) {
			Commodity reCommodity = (Commodity) retrieveNMultiPackageCommodity.get(i);
			MvcResult mr2 = mvc.perform(//
					get("/commoditySync/deleteEx.bx?ID=" + reCommodity.getID() + "&type=" + reCommodity.getType()) //
							.contentType(MediaType.APPLICATION_JSON) //
							.session((MockHttpSession) session)) //
					.andExpect(status().isOk()) //
					.andDo(print()) //
					.andReturn();

			Shared.checkJSONErrorCode(mr2);
		}

		MvcResult mrDelete1 = mvc.perform( //
				get("/commoditySync/deleteEx.bx?ID=" + commCreated.getID()) //
						.session((MockHttpSession) Shared.getStaffLoginSession(mvc, Shared.PhoneOfBoss)) //
						.contentType(MediaType.APPLICATION_JSON) //
		) //
				.andExpect(status().isOk()) //
				.andDo(print()) //
				.andReturn(); //

		Shared.checkJSONErrorCode(mrDelete1);// ...测试结果验证不足。应该用R1检查多包装商品是否存在,commCreated.getID()代表的商品是否也已被删除

		Map<String, Object> paramsR1 = commA.getRetrieve1Param(BaseBO.CASE_Commodity_RetrieveInventory, commCreated);

		DataSourceContextHolder.setDbName(Shared.DBName_Test);
		Commodity commR1 = (Commodity) commodityMapper.retrieveInventory(paramsR1);
		assertTrue(commR1 == null);

	}

8、syncSITTest(同步集成测试)。

同步SIT集成自动化测试,模拟多个Pos客户端同步服务器中的数据。所有Pos客户端都同步完成,验证服务器中的数据是否正常。

同步类的测试方法:

	/** 1.pos机1创建1个Commodity,pos机2创建2个Commodity,然后分别做同步更新。
	 * 2.pos3,4,5等待pos1,2更新完后,开启同步器 3.当所有pos机都已经同步完成后,删除DB和同存相关的数据 */
	@Test(timeOut = 60000)
	public void runCommoditySyncProcess() throws Exception {
		Shared.printTestMethodStartInfo();

		runSITTest1();
	}

	protected boolean createObject1() throws Exception {
		Commodity c = new Commodity();
		MvcResult pos1CreateCommodityA = mvc.perform(post("/commoditySync/createEx.bx")//
				.contentType(MediaType.APPLICATION_JSON)//
				.session((MockHttpSession) getLoginSession(1))//
		……

         BaseSyncSITTest1的runSITTest1()方法:

/** 1.pos机1创建1个Object,pos机2创建2个Object,然后分别做同步更新。 2.pos3,4,5等待pos1,2更新完后,开启同步器
	 * 3.当所有pos机都已经同步完成后,删除DB和同存相关的数据 */
	@SuppressWarnings("unchecked")
	protected void runSITTest1() throws Exception {
		Shared.printTestMethodStartInfo();

		// 先清空同存和DB中已经存在的同存DB数据,否则可能影响以下测试
		deleteSyncCacheInDBAndMemory();

		// 1、开启pos1 和 pos2的同步器
		BaseSyncSITTestThread1 t1 = getThread(mvc, getLoginSession(1), 1, 2, 3);
		BaseSyncSITTestThread1 t2 = getThread(mvc, getLoginSession(2), 1, 2, 3);
		t1.setName("POS1");
		t2.setName("POS2");
		t1.start();
		t2.start();
		t1.setDbName(Shared.DBName_Test);
		t2.setDbName(Shared.DBName_Test);

		DataSourceContextHolder.setDbName(Shared.DBName_Test);
		assertTrue(createObject1());

		DataSourceContextHolder.setDbName(Shared.DBName_Test);
		assertTrue(createObject2AndObject3());

		t1.join();
		t2.join();

		// 4、检查普存和同存是否正确,这时的同存应该有2 Poses X 3 Objects =
		// 6条数据,同存DB的主表应该有3条数据,代表3个Object块未给其它POS同步
		List<BaseModel> ssInfoList = SyncCacheManager.getCache(Shared.DBName_Test, getSyncCacheType()).readN(false, false);

		slaveSyncBlockNO = 0;
		for (BaseModel bm : ssInfoList) {
			slaveSyncBlockNO += bm.getListSlave1().size();
		}

		assertTrue(slaveSyncBlockNO == 2 * 3, "同存应该有2 Poses X 3 Vips = 6条数据!");

		System.out.println("pos1 和 pos2 同步完成!");

		if (getSyncActionDeleteExURL().length() > 0) {
		…… 

BaseSyncSITTestThread1的run方法:

	@Override
	public void run() {
		doRun();
	}

	public void doRun() {
		while (true) {
			try {
				System.out.println("该线程名称:" + this.getName());
				Thread.sleep(1000);// 每1秒运行一次
				MvcResult mr = mvc.perform(get(getSyncActionRetrieveNExURL()).contentType(MediaType.APPLICATION_JSON).session((MockHttpSession) session)).andExpect(status().isOk()).andDo(print()).andReturn();
				Shared.checkJSONErrorCode(mr);
				// 获取到返回objectID和ErrorCode
				String json = mr.getResponse().getContentAsString();
				JSONObject o = JSONObject.fromObject(json);
				String errorCode = JsonPath.read(o, "$.ERROR");
				List<?> bmList = JsonPath.read(o, "$.objectList[*].ID");
				bmList = bmList.stream().distinct().collect(Collectors.toList());// 去除重复

				String ids = "";
				for (Object bmID : bmList) {
					ids += bmID + ",";
				}

				mvc.perform(get(getSyncActionFeedbackExURL(ids, errorCode)).contentType(MediaType.APPLICATION_JSON).session((MockHttpSession) session)).andExpect(status().isOk()).andDo(print()).andReturn();
				slaveSyncBlockNO = 0;

				List<BaseModel> bmList1 = SyncCacheManager.getCache(Shared.DBName_Test, getSyncCacheType()).readN(false, false);
				for (BaseModel bm : bmList1) {
					slaveSyncBlockNO += bm.getListSlave1().size();
				}

				if (iPhase == 1) {
					if (slaveSyncBlockNO == iPosNO * iSyncBlockNO) {
						getNumberSynchronized().incrementAndGet();// 当前所有POS机已经同步所有块,结束同步
						break;
					}
				} else if (iPhase == 2) {
					if (slaveSyncBlockNO == iPosNO * iSyncBlockNO) {
						getNumberSynchronized().incrementAndGet();// 当前所有POS机已经同步所有块,结束同步
						break;
					}
				} else if (iPhase == 3) {
					if (getNumberSynchronized().get() > 0 && bmList1.size() == 0) {// 有POS已经同步,但是同步块又被清空了,证明所有POS都已经同步了块,块才被清空的,同步结束
						break;
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

9、TestNG.xml(测试类分组运行)。

使用TestNG.xml可以将测试进行分组运行:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="NbrUnitTest">
	<test name="Commodity">
		<classes>
			<class name="com.bx.erp.test.JenkinsUtilTest" />
			<class name="com.bx.erp.model.commodity.CommodityPropertyTest" />
			<class name="com.bx.erp.model.commodity.CommodityHistoryTest" />
			
			<class name="com.bx.erp.test.JenkinsUtilTest" />
			<class name="com.bx.erp.test.commodity.BrandActionTest" />
			<class name="com.bx.erp.test.commodity.BrandSyncActionTest" />
			<class name="com.bx.erp.test.commodity.BrandMapperTest" />
			<class name="com.bx.erp.test.commodity.BrandSyncCacheDispatcherMapperTest" />
			<class name="com.bx.erp.test.commodity.BrandSyncCacheMapperTest" />
			
			<class name="com.bx.erp.test.JenkinsUtilTest" />
			<class name="com.bx.erp.test.commodity.CategoryActionTest" />
			<class name="com.bx.erp.test.commodity.CategoryMapperTest" />
			<class name="com.bx.erp.test.commodity.CategoryParentActionTest" />
			<class name="com.bx.erp.test.commodity.CategoryParentMapperTest" />
			<class name="com.bx.erp.test.commodity.CategorySyncActionTest" />
			<class name="com.bx.erp.test.commodity.CategorySyncCacheDispatcherMapperTest" />
			<class name="com.bx.erp.test.commodity.CategorySyncCacheMapperTest" />

			……

10、测试文档

使用测试文档来管理测试用例,方便维护:

测试文档

测试用例


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM