一、框架及知识点梳理
1、框架
1)输入信息(model)
Model复杂程度 场景 使用Model的类型
简单 1~2个字段 参数
正常 多个字段 实体类
非常复杂 多个字段,且嵌套数据较多 yaml、json、excel
2)接口信息(api)
3)测试用例(testcase)
2、关键知识点
1)实体类转换为json
a、添加 POM 依赖:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.13.1</version>
</dependency>
b、应用实例:
UserModel userModel = new UserModel();
ObjectMapper mapper = new ObjectMapper();
try {
String Json = mapper.writeValueAsString(userModel);
System.out.println(Json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
2)Json 快速转换实体类
a、添加IDEA插件
GsonFormatPlus+LomBok
b、添加POM依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
3)过滤器
示例代码:
public class DemoFilter implements Filter {
@Override
public Response filter(FilterableRequestSpecification filterableRequestSpecification,
FilterableResponseSpecification filterableResponseSpecification,
FilterContext filterContext) {
// 设置基地址
filterableRequestSpecification.baseUri("基地址");
filterableRequestSpecification.contentType("application/json");
// 发起真实请求
Response response = filterContext.next(filterableRequestSpecification, filterableResponseSpecification);
// 拼接请求信息
String requests_info = filterableRequestSpecification.getMethod() + " "
+ filterableRequestSpecification.getURI()
+ "\n Request Body =>"
+ filterableRequestSpecification.getBody();
log.debug("接口请求日志:" + requests_info);
// 在 allure 中添加日志信息
addAttachment(filterableRequestSpecification.getBasePath() + "接口请求:", requests_info);
String response_info =
"\n Response Status => "
+ response.getStatusCode()
+ " " + response.getStatusLine()
+ "\n Response Body => " + response.getBody().asPrettyString();
addAttachment(filterableRequestSpecification.getBasePath() + "接口响应:", response_info);
log.debug("接口响应日志:" + response_info);
return response;
}
}
4)日志配置
public class Log {
public static Logger log = LoggerFactory.getLogger("litemall");
}
优雅日志格式-在backlog.xml中添加
<pattern>
<!--优雅的日志格式-->
%d{dd-MM-yyyy HH:mm:ss.SSS} %highlight(%-5level) %magenta([%thread]) %yellow(%logger{40}.%M\(%class{0}.java:%line\)) - %msg%throwable%n
</pattern>
二、代码实践
1、api包(存放接口类,类中显示接口的各种动作,如增、删、改、查等操作)
1)创建 BaseLitemallApi 类,用于存放接口类中的公共信息,如获取 token ;
package com.ceshiren.hogwarts.litemall.api;
import io.restassured.filter.Filter;
import io.restassured.http.Header;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.restassured.RestAssured.given;
public class BaseLitemallApi {
public static Logger logger;
//获取 filter 对象
// 问题:随着业务接口增多,filter 需要再多处进行实例化操作
// 解决:在父类进行filter 的实例化操作,子类继承后可以直接调用 filter 实例
public Filter apiFilter ;
//Litemall的角色属性,根据角色的不同初始化对应的不同的token信息;
public String role;
public Header token;
public void setRole(String role) {
this.role = role;
}
static void BaseLitemallApi(){
logger = LoggerFactory.getLogger("litemall");
}
public Header getToken() {
return token;
}
public void setToken(Header token) {
this.token = token;
}
public void addRole(BaseLitemallApi baseLitemallApi){
this.apiFilter = new ApiFilter(baseLitemallApi.getToken());
}
//初始化token时应该传入用户名密码
public void initToken(String username,String password){
//获取token
//根据角色的不同初始化对应的不同token信息
//如果是admin,则初始化admin的token信息
if(this.role.equals("admin")){
String paraInfo = "{\"username\":\""+username+"\",\"password\":\""+password+"\",\"code\":\"\"}";
String tokenValue = given().log().all()
.contentType("application/json;charset=UTF-8")
.body(paraInfo)
.when().post("https://litemall.hogwarts.ceshiren.com/admin/auth/login")
.then().extract().path("data.token");
this.token = new Header("X-Litemall-Admin-Token",tokenValue);
//如果是client,则初始化client的token信息
} else {
String info = "{\"username\":\""+username+"\",\"password\":\""+password+"\"}";
String tokenValue = given().log().all()
.contentType("application/json")
.body(info)
.when().post("https://litemall.hogwarts.ceshiren.com/wx/auth/login")
.then().extract().path("data.token");
this.token = new Header("X-Litemall-Token",tokenValue);
}
}
}
2、创建 ApiFilter 类,使用 RestAssured 的 filter 方法对 given() 公共的代码进行整合,减少冗余代码。
package com.ceshiren.hogwarts.litemall.api;
import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.http.Header;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import static com.ceshiren.hogwarts.utils.Log.log;
import static io.qameta.allure.Allure.addAttachment;
import static io.restassured.RestAssured.given;
//问题1:如果出现多个角色,多个token应该如何处理
//问题2:多次调用login请求获取token,影响用例的执行效率;
//解决方案:添加一个初始化token的方法,并调出,并添加一个初始化filter的方法,传入token给filter;
public class ApiFilter implements Filter {
public Header tokenHeader;
public ApiFilter(Header token){
this.tokenHeader = token;
}
@Override
public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec, FilterContext context) {
//添加基地址
requestSpec.baseUri("https://litemall.hogwarts.ceshiren.com/");
//添加头部信息-token
requestSpec.contentType("application/json");
requestSpec.header(tokenHeader);
//3、日志配置:即每个接口在调用过程中,自动调用日志信息
//请求信息
String requests_info = "\n 请求方法:" + requestSpec.getMethod()
+"\n 请求地址: "+requestSpec.getURI()
+"\n 请求体:"
+ requestSpec.getBody();
//真实发起请求
Response response = context.next(requestSpec,responseSpec);
//响应信息
String response_info = "\n 响应状态:"
+ response.getStatusCode()
+ "\n 响应行: " + response.getStatusLine()
+ "\n 响应体:" + response.getBody().asPrettyString();
log.debug("接口请求" + requests_info);
addAttachment("接口请求" ,requests_info);
log.debug("接口响应" + response_info);
addAttachment("接口响应" , response_info);
return response;
}
}
3、以 商品接口 (GoodsApi)中包含接口的增、删、改、查;
package com.ceshiren.hogwarts.litemall.api;
import com.ceshiren.hogwarts.litemall.model.CartModel;
import com.ceshiren.hogwarts.litemall.model.GoodsModel;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.restassured.filter.Filter;
import io.restassured.response.Response;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.requestSpecification;
//业务模板:货品相关
public class GoodsApi extends BaseLitemallApi {
//增删查改
//问题一:如果传入的数据格式必须是json,如果把object转换为json(选修)
//解决方案:使用jack直播转换即可
// ObjectMapper mapper = new ObjectMapper();
// try {
// System.out.println(mapper.writeValueAsString(carModel));
// } catch (JsonProcessingException e) {
// e.printStackTrace();
// }
public Response create(String goodsData){
return given().filter(apiFilter)
.body(goodsData)
.when().post("/admin/goods/create");
}
public void delete(Integer id){
String body = "{\"id\": \"" + id + "\"}";
given().filter(apiFilter)
.body(body)
.when().post("/admin/goods/delete")
.then();
}
public String get(String goodsSn){
String goodIdsObject = given().filter(apiFilter)
.param("goodsSn",goodsSn)
.when().get("/admin/goods/list")
.then().extract().body().asString();
return goodIdsObject;
}
public void update(){
}
public String getDetail(Integer goodIds){
String productObject = given().filter(apiFilter)
// .header("X-Litemall-Admin-Token",token)
.param("id",goodIds)
.when().get("/admin/goods/detail")
.then().extract().body().asString();
return productObject;
}
}
2、model包(存放接口类,类中主要包含接口字段或get、set方法)
1)GoodsModel 类(存入商品接口字段及get\set方法)
package com.ceshiren.hogwarts.litemall.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@Data
public class GoodsModel {
@JsonProperty("goods")
private GoodsDTO goods;
@NoArgsConstructor
@Data
public static class GoodsDTO {
@JsonProperty("goodsSn")
private String goodsSn;
@JsonProperty("name")
private String name;
}
}
2)CartModel类(存入购物车接口字段及get\set方法)
package com.ceshiren.hogwarts.litemall.model;
public class CartModel {
private Integer goodsId;
private Integer number;
private Integer productId;
public Integer getGoodsId() {
return goodsId;
}
public void setGoodsId(Integer goodsId) {
this.goodsId = goodsId;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
}
3、通用包(存入通用工具类,如 log)
package com.ceshiren.hogwarts.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log {
public static Logger log = LoggerFactory.getLogger("litemall");
}
4、测试包(存放测试用例)
1)创建 LitemallBaseTest 类,存放一些公共代码
package com.ceshiren.hogwarts.litemall.api;
import org.junit.jupiter.api.BeforeAll;
public class LitemallBaseTest {
static BaseLitemallApi admin;
static BaseLitemallApi client;
@BeforeAll
static void setUp(){
//问题:market 和 goods 都属于同一个角色。使用的功能,在这个过程中,不需要切换角色。
//解决方案:角色的初使化不要和具体的业务挂钩,将角色的初始化和业务的初始化分开进行。
//实例化一个 admin 角色
admin = new BaseLitemallApi();
admin.setRole("admin");
admin.initToken("admin123","admin123");
//实例化一个客户角色
client = new BaseLitemallApi();
client.setRole("client");
client.initToken("user123","user123");
}
}
2)创建 CartApiTest 类,实现
//步骤1:调用上架商品接口,上架商品
//步骤2:调用查询商品列表接口,获取商品ID
//步骤3:查询商品详情接口,获取商品库存ID
//步骤4:调用添加购物车接口
package com.ceshiren.hogwarts.litemall.api;
import com.ceshiren.hogwarts.litemall.api.ApiFilter;
import com.ceshiren.hogwarts.litemall.api.BaseLitemallApi;
import com.ceshiren.hogwarts.litemall.api.CartApi;
import com.ceshiren.hogwarts.litemall.api.GoodsApi;
import com.ceshiren.hogwarts.litemall.model.CartModel;
import com.jayway.jsonpath.JsonPath;
import io.restassured.filter.Filter;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.slf4j.Logger;
import static com.ceshiren.hogwarts.utils.Log.log;
import java.util.ArrayList;
import java.util.List;
class CartApiTest extends LitemallBaseTest{
static GoodsApi goodsApi;
static CartApi cartApi;
static List<Integer> deleteList = new ArrayList<>();
@BeforeAll
static void setUpClass(){
goodsApi = new GoodsApi();
goodsApi.addRole(admin);
cartApi = new CartApi();
cartApi.addRole(client);
}
@AfterAll
static void cleanUp(){
//删除
deleteList.forEach((id) -> {
log.info("删除的ID为:",id);
goodsApi.delete(id);
});
}
//问题二:token 依然暴露在测试用例里面,且token和cart没有任何的业务关系,应该隐藏起来
//问题三:输入以及输出
@ParameterizedTest
@CsvSource({"S180009,小熊09"})
void create(String goodsSn,String name) {
//步骤1:调用上架商品接口,上架商品
String goodsData = "{\"goods\":{\"picUrl\":\"\",\"gallery\":[],\"isHot\":false,\"isNew\":true,\"isOnSale\":true,\"goodsSn\":\""+goodsSn+"\",\"name\":\""+name+"\"},\"specifications\":[{\"specification\":\"规格\",\"value\":\"标准\",\"picUrl\":\"\"}],\"products\":[{\"id\":0,\"specifications\":[\"标准\"],\"price\":0,\"number\":0,\"url\":\"\"}],\"attributes\":[]}";
goodsApi.create(goodsData).then().statusCode(200);
CartModel cartModel = new CartModel();
//步骤2:调用查询商品列表接口,获取商品ID
//体现出接口之间的数据关联
String goodsObject = goodsApi.get(goodsSn);
System.out.println("goodsObject的值为"+goodsObject);
List<Integer> goodsid = JsonPath.read(goodsObject,"$..id");
System.out.println("List<Integer> goodsid的值为:"+ goodsid.toString());
cartModel.setGoodsId(goodsid.get(0));
//步骤3:查询商品详情接口,获取商品库存ID
//.path("data.products[0].id")
String productObject = goodsApi.getDetail(goodsid.get(0));
List<Integer> productId = JsonPath.read(productObject,"$..products[0].id");
cartModel.setProductId(productId.get(0));
cartModel.setNumber(1);
//步骤4:调用添加购物车接口
cartApi.create(cartModel);
deleteList.add(goodsid.get(0));
}
}
三、pom 相关依赖
<?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>hogwarts-api</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<!--<module>hogwarts-api</module>-->
</modules>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!--Junit5-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.0-M1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.0-M1</version>
<scope>test</scope>
</dependency>
<!--RestAssured-->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.1.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>5.1.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.13.4</version>
</dependency>
<!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.3</version>
</dependency>
<!--allure-->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<version>2.16.0</version>
</dependency>
</dependencies>
</project>
四、logback相关代码
<configuration>
<!-- name指定<appender>的名称 class指定<appender>的全限定名 ConsoleAppender的作用是将日志输出到控制台-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 输出时间格式-->
<pattern>
<!--优雅的日志格式-->
%d{dd-MM-yyyy HH:mm:ss.SSS} %highlight(%-5level) %magenta([%thread]) %yellow(%logger{40}.%M\(%class{0}.java:%line\)) - %msg%throwable%n
</pattern>
</encoder>
</appender>
<logger name="litemall" level="DEBUG" />
<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>