Java自动化环境搭建笔记(1)

自动化测试

先搭建java接口测试的环境:

使用mvn命令构建项目

测试集通过testNG.xml组织并运行

测试数据解耦,通过Excel等文件提供

基础依赖

创建maven项目(包含一些基础的插件,见pom.xml)

引入testNG测试框架

引入allure报告框架

其他日志等依赖引入

测试依赖待开发

测试基类(统一数据提供方法)

Excel读取工具类

注解与监听类

1. 基础环境

java版本:1.8.0_191

IDEA

maven:Apache Maven 3.6.0

allure2:2.10.0

allure安装

随项目一起放到项目根目录.allure目录,可以参考官方给出的项目示例:参考地址

allure2可以直接去官方github下载:下载地址

2. 项目结构

项目目录

Java自动化实现 java做自动化_初始化

3. 代码开发

3.1 Excel读取

Excel读取直接按照规定的方法读取数据:

未指定sheet,默认读取Sheet1。

指定Sheet名称读取到无空白标题或空白行隔断的所有数据。

指定Sheet名称与locate(一个定位器,也就是一个cell的值),从这个cell开始读取到无空白标题或空白行隔断的所有数据。

指定Name读取Name区域的所有数据。name就是Excel的名称,需要工作簿范围的名称

所有读取方式都会默认跳过定位到的区域前两行。如:name定位到第2行第2列开始,会从第2行为可备注等随意填写数据区域,第3行为列名,第四行开始为数据。

name定义方法:

选中区域,鼠标右键定义名称,

公式 -> 名称管理器 -> 新建

定义的名称可左上角点击直接跳到指定区域,或通过名称管理器

查看名称

默认Sheet1

Java自动化实现 java做自动化_初始化_02

指定Sheet读取

Java自动化实现 java做自动化_java_03

指定name读取

Java自动化实现 java做自动化_数据_04

指定locate读取

package per.hao.utils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ExcelReader implements Iterator{
private static final Logger logger = LoggerFactory.getLogger(ExcelReader.class);
private static final char[] LETTER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private InputStream in = null;
private Workbook workbook = null;
private Sheet sheet = null;
/* 当前行id 列id */
private int curRowNum = 1;
private int curColNum = 0;
/* 当前工作表中可读最大行 */
private int maxRowNum = 0;
private int maxColNum = 0;
/* 读取行列数量记录 */
private int readRowNum = 0;
private int readColNum = 0;
/* 列名记录 */
List colNames = new ArrayList<>();
/**
* 根据sheetName初始化
*
* @param filePath excel文件路径
* @param sheetName 读取的sheet名称
* */
public static ExcelReader getDataBySheetName(String filePath, String sheetName){
return new ExcelReader(filePath, sheetName, "", "", 1);
}
/**
* 根据定位器初始化
*
* @param filePath excel文件路径
* @param sheetName 读取的sheet名称
* @param locate 定位器名称
* */
public static ExcelReader getDataByLocate(String filePath, String sheetName, String locate){
return new ExcelReader(filePath, sheetName, locate, "", 2);
}
/**
* 根据名称初始化
*
* @param filePath excel文件路径
* @param Name 名称
* */
public static ExcelReader getDataByName(String filePath, String Name){
return new ExcelReader(filePath, "", "", Name, 3);
}
/**
* 私有构造方法
*
* @param filePath excel文件路径
* @param sheetName 读取的sheet名称
* @param locate 定位器名称
* @param name 名称
* @param type 初始化的类型
*
* 根据type的值来确定初始化的方法
* 1 : 根据sheetName初始化, filePath、sheetName不为空
* 2 : 根据定位器初始化, filePath、sheetName、 locate不为空
* 3 : 根据名称初始化, name不为空
* */
private ExcelReader(String filePath, String sheetName, String locate, String name, int type){
initWorkBook(filePath);
if (type == 1) {// 根据sheetName初始化
initSheet(filePath, sheetName);
initParam();
ininColumnName();
} else if (type == 2) {// 根据定位器初始化
initSheet(filePath, sheetName);
initParam();
registerLocate(locate);
ininColumnName();
} else if (type == 3) {// 根据名称初始化
initByName(filePath, name);
}
}
/**
* 根据名称初始化当前读取区域定位信息
*
* @param filePath excel路径
* @param designation 名称
*
* 读取到的名称需要是工作簿作用范围,匹配定位信息初始化curColNum、 curRowNum、maxColNum、maxRowNum
* */
private void initByName(String filePath, String designation){
Name name = workbook.getName(designation);
if (name != null) {
initSheet(filePath, name.getSheetName());
/* 根据excel定位字符串初始化名称区域 */
Matcher matcher = Pattern.compile("^.*?\\$(\\w+)\\$(\\d+):\\$(\\w+)\\$(\\d+)$")
.matcher(name.getRefersToFormula());
if (matcher.find()) {
curColNum = letterToDec(matcher.group(1));
curRowNum = Integer.parseInt(matcher.group(2));
maxColNum = letterToDec(matcher.group(3));
maxRowNum = Integer.parseInt(matcher.group(4));
} else {
logger.error("cannot find coordinate: {}", name.getRefersToFormula());
}
ininColumnName();
} else {
logger.error("cannot find name: {}", designation);
}
}
/**
* 将Excel列定位字母转换为数字
* 从0开始,A -> 0, AA -> 26, BA -> 53以此类推, 也就是特殊的26进制
*
* @param letterCoordinate 列
*
* @return int
* */
private int letterToDec(String letterCoordinate){
char[] cs = letterCoordinate.toCharArray();
int decIndex = 0, i = 0;
for (; i < cs.length; i++) {
decIndex += Math.pow(26, (cs.length - i - 1)) * (Arrays.binarySearch(LETTER, cs[i]) + 1);
}
return decIndex - 1;// 从0开始 减1
}
/**
* 初始化列名
* */
private void ininColumnName(){
Row colNameRow = sheet.getRow(curRowNum);
/* 遇到空的cell读取结束 */
for (int i = 0; i < colNameRow.getLastCellNum(); i++) {
String cellContents = getCellContents(colNameRow.getCell(i));
if (!"".equals(cellContents)) {
colNames.add(cellContents);
} else {
logger.debug("Read to blank cell default read column end");
break;
}
}
curRowNum ++;
}
/**
* 根据定位器初始化当前行
*
* @param locate 定位器
* */
private void registerLocate(String locate){
/* 至少存在1列 */
if (maxColNum >= 1) {
boolean flag = false;// 默认无定位器
for (int i = 0; i < sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) { continue; }
Cell cell = row.getCell(0);
/* 找到定位器,初始化当前读取位置 */
if (locate.equals(getCellContents(cell))) {
flag = true;
curRowNum = cell.getAddress().getRow() + 1;// 从定位器下一行开始
}
if (flag) {
logger.info("find locate point, read from next line");
} else {
logger.info("cannot find locate point, read from default line");
}
}
}
}
/**
* 初始化部分变量
*
* */
private void initParam(){
maxRowNum = sheet.getLastRowNum() + 1;
maxColNum = sheet.getLastRowNum();
readColNum = colNames.size();
}
/**
* 根据Sheet名称获取Sheet
*
* @param sheetName sheet名称
* */
private void initSheet(String filePath, String sheetName){
/* 获取WorkBook 中的sheet */
if (workbook != null) {
sheet = workbook.getSheet(sheetName);
} else {
logger.error("WorkBook Object dose not exist!");
}
/* sheet未获取到 */
if (sheetName == null) {
logger.error("In {} sheet does not exist", filePath);
} else {
logger.info("read sheet: {}", sheetName);
}
}
/**
* 根据后缀判断版本获取excel对象
*
* @param filePath 文件路径
* */
private void initWorkBook(String filePath){
try {
in = new BufferedInputStream(new FileInputStream(filePath));
/* 2003 Excel */
if (filePath.endsWith(".xls")) {
workbook = new HSSFWorkbook(in);
/* 2007 Excel */
} else if (filePath.endsWith(".xlsx")) {
workbook = new XSSFWorkbook(in);
} else {
logger.error("File is not Excel File: {}", filePath);
}
} catch (FileNotFoundException e) {
logger.error("File not found: ", e);
} catch (IOException e) {
logger.error("Create WorkBook failed: ", e);
}
// 设置默认返回空串
workbook.setMissingCellPolicy(Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
logger.info("read excel: {}", filePath);
}
/**
* 判断是否为空行
* */
private boolean judgeblankRow(Row row){
for (int i = 0; i < row.getLastCellNum(); i++) {
Cell cell = row.getCell(i);
/* 存在单元格不为空字符串则为非空行 */
if (!"".equals(getCellContents(cell))) {
return false;
}
}
return true;
}
/**
* 关闭资源
* */
private void close(){
if (in != null) {
try {
in.close();
} catch (IOException e) {
logger.error("close file filed", e);
}
}
}
/**
* 判断是否存在下一行
*
* 如果下一行为空行 @return true
* 如果下一行为非空行 @return false
* */
@Override
public boolean hasNext(){
if (curRowNum < maxRowNum) {
Row curRow = sheet.getRow(curRowNum);
if (judgeblankRow(curRow)) {
return false;
}
return true;
}
return false;
}
/**
* 获取下一行数据
*
* @return Object[]数组对象
* */
@Override
public Object[] next() {
Map curRowData = new HashMap<>();
Row curRow = sheet.getRow(curRowNum);
/* 根据列名取对应列数据 */
for (int i = curColNum; i < colNames.size(); i++) {
Cell cell = curRow.getCell(i);
curRowData.put(colNames.get(i), getCellContents(cell));
}
readRowNum ++;
curRowNum ++;
return new Object[] { curRowData };
}
/**
* 以String类型返回值
*
* */
private String getCellContents(Cell cell){
cell.setCellType(CellType.STRING);
return cell.getStringCellValue();
}
@Override
public void remove(){
logger.error("not support remove");
}
}
3.2 数据提供与监听器
testNG提供了DataProvider注解来指定数据提供接口,这里定义一个BaseTester类添加一个公共的getData方法作为公共的数据提供接口。
读取数据需要指定dataProvider="名称",使用监听器将dataProvider指定为默认的。
定义自己的DataSource注解,定义文件路径、sheetName等参数,在getData方法被调用的时候解释DataSource注解内参数来去读指定数据。
3.2.1 BaseTester测试基类
在测试类中extends BaseTester
package per.hao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.DataProvider;
import per.hao.annotations.DataSource;
import per.hao.utils.DataSourceType;
import per.hao.utils.ExcelReader;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.regex.Matcher;
public class BaseTester{
public static final Logger logger =
LoggerFactory.getLogger(BaseTester.class);
/**
* 数据提供公共接口
* */
@DataProvider(name = "getData")
public static Iterator getData(Method method) {
DataSource dataSource = null;
/** 数据源注解存在判断 */
if (method.isAnnotationPresent(DataSource.class)) {
dataSource = method.getAnnotation(DataSource.class);
} else {
logger.error("未指定@DataSource注解却初始化了dataProvider");
}
/** 根据数据源类型返回对应数据迭代器 */
if (DataSourceType.CSV
.equals(dataSource.dataSourceType())) {
// CSVReader
} else if (DataSourceType.POSTGRESQL
.equals(dataSource.dataSourceType())) {
// PostgresqlReader
}
/* 默认读取excel */
// 根据名称
if (!"".equals(dataSource.name())) {
return ExcelReader.getDataByName(
dealFilePath(dataSource.filePath()), dataSource.name());
// 根据锚点
} else if (!"".equals(dataSource.locate())) {
return ExcelReader.getDataByLocate(
dealFilePath(dataSource.filePath()), dataSource.sheetName(), dataSource.locate());
// 读取整个sheet页
} else {
return ExcelReader.getDataBySheetName(
dealFilePath(dataSource.filePath()), dataSource.sheetName());
}
}
/**
* 如果只存在文件名,则拼接默认读取目录,否则使用指定的路径
*
* @param filePath 文件路径
* */
private static String dealFilePath(String filePath){
if (!filePath.matches(".*[/\\\\].*")) {
filePath = "src/test/resources/data/" + filePath;
}
return new File(filePath.replaceAll("[/\\\\]+",
Matcher.quoteReplacement(File.separator))).getAbsolutePath();
}
}

3.2.2 DataSourceListener监听器

DataSourceListener
package per.hao.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;
import per.hao.BaseTester;
import per.hao.annotations.DataSource;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* 监听测试是否存在{@code DataSource.CLASS}注解:
* 1. 如果存在@DataSource注解且@Test注解中未对dataProvider赋值
* 则指定{@code BaseTester.CLASS}中提供的getData数据提供者方法
* 2. 如果不存在@DataSource注解或@Test注解中已经对dataProvider赋值
* 则不修改dataProvider
*
* 测试方法@Test注解dataProviderClass如果值为Object.class则修改
* 为BaseTest.class
* */
public class DataSourceListener implements IAnnotationTransformer{
private static final Logger logger = LoggerFactory.getLogger(DataSourceListener.class);
/**
* 本监听的入口,监听每次测试方法的调用,并设置相应参数
*
* @param iTestAnnotation 提供对@Test注解操作的对象
* @param aClass
* @param constructor
* @param method 本次触发监听的测试方法
* */
@Override
public void transform(ITestAnnotation iTestAnnotation, Class aClass, Constructor constructor, Method method){
// 测试为null
if (iTestAnnotation == null || method == null) {
return;
}
/** 判断并修改@Test注解 dataProvider 值 */
modifyDataProvider(iTestAnnotation, method);
/** 判断并修改@Test注解 dataProviderClass 值 */
if (iTestAnnotation.getDataProviderClass() == null) {
iTestAnnotation.setDataProviderClass(BaseTester.class);
logger.debug("dataProviderClass设置为: {}", BaseTester.class);
} else {
logger.debug("dataProviderClass已经指定: {}", iTestAnnotation.getDataProviderClass());
}
}
/**
* 对@Test注解dataProvider数据提者判断修改的方法
*
* @param iTestAnnotation 提供对@Test注解操作的对象
* @param method 本次触发监听的测试方法
* */
private void modifyDataProvider(ITestAnnotation iTestAnnotation, Method method){
/** 如果存在@DataSource注解 */
if (method.isAnnotationPresent(DataSource.class)) {
if ("".equals(iTestAnnotation.getDataProvider())) {
iTestAnnotation.setDataProvider("getData");
logger.debug("dataProvider设置为: getData");
} else {
logger.debug("dataProvider已指定: {}", iTestAnnotation.getDataProvider());
}
/* 未指定@DataSource注解却指定了dataProvider */
} else if ((! method.isAnnotationPresent(DataSource.class)) &&
! "".equals(iTestAnnotation.getDataProvider())) {
logger.error("未指定@DataSource注解却初始化了dataProvider");
}
}
}

将监听器配置到testNG.xml中

3.2.3 DataSource数据源注解

SQL与CSV方法目前没有写,先只写了Excel数据读取的工具类

package per.hao.annotations;
import per.hao.utils.DataSourceType;
import java.lang.annotation.*;
/**
* 标注参数化数据源, 默认从提供Excel读取
* */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
DataSourceType dataSourceType() default DataSourceType.EXCEL;
String filePath() default "";
String sheetName() default "Sheet1";// 除根据name外需要指定, 默认读取Sheet1
String locate() default "";// 定位器, 左上角第一个cell(ReadExcel用)
String name() default ""; // 可指定Excel名称读取
String sql() default "";
}

4. 配置

4.1 pom.xml
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
per.hao
selenium-project-hzhang
1.0.0
UTF-8
2.10.0
1.8
1.9.2
org.testng
testng
6.14.3
test
io.qameta.allure
allure-testng
${allure.version}
test
io.qameta.allure
allure-maven
${allure.version}
test
org.hamcrest
hamcrest-all
1.3
test
org.slf4j
slf4j-api
1.7.25
test
org.slf4j
slf4j-log4j12
1.7.25
test
org.apache.poi
poi
4.1.0
test
org.apache.poi
poi-ooxml
4.1.0
test
org.apache.maven.plugins
maven-surefire-plugin
2.22.1
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
true
src/test/resources/testng/testNG.xml
org.aspectj
aspectjweaver
${aspectj.version}
org.apache.maven.plugins
maven-compiler-plugin
3.8.0
1.8
1.8
utf-8
true
io.qameta.allure
allure-maven
${allure.version}
${allure.version}
4.2 allure.properties
allure.results.directory=target/allure-results
allure.link.issue.pattern=https://example.org/issue/{}
allure.link.tms.pattern=https://example.org/tms/{}
4.3 log4j.properties
log4j.rootLogger=CONSOLE,FILE
log4j.addivity.org.apache=false
# 应用于控制台
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=DEBUG
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.Encoding=UTF-8
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d - %c -%-4r [%t] %-5p - %m%n
# 每天新建日志
log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.File=E:/log4j/log
log4j.appender.A1.Encoding=UTF-8
log4j.appender.A1.Threshold=DEBUG
log4j.appender.A1.DatePattern='.'yyyy-MM-dd
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L : %m%n
#应用于文件
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.File=E:/log4j/file.log
log4j.appender.FILE.Threshold=DEBUG
log4j.appender.FILE.Append=true
log4j.appender.FILE.Encoding=UTF-8
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%d - %c -%-4r [%t] %-5p - %m%n
4.4 testNG.xml

5. 测试示例

5.1 SampleTest

package per.test;
import io.qameta.allure.*;
import org.testng.annotations.Test;
import per.hao.annotations.DataSource;
import java.util.Map;
import static io.qameta.allure.Allure.*;
public class SimpleTest extends DataSourceListener{
@Test(description = "测试默认读取Sheet1")// 这个desciption是标题
@Severity(SeverityLevel.CRITICAL)// 测试优先级
@DataSource(filePath = "param.xlsx")
@Description("在不指定Sheet名称的情况下,测试能否读取默认数据。")// 这个才是描述
@Step("第一步先测试一下能否读取Sheet1中的数据")// 父步骤
@Link(name = "默认读取Sheet1需求", url = "")// 需求
@Issue("")// BUG
public void simpleTest1(Map row){
step("step 1: 获取用例数据");
String caseName = row.get("用例名称");
step("step 2: 返回实际结果" + caseName);
}
@Test(description = "测试指定Sheet名称读取")
@DataSource(filePath = "param.xlsx", sheetName = "Sheet读取")
public void simpleTest2(Map row){
step("step 1: 获取用例数据");
String caseName = row.get("用例名称");
step("step 2: 返回实际结果" + caseName);
}
@Test(description = "测试指定名称读取")
@DataSource(filePath = "param.xlsx", name = "测试name读取")
public void simpleTest3(Map row){
step("step 1: 获取用例数据");
String caseName = row.get("用例名称");
step("step 2: 返回实际结果" + caseName);
}
@Test(description = "测试指定locate读取")
@DataSource(filePath = "param.xlsx", sheetName = "locate读取", locate = "I'm locate")
public void simpleTest4(Map row){
step("step 1: 获取用例数据");
String caseName = row.get("用例名称");
step("step 2: 返回实际结果" + caseName);
}
}

5.2 测试运行

项目根目录下打开命令行:

#1. 运行测试

mvn clean test site

#2. 生成报告

mvn io.qameta.allure:allure-maven:serve

5.3 报告示例

概览有地方没数据是因为没有历史记录,使用jenkins运行持续集成运行几次就有了。

Java自动化实现 java做自动化_初始化_05

概览

按测试集查看