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、测试文档
使用测试文档来管理测试用例,方便维护: