文章目录
前言
我们在《接口测试第三篇: 基于代码(Maven+TestNG+Rest-Assured)的接口自动化测试》里面介绍了如何基于TestNG和Rest-Assured做接口自动化测试。无论是TestNG还是Rest-Assured,都是我们在接口自动化测试里面会用到的工具。那构建自动化测试框架时,是不是就是把这些工具堆叠起来就行了?答案当然是否。一个好的自动化测试框架的结构是经过精心设计的,框架结构需要做到分层清晰,才能易于维护和扩展。今天就来手把手教大家从0构建一个高可维护性的接口自动化测试框架。
本文为大家展示了绝大部分框架里面的内容,大家可以按照文章中的内容自己构建一个测试框架。时间有限的同学,也可以直接下载我上传的自动化测试框架资源部署后进行测试哦(自动化测试框架中的被测接口为reqres.in 的范例测试接口,可以直接运行)
一、基础环境搭建
1.1 Java 开发环境安装(JDK)
Java 是框架运行的基础环境,需安装 Java Development Kit (JDK)(建议 JDK 11+)。以下是详细步骤:
- 下载 JDK
访问 Oracle JDK 下载页 或 OpenJDK 官网,选择与操作系统匹配的安装包(推荐 LTS 长期支持版本,如 JDK 21)。
Windows:下载.exe安装包,双击运行。 - 配置环境变量
安装完成后,需将 JDK 的bin目录添加到系统环境变量,以便命令行调用java和javac。
右键 “此电脑” → “属性” → “高级系统设置” → “环境变量”。
在 “系统变量” 中,新建JAVA_HOME,值为 JDK 安装路径(如C:\Program Files\Java\jdk-21)。
编辑Path变量,新增%JAVA_HOME%\bin。 - 验证安装
打开命令行,输入:
java -version
# 输出类似:java version "21.0.1" 2024-04-16
javac -version
# 输出类似:javac 21.0.1
1.2 Maven 安装与配置
Maven 用于管理项目依赖和构建,以下是安装步骤:
-
下载 Maven
访问 Maven 官网下载页,下载最新稳定版(如 3.9.7)的二进制压缩包:apache-maven-3.9.7-bin.zip。 -
解压与配置
将压缩包解压到自定义目录(如D:\maven\apache-maven-3.9.7)。 -
配置环境变量
新增系统变量MAVEN_HOME,值为 Maven 解压路径(如D:\maven\apache-maven-3.9.7)。
编辑Path变量,新增%MAVEN_HOME%\bin。 -
配置本地仓库(可选)
Maven 默认使用用户目录下的.m2/repository作为本地仓库,可通过修改settings.xml自定义路径:
进入 Maven 安装目录的conf文件夹,找到settings.xml。
在 < localRepository > 标签中设置路径(如 < localRepository > D:\maven\repo)。 -
验证安装
命令行输入:
mvn -v
#输出类似:Apache Maven 3.9.7 (1a9cc05b15923ec9e0168c2edf414b5354594c39)
1.3 安装IDEA
1.3.1 下载和安装IDEA
- 下载
访问 JetBrains 官方网站,根据需求选择社区版(免费,适合学习和小型项目)或旗舰版(功能全,适合商业开发),点击 “Download” 下载对应操作系统版本 - 安装
找到下载的.exe 安装文件,双击启动安装向导,按提示操作,可自定义安装路径,勾选相关选项(如创建桌面快捷方式等 )后点击 “Install”,安装完成点击 “Finish”。
1.3.2 在IDEA中配置Maven
在 IDEA 中配置 Maven:打开 IDEA,点击 “File” -> “Settings”(Windows/Linux )或 “IntelliJ IDEA” -> “Preferences”(Mac ),搜索 “Maven”。设置 “Maven home directory” 为 Maven 安装目录,“User settings file” 指向 Maven 安装目录下 conf 文件夹中的 settings.xml 文件
1.3.3 在IDEA中安装TestNG插件
在 “Plugins” 设置页面,搜索 “TestNG”,点击 “Install” 安装,安装完成重启 IDEA。
1.4 创建 Maven 项目
在IDEA中,点击File》New》Project,弹出如下窗口,创建Maven工程。
1.5 引入所有工具相关依赖
在pom.xml中,引入TestNG、Rest-Assured、Allure等工具:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>api-test-framework</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- Rest-Assured -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.5.1</version>
</dependency>
<!-- TestNG -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.13</version>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>2.21.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-java-commons</artifactId>
<version>2.21.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.4.1</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-maven</artifactId>
<version>2.15.2</version>
<configuration>
<resultsDirectory>../allure-results</resultsDirectory>
</configuration>
</plugin>
</plugins>
</build>
</project>
二、项目结构设计
2.1 分层架构设计
为了使框架具有良好的分层结构,我们将项目划分为以下几个主要层次:
- api层(api)
用于封装待测试的接口,隔离业务逻辑与测试代码,提高代码复用性和可维护性。 - 基础层(base)
封装全局的基础信息,实现所有用例运行前需要做的初始化操作和所有用例结束后需要做的释放资源操作等。 - 测试层(tests)
存放具体的测试用例类。每个测试用例类对应一个或多个接口的测试,使用 TestNG 的注解来定义测试方法、测试分组等。 - 工具层(data)
存放测试框架的各种工具,如数据读取工具、日志工具、请求工具等。 - 资源层(resources)
存放测试框架所需的基础的配置信息,测试用例所需的测试数据等。
2.2 具体目录结构
三、核心模块实现
3.1 基础工具层实现
3.1.1 配置文件读取工具
配置文件读取(ConfigReader.java)在 test/resources/config 目录下创建 config.properties 文件,内容示例:
config.properties内容:
base.url=https://reqres.in
timeout=10000
ConfigReader.Java :
package org.example.utils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* ConfigReader 类用于读取配置文件 `config.properties` 中的配置信息。
* 该类使用静态代码块在类加载时加载配置文件,提供了获取基础 URL 和超时时间的静态方法。
*/
public class ConfigReader {
/**
* 静态属性,用于存储从配置文件中加载的属性信息。
*/
private static Properties properties;
/**
* 静态代码块,在类加载时执行,负责加载配置文件 `config.properties`。
*/
static {
// 初始化 Properties 对象,用于存储配置信息
properties = new Properties();
// 尝试从类路径下获取配置文件的输入流
try (InputStream input = ConfigReader.class.getClassLoader().getResourceAsStream("config/config.properties")) {
// 检查输入流是否为空
if (input == null) {
// 若为空,打印提示信息
System.out.println("Sorry, unable to find config.properties");
} else {
// 若不为空,将配置文件中的属性加载到 Properties 对象中
properties.load(input);
}
} catch (IOException e) {
// 捕获并打印加载配置文件时可能出现的 IO 异常
e.printStackTrace();
}
}
/**
* 从配置文件中获取基础 URL。
*
* @return 配置文件中 `base.url` 属性的值
*/
public static String getBaseUrl() {
// 从 Properties 对象中获取 `base.url` 属性的值并返回
return properties.getProperty("base.url");
}
/**
* 从配置文件中获取超时时间。
*
* @return 配置文件中 `timeout` 属性的值,转换为整数类型
*/
public static int getTimeout() {
// 从 Properties 对象中获取 `timeout` 属性的值,并将其转换为整数类型后返回
return Integer.parseInt(properties.getProperty("timeout"));
}
}
3.1.2 测试数据读取工具
将测试数据存放在外部excel中,通过该工具读取数据注入到测试用例中进行测试,实现数据驱动测试。
ExcelDataReader.Java
package org.example.utils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ExcelDataReader {
public static List<Map<String, String>> readExcelData(String filePath, String sheetName) {
List<Map<String, String>> dataList = new ArrayList<>();
try (FileInputStream file = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(file)) {
Sheet sheet = workbook.getSheet(sheetName);
Row headerRow = sheet.getRow(0);
int columnCount = headerRow.getPhysicalNumberOfCells();
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row dataRow = sheet.getRow(i);
Map<String, String> dataMap = new HashMap<>();
for (int j = 0; j < columnCount; j++) {
Cell headerCell = headerRow.getCell(j);
Cell dataCell = dataRow.getCell(j, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
dataMap.put(headerCell.getStringCellValue(), getCellValue(dataCell));
}
dataList.add(dataMap);
}
} catch (IOException e) {
e.printStackTrace();
}
return dataList;
}
private static String getCellValue(Cell cell) {
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue().toString();
} else {
return String.valueOf((int) cell.getNumericCellValue());
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
default:
return "";
}
}
}
3.1.3 HTTP 请求工具
使用 RestAssured 封装 HTTP 请求,编写 HttpUtil 类:
HttpUtil.java
package org.example.utils;
import io.restassured.RestAssured;
import io.restassured.config.HttpClientConfig;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
/**
* HttpUtil 类提供了一组静态方法,用于发送常见的 HTTP 请求,如 GET、POST、PUT 和 DELETE。
* 该类使用 RestAssured 库来简化 HTTP 请求的发送过程,并在静态代码块中进行了基本的配置。
*/
public class HttpUtil {
/**
* 私有静态的请求规范对象,用于配置和构建 HTTP 请求。
* 所有的 HTTP 请求都会基于这个请求规范来发送。
*/
private static RequestSpecification request;
/**
* 静态代码块,在类加载时执行,用于初始化 RestAssured 的基本配置。
* 设置请求头的 Content-Type 为 application/json。
*/
static {
// 初始化请求规范对象
request = RestAssured.given();
// 设置请求头的 Content-Type 为 application/json
request.header("Content-Type", "application/json");
}
/**
* 发送 HTTP GET 请求。
*
* @param url 请求的 URL
* @return 包含响应信息的 Response 对象
*/
public static Response get(String url) {
// 使用预配置的请求规范对象发送 GET 请求并返回响应
return request.when().get(url);
}
/**
* 发送 HTTP POST 请求。
*
* @param url 请求的 URL
* @param body 请求的 JSON 格式的请求体
* @return 包含响应信息的 Response 对象
*/
public static Response post(String url, String body) {
// 将请求体添加到请求规范对象中,并发送 POST 请求,最后返回响应
return request.body(body).when().post(url);
}
/**
* 发送 HTTP PUT 请求。
*
* @param url 请求的 URL
* @param body 请求的 JSON 格式的请求体
* @return 包含响应信息的 Response 对象
*/
public static Response put(String url, String body) {
// 将请求体添加到请求规范对象中,并发送 PUT 请求,最后返回响应
return request.body(body).when().put(url);
}
/**
* 发送 HTTP DELETE 请求。
*
* @param url 请求的 URL
* @return 包含响应信息的 Response 对象
*/
public static Response delete(String url) {
// 使用预配置的请求规范对象发送 DELETE 请求并返回响应
return request.when().delete(url);
}
}
3.2 接口封装层实现
通过封装接口,测试用例可直接调用 UserApi 类的方法,降低代码耦合度。以用户管理模块为例,编写 UserApi 类封装用户相关接口:
UserApi.Java
package org.example.api;
import io.restassured.response.Response;
import org.example.base.BaseTest;
import org.example.utils.HttpUtil;
/**
* UserApi 类提供了与用户相关的 API 操作方法,包括获取用户列表和创建用户。
*/
public class UserApi {
/**
* 用户相关 API 的基础 URL 路径,用于构建具体的请求 URL。
*/
static String userUrl = "/api/users";
/**
* 根据传入的页码获取用户列表。
*
* @param i 页码,用于指定要获取的用户列表所在的页码。
* @return 包含用户列表信息的响应对象。
*/
public static Response getUserList(int i) {
// 调用 HttpUtil 工具类的 get 方法发送 GET 请求,获取指定页码的用户列表
return HttpUtil.get(userUrl + "page=" + i );
}
/**
* 根据传入的用户名和职位信息创建一个新用户。
*
* @param name 新用户的姓名。
* @param job 新用户的职位。
* @return 包含创建用户操作结果的响应对象。
*/
public static Response createUser(String name, String job) {
// 构建创建用户请求的 JSON 格式请求体
String body = "{\"name\":\"" + name + "\",\"job\":\"" + job + "\"}";
// 调用 HttpUtil 工具类的 post 方法发送 POST 请求,创建新用户
return HttpUtil.post(userUrl, body);
}
}
3.3 测试层实现
测试类继承 BaseTest 类,使用接口封装层的 UserApi 类进行测试,通过 TestNG 注解定义测试方法和数据提供器。以用户管理模块测试为例,编写 UserTest 类:
UserTest.Java
package org.example.tests;
import io.restassured.response.Response;
import org.example.api.UserApi;
import org.example.base.BaseTest;
import org.example.utils.ExcelDataReader;
import org.example.utils.LogUtil;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.util.List;
import java.util.Map;
/**
* UserTest 类继承自 BaseTest 类,用于编写与用户相关的测试用例。
* 该类包含获取用户列表和创建用户的测试方法,使用 TestNG 框架进行测试。
*/
public class UserTest extends BaseTest{
/**
* 测试获取用户列表的功能。
* 该方法使用 UserApi 类的 getUserList 方法获取第二页的用户列表,
* 并验证响应状态码是否为 200。
*
* @Test 注解表明这是一个 TestNG 测试用例,
* groups 属性将该测试用例归类到 "user" 组,
* description 属性描述了测试用例的功能。
*/
@Test(groups = "user", description = "获取用户列表")
public void testGetUserList() {
// 调用 getUserList 方法获取第二页的用户列表,并获取响应
Response response = UserApi.getUserList(2);
// 验证响应的状态码是否为 200
Assert.assertEquals(response.getStatusCode(), 200);
}
/**
* 数据提供器方法,为 testCreateUser 测试方法提供测试数据。
* 该方法返回一个二维对象数组,每个子数组包含两个元素,分别为用户名和职位。
*
* @DataProvider 注解指定该方法为数据提供器,name 属性指定数据提供器的名称。
*
* @return 包含测试数据的二维对象数组
*/
@DataProvider(name = "createUserData")
public Object[][] createUserData() {
// 返回测试数据,每行代表一组测试数据,包含用户名和职位
return new Object[][]{
{"user1", "leader"},
{"user2", "employee"}
};
}
/**
* 测试创建用户的功能。
* 该方法使用 UserApi 类的 createUser 方法创建用户,
* 并验证响应状态码是否为 201,同时验证返回的用户名和邮箱是否与传入的参数一致。
*
* @Test 注解表明这是一个 TestNG 测试用例,
* groups 属性将该测试用例归类到 "user" 组,
* description 属性描述了测试用例的功能,
* dataProvider 属性指定使用 "createUserData" 数据提供器提供测试数据。
*
* @param name 要创建的用户的姓名
* @param job 要创建的用户的职位,这里代码中错误地用职位验证邮箱字段
*/
@Test(groups = "user", description = "创建用户", dataProvider = "createUserData")
public void testCreateUser(String name, String job) {
// 调用 createUser 方法创建用户,并获取响应
Response response = UserApi.createUser(name, job);
// 验证响应的状态码是否为 201
Assert.assertEquals(response.getStatusCode(), 201);
// 验证响应中返回的用户名是否与传入的用户名一致
Assert.assertEquals(response.jsonPath().getString("name"), name);
// 此处代码可能存在逻辑错误,用职位验证邮箱字段,需确认接口返回结构
Assert.assertEquals(response.jsonPath().getString("job"), job);
}
// 从Excel获取测试数据的DataProvider
@DataProvider(name = "excelCreateUserData")
public Object[][] excelCreateUserData() {
// 读取resources/data/user_data.xlsx中的"Sheet1"工作表
List<Map<String, String>> dataList = ExcelDataReader.readExcelData(
"./src/test/resources/data/user_data.xlsx", "Sheet1"
);
// 将List<Map>转换为TestNG需要的Object[][]格式
Object[][] data = new Object[dataList.size()][2];
for (int i = 0; i < dataList.size(); i++) {
data[i][0] = dataList.get(i).get("name");
data[i][1] = dataList.get(i).get("job");
}
return data;
}
@Test(groups = "user", description = "创建用户(数据驱动测试)", dataProvider = "excelCreateUserData")
public void testCreateUserWithExcelData(String username, String job) {
Response response = UserApi.createUser(username, job);
// 断言状态码和响应数据
Assert.assertEquals(response.getStatusCode(), 201, "创建用户接口状态码异常");
Assert.assertEquals(response.jsonPath().getString("name"), username, "用户名验证失败");
Assert.assertEquals(response.jsonPath().getString("job"), job, "工作验证失败");
// 打印日志(示例)
LogUtil.info("创建用户成功:username=" + username + ", job=" + job);
}
}
四、执行测试
打开命令行工具,进入测试工程根目录:1、执行mvn clean test运行测试用例。2、执行mvn allure:serve生成测试报告。
最终的测试报告:
总结
本文详细介绍了从 0 构建高可维护的企业级接口自动化测试框架的全过程,大家可以通过文章中的内容搭建一个自己的接口自动化测试框架。如果觉得自己搭建框架太耗时,也可以下载我上传的自动化测试框架资源来使用。这个资源基本是能够开箱即用,里面的被测对象为公网网站,可以直接运行。后续可以将里面的接口改为您的项目接口,即可实现企业的项目接口测试。最后祝大家都能高效测试,开心工作!