关注小程序 找一找教程网-随时随地学编程

Java教程

[Java版]selenium关键字驱动框架设计实战(二)

[Java版]selenium关键字驱动框架设计实战(一)_职说测试-CSDN博客

关键字框架实现

前面已经带领大家认识了java反射,也演示了反射的调用示例,关键字的封装、更有关键字框架的代码实现;接下来就是对这个框架的具体实现进行封装。

读取Excel测试用例,需要用到poi工具包

<!-- excel读写工具包 -->
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>3.17</version>
    </dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>3.17</version>
    </dependency>

如何设计excel用例呢,首先1条用例是1个场景,实现场景的代码有很多,譬如:用户登录系统,进入某模块,发布1条消息。又要考虑一些场景:有些时候我并不想执行。这就是TestSuit和testcase的关系

看到上面的设计之后肯定又需要做进一步优化,因为在testsuit找到要执行的用例,需要去teststep中执行步骤,那么不是所有场景的用例都写在teststeps吧,那么变换一下,将testsuiteId作为teststep的sheet名

  • 上面的意思就是找到suite页runmode为yes的testsuiteId,再去找到testsuiteId的sheet页,执行里面的操作步骤;作为一次循环;
  • 先封装excel封装:读excel、获取sheet的行和列、读取关键的单元格数据,还有回写数据到excel指定sheet页的单元格
/**
 * 
 * TODO:操作excel
 *
 * @author Joe-Tester
 * @time 2021年8月27日
 * @file ExcelUtils.java
 */
public class ExcelUtils {

	public static HSSFSheet ExcelSheet;
	public static HSSFWorkbook ExcelBook;
	public static HSSFRow Row;
	public static HSSFCell Cell;

	/**
	 * 加载Excel
	 * 
	 * @param Path
	 *            :文件路径
	 */
	public static void setExcelFile(String Path) {
		FileInputStream ExcelFile;
		try {
			ExcelFile = new FileInputStream(Path);
			ExcelBook = new HSSFWorkbook(ExcelFile);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 结果回写excel
	 * 
	 * @param Result
	 *            :执行结果回写
	 * @param RowNum
	 *            :每次判断关键字的行号
	 * @param ColNum
	 *            : 固定的结果列
	 * @param Path
	 *            :文件路径
	 * @param SheetName
	 *            :sheet页
	 */
	public static void setCellData(String Result, int RowNum, int ColNum,
			String Path, String SheetName) {
		try {
			// 获取到excel的sheet表单
			ExcelSheet = ExcelBook.getSheet(SheetName);
			// 获取sheet表单的行数
			Row = ExcelSheet.getRow(RowNum);
			// 行+列确定单元格
			Cell = Row.getCell(ColNum, MissingCellPolicy.RETURN_BLANK_AS_NULL);
			// 结果写入,如果没有单元格则创建单元格
			if (Cell == null) {
				Cell = Row.createCell(ColNum);
				Cell.setCellValue(Result);
			} else {
				Cell.setCellValue(Result);
			}
			// 这个操作其实是重新创建了一个excel
			FileOutputStream fileOut = new FileOutputStream(Path);
			ExcelBook.write(fileOut);
			fileOut.flush();// 这个特别注意,需要刷新一下数据,相当于保存
			fileOut.close();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 获取excel表格数据
	 * 
	 * @param RowNum
	 * @param CloNum
	 * @param SheetName
	 * @return
	 */
	public static String getCellDate(int RowNum, int CloNum, String SheetName) {
		ExcelSheet = ExcelBook.getSheet(SheetName);
		Cell = ExcelSheet.getRow(RowNum).getCell(CloNum);
		// 万一单元格出现int类型,获取却不是String,先设置其单元格值为String;或者在单元格写入数字前单引号'
		Cell.setCellType(CellType.STRING);
		String cellData = Cell.getStringCellValue();
		return cellData;

	}

	/**
	 * 获取最后sheet最后一行
	 * 
	 * @param SheetName
	 * @return
	 */
	public static int getLastRowNums(String SheetName) {
		try {
			ExcelSheet = ExcelBook.getSheet(SheetName);
			int rowCount = ExcelSheet.getPhysicalNumberOfRows();
			// getLastRowNum()+1;//最后一行行标,比行数小1
			return rowCount;
		} catch (Exception e) {
			throw (e);
		}
	}

	/**
	 * 
	 * @param filePath
	 * @param sheetName
	 * @param picName
	 */
	public static void insertPicExcel(String filePath, String sheetName) {
		FileOutputStream fileOut = null;
		BufferedImage bufferImg = null;
		// 先把读进来的图片放到一个ByteArrayOutputStream中,以便产生ByteArray
		try {
			ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
			bufferImg = ImageIO.read(new File(filePath));
			ImageIO.write(bufferImg, "png", byteArrayOut);
			HSSFSheet testStepSheet = ExcelBook.getSheet(sheetName);
			// 画图的顶级管理器,一个sheet只能获取一个(一定要注意这点)
			HSSFPatriarch patriarch = testStepSheet.createDrawingPatriarch();
			// anchor主要用于设置图片的属性
			HSSFClientAnchor anchor = new HSSFClientAnchor(0, 0, 255, 255,
					(short) 1, 1, (short) 5, 8);
			anchor.setAnchorType(AnchorType.DONT_MOVE_AND_RESIZE); // setAnchorType(3)不支持这个数值了
			// 插入图片
			patriarch
					.createPicture(anchor, ExcelBook.addPicture(
							byteArrayOut.toByteArray(),
							HSSFWorkbook.PICTURE_TYPE_JPEG));
			fileOut = new FileOutputStream(Contants.excelFile
					+ Contants.excelName);
			// 写入excel文件
			ExcelBook.write(fileOut);

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (fileOut != null) {
				try {
					fileOut.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

 然后再写操作excel的逻辑

/**
 * 
 * TODO:这个类的处理逻辑是和Excel表格对应的,如果要更改Excel只需要改这个类,或者新增加一个和Excel对应的类
 *
 * @author Joe-Tester
 * @time 2021年8月27日
 * @file StartEngine.java
 */
public class StartEngine {

	public static String Keywords = null;
	public static String r;
	public static boolean bResult;

	public static void StartEngine(Object actionKeyWords) throws IOException {

		// 初始化结果状态
		bResult = true;
		// 打开excel用例文件
		ExcelUtils.setExcelFile(Contants.excelFile + Contants.excelName);
		// 获取执行场景总行数
		for (int j = 1; j < ExcelUtils.getLastRowNums(Contants.suitSheet); j++) {
			// 是否需要执行
			String caseStatus = ExcelUtils.getCellDate(j, Contants.suitRunmode,
					Contants.suitSheet);
			// 获取场景的id作为操作步骤的sheet
			String testStepsSheet = ExcelUtils.getCellDate(j,
					Contants.suitTestSuiteId, Contants.suitSheet);
			int row;
			// 遍历场景行数,判断是否需要执行
			if (caseStatus.equals("YES")) {
				Log.startTestCase("执行第" + j + "条用例:" + testStepsSheet);
				// 遍历操作步骤的关键字及操作元素
				// Log.info(suitTestSuiteId+",sheet页,最后一行的行号:"+ExcelUtils.getLastRowNums(suitTestSuiteId));
				for (row = 0; row < ExcelUtils.getLastRowNums(testStepsSheet); row++) {
					// 操作的关键字
					String Keywords = ExcelUtils.getCellDate(row,
							Contants.excelKWCloNum, testStepsSheet);
					// 操作的页面元素
					String r = ExcelUtils.getCellDate(row,
							Contants.excelPOCloNum, testStepsSheet);
					// 操作的值
					String v = ExcelUtils.getCellDate(row,
							Contants.excelVaCloNum, testStepsSheet);
					// 调用关键字,注意suitTestSuiteId作为用例步骤的sheet页签的名字
					Common_Engine.Action(Keywords, actionKeyWords, r, v, row,
							bResult, testStepsSheet);

					// 回写用例步骤页
					if (bResult == false) {
						ExcelUtils.setCellData(Contants.fail, j,
								Contants.suitResult, Contants.excelFile
										+ Contants.excelName,
								Contants.suitSheet);

						String destination = Screenshots.takeScreenshot(
								KeywordsDriven.driver, "error");

						ExcelUtils.insertPicExcel(destination, testStepsSheet);

					}
				}
				Log.endTestCase("执行第" + j + "条用例:" + testStepsSheet + "结束!!!");
				// 回写suit页
				if (bResult == true) {
					ExcelUtils.setCellData(Contants.pass, j,
							Contants.suitResult, Contants.excelFile
									+ Contants.excelName, Contants.suitSheet);
				}
			} else {
				Log.info("当前用例:" + testStepsSheet + ",执行状态为:" + caseStatus
						+ ",不执行!!!");
				// 如果出现YES-NO-YES的用例场景,在第一个NO之后的YES或者NO都不执行
				// break;
			}
		}
	}
}

最后完善Common_Engine部分的关键字调用方法,通过关键字的类对象来进行反射调用

/**
 * 
 * TODO:通用引擎,执行的是test steps关键字操作步骤,java反射获取关键字方法
 *
 * @author Joe-Tester
 * @time 2021年8月27日
 * @file Common_Engine.java
 */
public class Common_Engine {

	// 日志收集器
	// private static final Logger log =
	// LogManager.getLogger(Common_Engine.class.getName());

	/**
	 * 
	 * @param Keywords
	 *            :读取excel中关键字,判断是否在关键字对象中
	 * @param actionKeyWords
	 *            :关键字对象
	 * @param element
	 *            :关键操作对象的元素
	 * @param value
	 *            :输入值
	 * @param rowNum
	 *            : 操作步骤的行号
	 * @param bResult
	 *            : 结果
	 * @param SheetName
	 *            :执行场景步骤sheet页
	 */
	public static void Action(String Keywords, Object actionKeyWords,
			String element, String value, int rowNum, boolean bResult,
			String SheetName) {
		// java反射获取类对象的所有方法
		/* actionKeyWords= new Keywords_Driven(); */
		Method[] method = actionKeyWords.getClass().getMethods();
		// 遍历反射类对象
		for (int i = 0; i < method.length; i++) {
			// 判断获取对象的方法等于读取excel的关键字
			if (method[i].getName().trim().equals(Keywords)) {
				try {
					// invoke调用关键字方法,字符串参数
					method[i].invoke(actionKeyWords, element, value);
					// Log.info("调用关键字<" + Keywords + ">方法.");
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				// 如果执行关键字方法回写用例执行步骤excel
				if (bResult == true) {
					ExcelUtils.setCellData(Contants.pass, rowNum,
							Contants.testResult, Contants.excelFile
									+ Contants.excelName, SheetName);
				} else {
					ExcelUtils.setCellData(Contants.fail, rowNum,
							Contants.testResult, Contants.excelFile
									+ Contants.excelName, SheetName);
				}
				break;
			}
		}
	}
}

关键字的封装,按照excel的用例设计,每个关键字都只需要传入两个值,一个是操作元素,一个是值:function(locotor,value)

使用testng框架开始执行测试用例:

/**
 * 
 * TODO: 用例演示
 *
 * @author Joe-Tester
 * @time 2021年9月8日
 * @file testExamlpe.java
 */
public class testExamlpe {

	// 初始化关键字对象类
	public static Object actionKeyWords;
	/**
	 * 测试用例
	 * 
	 * @throws IOException
	 */
	@Test(groups = { "p0" })
	void KeywordsTestCase() throws IOException {
		actionKeyWords = new KeywordsDriven();
		StartEngine.StartEngine(actionKeyWords);
	}
}
  • 在这里testng框架在代码中可能没能提现出特别的优势,因为所有的用例都在一个excel里,所以也只需要一个测试方法;这对于testng来讲就是一个测试用例;
  • testng还有用法,就是写xml文件组织用例:
<?xml version="1.0" encoding="UTF-8"?>
<suite name="precodition">
    <!--platformName in  {web ,android ,ios }-->
    <!-- <parameter name="className" value="LoginKeyWords"/> -->
    <test name="Web_Tests">
        <groups>
            <define name="p0">
                <include name="p0" />
            </define>
            <run>
                <include name="p0" />
                <!--<exclude name="xxxx" />-->
            </run>
        </groups>
        <classes>
            <class name="selenium.keyword.testcase.testExamlpe"/>
        </classes>
    </test>
	<listeners>
		<!-- 添加ExtentRport监听 -->
		<listener class-name="selenium.keyword.extenreports.MyExtentTestNgListener" />
	</listeners>
</suite>
  • 注意xml中有一个监听器:listener,这部分代码无论是po模式或是PageFactory模式,都是同一份报告模板,只是在关键字框架设计中有些不足,断言不完善,无法截图在报告中体现。
  • 还有要强调日志收集,在执行过程中方便定位问题;引用了log4j,有两种用法,一种是在每个类单独使用,这样的好处log输出的位置是当前类的行,第二种是封装,这样的log输出的位置是封装后的行
// 第一种日志收集器
private static final Logger log = LogManager.getLogger(KeywordsDriven.class.getName());

// 第二种封装log4j
/**
 * 
 * TODO:封装日志工具
 *
 * @author Joe-Tester
 * @time 2021年9月10日
 * @file Log.java
 */
public class Log {

	private static Logger Log = Logger.getLogger(Log.class.getName());

	// This is to print log for the beginning of the test case, as we usually
	// run so many test cases as a test suite
	public static void startTestCase(String sTestCaseName) {

		Log.info("****************************************************************************************");
		Log.info("****************************************************************************************");
		Log.info("$$$$$$$$$$$$$$$$$$$$                 " + sTestCaseName
				+ "        $$$$$$$$$$$$$$$$$$$$");
		Log.info("****************************************************************************************");
		Log.info("****************************************************************************************");

	}

	// This is to print log for the ending of the test case
	public static void endTestCase(String sTestCaseName) {
		Log.info("$$$$$$$$$$$$$$$$$$$$            " + sTestCaseName
				+ "      $$$$$$$$$$$$$$$$$$$$");
		Log.info("****************************************************************************************\n");

	}

	// Need to create these methods, so that they can be called
	public static void info(String message) {
		Log.info(message);
	}

	public static void warn(String message) {
		Log.warn(message);
	}

	public static void error(String message) {
		Log.error(message);
	}

	public static void fatal(String message) {
		Log.fatal(message);
	}

	public static void debug(String message) {
		Log.debug(message);
	}

}
  • 执行用例,日志输出效果如下:

 testsuites执行结果:改名叫Scenarios场景,对应一个测试用例

总结 

Java版selenium工具实现关键字框架设计,从关键字封装到反射调用关键字、从Excel操作封装到Excel测试用例设计、从测试报告到日志收集器,都一一进行了代码演示;欢迎各位指正!