目录
- 接口自动化测试使用场景
- 产品需求分析
- 测试用例设计思路
- 接口自动化脚本编写
接口自动化测试使用场景
participant 需求
participant 设计
participant 开发
participant 测试
participant 预发布
participant 验收
participant 上线
scale 1000
autonumber
需求 → 设计:…
设计 → 开发:…
开发 → 测试: 冒烟测试
note left : 主流程自动化测试
测试 → 预发布:…
预发布 → 验收: 验收测试
note left : 全量自动化测试
验收 → 上线:…
接口自动化测试使用场景
电子商城产品图片
电子商城需求分析
- 商城管理后台(https://litemall.hogwarts.ceshiren.com/)
- 商城客户端(litemall-vue)
商城业务场景
- 商品上架
- 商品查询
- 加入购物车
scale 700*500
autonumber
participant 测试人员 as tester
participant 登录模块 as login
participant 管理后台 as admin
participant 客户端 as customer
tester → login: 编写脚本
login → admin: 登录接口
admin → customer: 商品上架接口
admin → customer: 商品查询接口
customer → customer: 加购接口
customer → tester: 测试断言
研发技术评审
- 管理后台接口文档
https://litemall.hogwarts.ceshiren.com/swagger-ui.html#/
接口测试用例设计思路
@startmindmap
*[#Orange] 接口测试思路
**[#lightblue] 基本功能流程测试(p1)
***[#lightgreen] 冒烟测试
***[#lightgreen] 正常流程覆盖测试
**[#lightblue] 基于输入域的测试(p2)
***[#lightgreen] 边界值测试
***[#lightgreen] 特殊字符校验
***[#lightgreen] 参数类型校验
***[#lightgreen] 必选参数校验
***[#lightgreen] 组合参数校验
***[#lightgreen] 有效性校验
***[#lightgreen] 默认值校验
***[#lightgreen] 排重逻辑
left side
**[#lightblue] 接口幂等性
***[#lightgreen] 重复提交
**[#lightblue] 故障注入
***[#lightgreen] Redis故障降级测试
***[#lightgreen] 服务故障转移测试
**[#lightblue] 线程安全测试
***[#lightgreen] 并发测试
***[#lightgreen] 分布式测试
***[#lightgreen] 数据库读写安全测试
@endmindmap
添加购物车流程脚本编写
title 编写思路
@startmindmap
- 思路
** 获取接口信息
*** swagger 接口文档
*** 前端抓包
** 单步调通接口后,根据业务流程串联起来
** 添加断言,确认流程正常
@endmindmap
- 调用上架商品接口,上架商品。
- 调用查询商品列表接口,获取商品ID
- 查询商品详情接口,获取商品库存ID
- 调用加入购物车接口,添加购物车
环境依赖(pom.xml)
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
<!-- 使用 Java 11 语言特性 ( -source 11 ) 并且还希望编译后的类与 JVM 11 ( -target 11 )兼容,您可以添加以下两个属性,它们是默认属性插件参数的名称-->
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<!-- 对应junit Jupiter的版本号;放在这里就不需要在每个依赖里面写版本号,导致对应版本号会冲突-->
<junit.jupiter.version>5.8.2</junit.jupiter.version>
<maven.compiler.version>3.8.1</maven.compiler.version>
<maven.surefire.version>3.0.0-M5</maven.surefire.version>
<hamcrest.version>2.2</hamcrest.version>
<!-- plugins -->
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
<slf4j.version>1.7.32</slf4j.version>
<logback.version>1.2.10</logback.version>
<!--对应解析-->
<jackson.version>2.13.1</jackson.version>
<!--allure报告-->
<allure.version>2.16.1</allure.version>
<aspectj.version>1.9.5</aspectj.version>
</properties>
<!-- 物料清单 (BOM)-->
<dependencyManagement>
<dependencies>
<!--当使用 Gradle 或 Maven 引用多个 JUnit 工件时,此物料清单 POM 可用于简化依赖项管理。不再需要在添加依赖时设置版本-->
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<!-- <version>5.8.2</version>-->
<version>${junit.jupiter.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<!-- 对应添加的依赖的作用范围-->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>
<!-- yaml文件解析-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- csv文件解析-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- allure报告-->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<version>${allure.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.rest-assured/rest-assured -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>4.5.1</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.qameta.allure/allure-rest-assured -->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-rest-assured</artifactId>
<version>2.16.1</version>
</dependency>
<!--json schema -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>4.4.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<encoding>UTF-8</encoding>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
脚本优化-参数化
使用ParameterizedTest注解,结合ValueSource进行商品名称的参数化
如果是多参数参数化,可以使用 MethodSource 或其他注解。
@ParameterizedTest
@ValueSource(strings = {"hogwarts1", "hogwarts2"})
脚本优化-添加日志(Java)
方式一: 使用REST Assured自带的日志
方式二: 使用 slf4j(配置在下一页)
// REST Assured自带的日志打印
given()
.log().all() // 请求日志
when()
.get(url).
then()
.log().all(); //响应日志
slf4j配置
<!-- 在`resources`中添加配置文件`logback.xml` -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- name指定<appender>的名称 class指定<appender>的全限定名 ConsoleAppender的作用是将日志输出到控制台-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 输出时间格式-->
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36}.%M\(%line\) -- %msg%n</pattern>
</encoder>
</appender>
<logger name="com.ceshiren" level="DEBUG" />
<logger name="com" level="WARN" />
<logger name="ceshiren" level="WARN" />
<logger name="org" level="WARN" />
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
<!-- 注意不要直接粘贴 -->
<!-- properties的版本配置 -->
<slf4j.version>1.7.32</slf4j.version>
<logback.version>1.2.10</logback.version>
<!-- 依赖信息 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>compile</scope>
</dependency>
脚本优化-数据清理(Java)
在用例执行完成之后调用删除接口完成数据清理
在编写用例的时候,将创建的ID保存到一个数组变量(文件)中,在AfterAll内,调用删除接口批量删除这些数据。
在AfterAll内,先调用获取接口,获取所有的数据信息后调用删除接口删除。
注意: 尽量不要使用AfterEach去做数据清理
脚本优化-报告展示
安装allure相关依赖
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<version>${allure.version}</version>
</dependency>
脚本优化-参数化
- 使用ParameterizedTest注解,结合ValueSource进行商品名称的参数化
- 如果是多参数参数化,可以使用 MethodSource 或其他注解。
@ParameterizedTest
@ValueSource(strings = {“hogwarts1”, “hogwarts2”})
脚本优化-添加日志(Java)
- 方式一: 使用REST Assured自带的日志
- 方式二: 使用 slf4j(配置在下一页)
// REST Assured自带的日志打印
given()
.log().all() // 请求日志
when()
.get(url).
then()
.log().all(); //响应日志
slf4j配置
<!-- 在`resources`中添加配置文件`logback.xml` -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- name指定<appender>的名称 class指定<appender>的全限定名 ConsoleAppender的作用是将日志输出到控制台-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 输出时间格式-->
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36}.%M\(%line\) -- %msg%n</pattern>
</encoder>
</appender>
<logger name="com.ceshiren" level="DEBUG" />
<logger name="com" level="WARN" />
<logger name="ceshiren" level="WARN" />
<logger name="org" level="WARN" />
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
<!-- 注意不要直接粘贴 -->
<!-- properties的版本配置 -->
<slf4j.version>1.7.32</slf4j.version>
<logback.version>1.2.10</logback.version>
<!-- 依赖信息 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>compile</scope>
</dependency>
脚本优化-数据清理(Java)
- 在用例执行完成之后调用删除接口完成数据清理
- 在编写用例的时候,将创建的ID保存到一个数组变量(文件)中,在AfterAll内,调用删除接口批量删除这些数据。
- 在AfterAll内,先调用获取接口,获取所有的数据信息后调用删除接口删除。
脚本优化-报告展示
- 安装allure相关依赖
io.qameta.allure
allure-junit5
${allure.version}
package com.restassured.litemall;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import static io.restassured.RestAssured.given;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class LitemallTest {
static String token;
static String clientToken;
static List<Integer> deleteIds = new ArrayList<>();
static Logger logger = LoggerFactory.getLogger(LitemallTest.class);
@BeforeAll
static void setupClass() {
String loginUrl = "https://litemall.hogwarts.ceshiren.com/admin/auth/login";
String loginData = "{\"username\":\"hogwarts\",\"password\":\"test12345\",\"code\":\"\"}";
token = given()
.body(loginData)
.contentType("application/json;charset=UTF-8")
.when()
.post(loginUrl)
.then()
// .log().all();
.extract().path("data.token");
System.out.println("token:" + token);
String clientLoginUrl = "https://litemall.hogwarts.ceshiren.com/wx/auth/login";
String clientLoginData = "{\"username\":\"user123\",\"password\":\"user123\"}";
clientToken = given()
.body(clientLoginData)
.contentType("application/json;charset=UTF-8")
.when()
.post(clientLoginUrl)
.then()
// .log().all();
.extract().path("data.token");
System.out.println("clientToken:" + clientToken);
}
@ParameterizedTest
@CsvSource({"1234567890,红烧肉", "1234567891,红烧肉2"})
void addCarts(String goodsSn, String goodsName) {
logger.info("goodsSn为:{},goodsName为:{}",goodsSn,goodsName);
//1. 调用上架商品接口,上架商品。
String addProduct = "https://litemall.hogwarts.ceshiren.com/admin/goods/create";
// String goodsSn="1234567890";
// String goodsName="红烧肉";
String productData = "{\"goods\":{\"picUrl\":\"\",\"gallery\":[],\"isHot\":false,\"isNew\":true,\"isOnSale\":true,\"goodsSn\":\"" + goodsSn + "\",\"name\":\"" + goodsName + "\"},\"specifications\":[{\"specification\":\"规格\",\"value\":\"标准\",\"picUrl\":\"\"}],\"products\":[{\"id\":0,\"specifications\":[\"标准\"],\"price\":\"23\",\"number\":\"100\",\"url\":\"\"}],\"attributes\":[]}";
given()
.relaxedHTTPSValidation()
.header("X-Litemall-Admin-Token", token)
.body(productData)
.log().all()
.contentType("application/json;charset=UTF-8")
.when()
.post(addProduct)
.then().log().all();
//2. 调用查询商品列表接口,获取商品ID
String getProductUrl = "https://litemall.hogwarts.ceshiren.com/admin/goods/list";
Integer goodsId = given()
.relaxedHTTPSValidation()
.header("X-Litemall-Admin-Token", token)
.queryParam("goodsSn", goodsSn)
.contentType("application/json")
.when()
.get(getProductUrl)
.then()
// .log().all();
.extract().path("data.list[0].id");
System.out.println("goodsId:" + goodsId);
//3. 查询商品详情接口,获取商品库存ID
String detailUrl = "https://litemall.hogwarts.ceshiren.com/admin/goods/detail";
Integer productId = given()
.header("X-Litemall-Admin-Token", token)
.param("id", goodsId)
.contentType("application/json")
.when()
.get(detailUrl)
.then()
.log().all()
.extract().path("data.products[0].id");
System.out.println("productId:" + productId);
//4. 调用加入购物车接口,添加购物车
String addCartUrl = "https://litemall.hogwarts.ceshiren.com/wx/cart/add";
String addCartData = "{\"goodsId\":" + goodsId + ",\"number\":1,\"productId\":" + productId + "}";
String errmsg = given()
.header("X-Litemall-Token", clientToken)
.contentType("application/json")
.body(addCartData)
.when()
.post(addCartUrl)
.then()
.log().all()
.extract().path("errmsg");
logger.info("添加购物车的断言结果是:{}", errmsg);
logger.info("goodsId为:{}",goodsId);
//数据清理:1、获取;2、保存;3、删除
deleteIds.add(goodsId);
assertEquals("成功", errmsg);
}
@AfterAll
static void cleanUp() {
logger.info("获取到的ids信息为:{}", deleteIds);
// String id = "1444464";
deleteIds.forEach((id) -> {
deleteById(id);
});
}
static void deleteById(Integer id) {
logger.info("id为:{}",id);
String deleteUrl = "https://litemall.hogwarts.ceshiren.com/admin/goods/delete";
String deleteData = "{\"id\":" + id + "}";
String errmsg = given()
.relaxedHTTPSValidation()
.header("X-Litemall-Admin-Token", token)
.contentType("application/json")
.body(deleteData)
.when()
.post(deleteUrl)
.then()
.log().all()
.extract().path("errmsg");
logger.info("删除商品后的断言结果是:{}", errmsg);
assertEquals("成功", errmsg);
}
}