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