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、測試文檔
使用測試文檔來管理測試用例,方便維護: