(二)接口自动化 框架java+testng+springboot 代码demo

上篇讲了什么是接口,如何通过postman去执行单接口。还不了解的,建议先看下接口的表现curl 直通上文
这篇文章重点记录下如何用自动化实现快速接口自动化。整个项目思路就是使用testng编写测试用例,引入springboot,使用注解快速实现。Jenkins调用xml去执行测试用例,最后生成报告。
通过持续集成Jenkins去执行并生成测试报告

本文讲述尽我可能的详细,小白对于个别注解或者引入包觉得模糊不清的,自行去补充下。文章比较基础,接下来我们一起梳理下吧。

1.创建一个Maven 项目

在这里插入图片描述

2. 在pom.xml文件中引入testng,springboot等的jar包

maven的好处就是无需手动寻找jar包引入,直接pom文件引入就可以,maven会自动解决重复和冲突问题,统一管理所有的依赖包
该项目中所用到的jar,大家根据自己的情况选择性引入

<dependencies>
    <!--引入springboot相关-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vinmtage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--java自动构造器,需手动引入插件,自动生成getter/setter、equals、hashcode、toString-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!--java集合框架-->
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.2</version>
    </dependency>
    <!--testng执行接口进行断言-->
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.13.1</version>
        <scope>test</scope>
    </dependency>
    <!--jason转换-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.58</version>
    </dependency>
    <!--http请求-->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.6</version>
    </dependency>
    <dependency>
        <!--配置allure,生成测试报告-->
        <groupId>io.qameta.allure</groupId>
        <artifactId>allure-testng</artifactId>
        <version>2.13.0</version>
        <scope>test</scope>
    </dependency>
    <!--链接mysql:jdbc,进程少,为了更快速,没有引入mybatis-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.34</version>
    </dependency>
    <!--解析yaml-->
    <dependency>
        <groupId>org.yaml</groupId>
        <artifactId>snakeyaml</artifactId>
        <version>1.17</version>
    </dependency>
    <!--redis-->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    <!--Kafka-->
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka_2.11</artifactId>
        <version>2.2.0</version>
    </dependency>

</dependencies>
3.整体项目目录介绍

在这里插入图片描述

4.新建即将运行xml,所含内容
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="hema_hotel_stage2" parallel="classes" thread-count="1">
    <!-- 域名配置:预发环境,suite名字随意命名-->

    <!-- 预发CASE配置 -->
    <test name="henmaHotel Test Cases">
        
        <!-- 接口url头、header信息参数配置,通过testng直接引入,方便统一管理 -->
        <parameter name="apiHost" value="http://xxxxxx.xx.com/xx"/>
        <parameter name="token" value="5dfc2b8f9725eb0007d0e9ea"/>
        <parameter name="traceSource" value="publicactivity"/>

        <!--配置数据库域名,及数据库name,通过接口的方式获得数据库返回结果json数据。线上 专用-->
        <parameter name="dburl" value="http://gateway.qcenter.xxxx.com/gateway/data/select"></parameter>
        <parameter name="dbname" value="TEArsenalMarketingSingleProduct"></parameter>
        <!--配置redis组,通过redis_properties.yaml找到对应的ip,获取redis中短信验证码-->
        <parameter name="redisGroup" value="redis-arsenal-produck"></parameter>
        <!--接口请求体中用到的参数,如果很多接口共用一个参数,也通过xml进行统一管理-->
        <parameter name="hotel_block" value="00101276"/>
        <parameter name="block_member" value="240000000804143402"/>
        

        <classes>

            <!--calsses中可以维护多个class类,运行时按照这里的顺序依次执行-->
            <!--短信验证码-->
            <class name="com.ly.qa.api.arsenal.hemaHotel.SendCode"></class>
            <class name="com.ly.qa.api.arsenal.hemaHotel.GetSendCode"></class>

            <!--裂变用户注册-->
            <class name="com.ly.qa.api.arsenal.hemaHotel.AgentWithVerifyPhone_Clint"></class>

        </classes>
    </test>

    <listeners>
        <!--为了解决一个类中多个方法可以按照已定义的顺序执行,引入监听,该文件网上一搜一堆-->
        <!--配置监听-->
        <listener class-name="com.ly.qa.api.arsenal.common.RePrioritizingListener"/>
    </listeners>

</suite>
5.原生httpclient实现get请求

实现接口deleteById 对应curl:

curl --location --request GET 'http://arsenalgw.elong.com/xxxxx/deleteById?token=nopass.2&id=0001' \
--header 'token: 5dfb21xxxxxxx40f' \
--header 'traceSource: pubxxxxxtage' \
--header 'Cookie: H5CookieId=af7c197f-0558-47b6-b376-2ee166a6fedf' \
--data-raw ''

java实现类 deleteById

package com.ly.qa.api.arsenal.hemaHotel;

import com.ly.qa.api.core.RestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.Assert;

import java.io.IOException;
import java.net.URISyntaxException;


@Service
public class DeleteUserInformation extends AbstractTestNGSpringContextTests {

    @Autowired
    RestUtils requests;

    private String api = "/xxxxx/deleteById";

    /**
     * 描述:删除BD及裂变用户注册信息
     * 预期:1.结果状态为success
     * 备注:获取用户的id
     */

	//接口所需要url,headers,以及删除用户头部信息,都通过xml配置文件传递过来,同名匹配规则。
	@Test(priority = 1) //方法执行顺序
    @Parameters({"apiHost", "headers", "userid"})
    public void deleteBd_clint(String apiHost, HttpHeaders headers, String userid) {
		//拼接url路径
        String url = apiHost.concat(api);

        //创建一个httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        //地址拼接参数
        URIBuilder uriBuilder = null;
        try {
            uriBuilder = new URIBuilder(url);
			//添加get请求 parameter
            uriBuilder.addParameter("token", "nopass.2");
            uriBuilder.addParameter("id", userid);

            HttpGet httpGet = new HttpGet(uriBuilder.build());
            //添加header
            httpGet.setHeader("token", headers.getFirst("token"));
            httpGet.setHeader("traceSource", headers.getFirst("traceSource"));

            //执行请求
            HttpResponse httpResponse = httpClient.execute(httpGet);
            //获取响应结果
            HttpEntity httpEntity = httpResponse.getEntity();

            String result = EntityUtils.toString(httpEntity, "utf-8");
			//断言结果为成功
            Assert.assertEquals(result, "success");

        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }
6.进一步封装,通过restTemplate实现post请求,进行简单的响应结果断言,

restTemolate有好多种实现方法,自行了解下,这里只引入本案例用到的

  • 还是一样,先来看一下即将要实现的接口curl
curl --location --request POST 'http://arsenalgw.qa.elong.com/xx/x/xxx/agentWithVerifyPhone' \
--header 'Cookie:  SessionToken=txEY4M/+xxx+u92yQzQx3XNFXmQw=; H5CookieId=af7c197f-0558-47b6-b376-xx' \
--data-raw '{
	"registerPhone": "15400000001",
	"inviteAgentId": "547",
	"role": "client"
}'
  • 实现接口代码片段
@SpringBootTest //继承接口实现了请求头以及数据库。进程进来时,会先执行继承类,获取到里面的值
public class AgentWithVerifyPhone_Clint extends ArsenalBaseTestNG {

	//自动注入,免去new对象的麻烦,直接调用类 DeleteUserInformation,往上翻看就会发现这是上面介绍的删除类
    @Autowired
    DeleteUserInformation deleteUserInformation;

    // 请求接口
    private String api = "/xx/create/agentWithVerifyPhone";

    /**
     * 接口描述:裂变用户注册
     * 方法:POST
     * 预期:注册成功与邀请人形成上下级关系
     */
    @Test(priority = 1)
    @Parameters({"seesionToken", "phoneNo", "inviteAgentId"})
    public void testClint(String seesionToken, String phoneNo, String inviteAgentId) {

        deleteuserid();
        
		 // 请求完整url拼接
        String url = apiHost.concat(api);

        // 拼接header
        HttpHeaders headers = new HttpHeaders();
        headers.add("Cookie", "SessionToken=" + seesionToken);
        headers.add("token", token);
        headers.add("traceSource", traceSource);

        //获取请求参数jsonbody
        JSONObject params = new JSONObject();
        params.put("registerPhone", "client");
        params.put("role", "client");
        params.put("inviteAgentId", "112");
        
        ResEntity resEntity = post(url, headers, params);
        System.out.println("代理商注册响应:" + resEntity);

        //断言请求接口响应200
        Assert.assertEquals(resEntity.getCode(), 200, "请求状态非200");

        String code = resEntity.getBody().getString("code");
        Assert.assertEquals(code, "0", "注册失败");
        
    }

		//执行接口,可以抽出来封装一下
        RestTemplate restTemplate;
   	    public ResEntity post(String url, HttpHeaders headers, JSONObject jsonBody) {

        //创建实例类,方便后续get
        ResEntity resEntity = new ResEntity();

        HttpEntity<String> httpEntity = new HttpEntity(jsonBody, headers);

        ResponseEntity<JSONObject> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, JSONObject.class);
        HttpStatus status = responseEntity.getStatusCode();

        //把响应code码、body内容存放到bean里
        resEntity.setCode(status.value());
        resEntity.setBody(responseEntity.getBody());

        return resEntity;
    }
//
//    @Data
//    @NoArgsConstructor
//    @AllArgsConstructor
//    public class ResEntity {
//
//        private int code;
//        private JSONObject body;
//
//    }
    }
  • 父类代码 ArsenalBaseTestNG
@SpringBootTest
public class ArsenalBaseTestNG extends AbstractTestNGSpringContextTests {

    @Parameters({"apiHost", "token", "traceSource", "dburl", "dbname"})
    @BeforeClass //执行类之前的运行方法
    public void initParameters(String apiHost, String token, String traceSource, String dburl, String dbname) {
       //可以把接口中通用的url拼接,header头信息等放到这里,统一管理,就不用每次写接口都重复写一遍重复的代码

    }

    @BeforeMethod //方法执行之前执行注解,只是为了日志输出漂亮点的分割线
    public void testStart(Method method) {
        System.out.println(">>>>>>>>>>>>>>>>>>>> Test case >>> " + method.getName());
    }

    @AfterClass //类执行后的方法
    public void finishTest() {
        System.out.println("====== End Test Class ".concat(this.getClass().getName()).concat(" ======"));
    }

}

7. 引入jdbc链接数据库
package com.ly.qa.api.arsenal.common;

import org.springframework.context.annotation.Configuration;

import java.sql.*;
import java.util.*;

/**
 * 数据库操作工具
 *
 * @author junjun.dai
 */
@Configuration
public class DbUtilsQA {

    static Connection conn = null;

    public static String driverClassName = "com.mysql.jdbc.Driver";
    public static String url = "jdbc:mysql://10.160.172.45:3046/数据库名称";
    public static String username = "TEArsenalMarketing";
    public static String password = "xxx";


    /**
     * 执行sql
     *
     * @param sql     sql语句
     * @return
     */
    public static List<Map<String, String>> getDataList(String sql,String[] columns) {
        List<Map<String, String>> paramList = new ArrayList<Map<String, String>>();
        Map<String, String> param = new HashMap<>();
        Statement stmt = null;
        try {
            // 注册 JDBC 驱动
            Class.forName(driverClassName);
            // 打开链接
            conn = DriverManager.getConnection(url, username, password);
            // 执行查询
            stmt = conn.createStatement();
            ResultSet rs = null;
            rs = stmt.executeQuery(sql);
            // 展开结果集数据库
            while (rs.next()) {
                Map<String, String> map = new LinkedHashMap<String, String>();
                for (int i = 0; i < columns.length; i++) {
                    String cellData = rs.getString(columns[i]);
                    map.put(columns[i], cellData);
                }
                paramList.add(map);
            }
            // 完成后关闭
            rs.close();
            stmt.close();
            conn.close();
        } catch (SQLException se) {
            // 处理 JDBC 错误
            System.out.println("处理 JDBC 错误!");
        } catch (Exception e) {
            // 处理 Class.forName 错误
            System.out.println("处理 Class.forName 错误");
        } finally {
            // 关闭资源
            try {
                if (stmt != null) stmt.close();
                if (conn != null) conn.close();
            } catch (SQLException se) {
                se.printStackTrace();
            }
        }
        return paramList;
    }

    /**
     * 更新sql
     *
     * @param sql
     * @return
     */
    public static Boolean executeSQL(String sql) {
        Statement stmt = null;
        boolean execute = Boolean.FALSE;
        try {
            // 注册 JDBC 驱动
            Class.forName(driverClassName);
            // 打开链接
            conn = DriverManager.getConnection(url, username, password);
            // 执行查询
            stmt = conn.createStatement();
            execute = stmt.execute(sql);
            // 完成后关闭
            stmt.close();
            conn.close();
        } catch (SQLException se) {
            // 处理 JDBC 错误
            System.out.println("处理 JDBC 错误!");
        } catch (Exception e) {
            // 处理 Class.forName 错误
            System.out.println("处理 Class.forName 错误");
        } finally {
            // 关闭资源
            try {
                if (stmt != null) stmt.close();
                if (conn != null) conn.close();
            } catch (SQLException se) {
                se.printStackTrace();
            }
        }
        return execute;
    }


    public List<Map<String, String>> dbDataMethod( String sql,String[] columns) {
//        String sql = "SELECT * FROM `activity_trace` WHERE template_id='552'";
        List<Map<String, String>> result = getDataList(sql,columns);
        return result;
    }

    public Boolean dbDataIsNull(String sql, String[] columns) {
        List<Map<String, String>> result = getDataList(sql,columns);
        return result.isEmpty();
    }

    public Boolean execute(String sql) {

        return executeSQL(sql);
    }


}

8.接口类中,查询库数据与接口响应body做对比,断言数据库
@SpringBootTest
public class AgentCodeClientAgentId extends AbstractTestNGSpringContextTests {

    @Autowired
    RestUtils requests;
    @Autowired //引入数据库
    DbUtilsQA dbUtils;

    // 请求接口
    private String api = "/hotel/xxx/xxx/agentcode";

    /**
     * 预期:裂变用户注册
     * 1、通过seesionToken判断用户是否已存在,已存在进行删除
     * 2、裂变用户进行注册。响应成功 msg:success。注册成功与邀请人形成上下级关系
     * 备注:˙注册成功,裂变用户为相对二级用户。一级用户id:112(agent表中用户id)
     */

    @Test(groups = "client",priority = 3)
    @Parameters({"apiHost", "seesionToken", "token", "traceSource"})
    public void testClient(String apiHost, String seesionToken, String token, String traceSource) {

        // 请求完整url拼接
        String url = apiHost.concat(api);

        // 拼接header
        HttpHeaders headers = new HttpHeaders();
        headers.add("Cookie", "SessionToken=" + seesionToken);
        headers.add("token", token);
        headers.add("traceSource", traceSource);

        //获取请求参数jsonbody
        JSONObject params = new JSONObject();
        params.put("role", "client");
        params.put("inviteAgentId", "112");

        ResEntity resEntity = requests.post(url, headers, params);
        System.out.println("代理商注册响应:" + resEntity);

        //断言请求接口响应200
        Assert.assertEquals(resEntity.getCode(), 200, "请求状态200");

		//返回数据是json串,取json中key:date。
		//{"code":"codeExpire","msg":"success","data":{"agentId":"202007081038"},"responseTime":"2021-04-27 16:41:54"}
        String agentId = resEntity.getBody().getJSONObject("data").getString("agentId");
        String[] columns_role = {"agent_role", "parent_agent_id"};
        String querySql = "select * from distribution_agent where agent_member_id =" + agentId;

        List<Map<String, String>> mapList = dbUtils.dbDataMethod(querySql, columns_role);

        Assert.assertFalse(mapList.isEmpty());

        for (Map<String, String> map : mapList) {
            Assert.assertEquals(map.get("agent_role"), "裂变用户", "角色注册不正确");
            Assert.assertEquals(map.get("parent_agent_id"), "190000000032799250", "父级关联关系不正确");
        }

        System.out.println("-------------裂变用户角色注册 自动化测试通过----------------");
        System.out.println();

    }
9.公共类参数,把接口中的数据,存放在公共类中,用以其他需要的接口的请求入参传入

在这里插入图片描述
存放和取值的代码片段

        //把接口响应数据,存到公共变量里
        distribution_param.inviteIdentityCode = data;
		
		//获取公共类中的参数。一般是一个xml执行时,顺序存放,顺序取值。
        String pagedata = distribution_param.inviteIdentityCode;
			
10. 右键xml,run执行接口

在这里插入图片描述

11. tips:
  1. 入参json层次比较深的话,用object对象代码会比较清晰
    post接口请求body
    在这里插入图片描述
    在这里插入图片描述
    对应取值方式,反向解析
    在这里插入图片描述
  2. 如果层级不深或者后续使用参数较少,可以直接text全部引入
    在这里插入图片描述
  3. 这里也可以引用mock的数据,当做入参。
    比如,在mock平台维护好需要的参数 (一般用作参数改动频繁,或者依赖外部参数传递,保证内部实现逻辑正确的情况)
    mock维护的数据展示
    在这里插入图片描述
    通过RestTemplate方法调用执行接口,贴代码片段
@SpringBootTest
public class OrderSync extends ArsenalBaseTestNG {

    // 请求接口
    private String api = "/hotel/xxx/xxx/sync";
    //mock配置的接口地址信息
    private String caseDataApi = "/arsenal/cases/hotel/distribution/order/sync";
    public String apiHost = "http://mock.17usoft.com";

    //订单号id随机数。利用时间戳13位
    Date date = new Date();
    String bizOrderNo = String.valueOf(date.getTime());

    JSONObject raw;

    @BeforeTest
    public void initTestData() {

        // rul = http://mock.17usoft.com/hotel/arsenal/cases/hotel/distribution/order/sync
        String url = apiHost.concat(caseDataApi);

        //类似于 Apache的HttpClient,使用更简单粗暴
        //getForObject这三个参数分别代表 请求地址、HTTP响应转换被转换成的对象类型。
        RestTemplate restTemplate = new RestTemplate();
		//获取到mock中维护的json,存放到raw对象中
        raw = restTemplate.getForObject(url, JSONObject.class);
    }
    /**
     * 描述:操作订单推送
     * 预期,code=0
     */
    @Test
    @Parameters({"seesionToken", "instanceid"})
    public void testSync(String seesionToken, String instanceid) {


        // 拼接header
        headers.add("Cookie", "SessionToken=" + seesionToken);
		//参数一直变化的,单独put进来
        raw.put("instanceId",instanceid);
        raw.put("updateTimestamp","1594958297883");
        raw.put("bizOrderNo",bizOrderNo);

		//该请求方式,上面的代码片段中描述过,依然是使用restTmplate的一个封装调用
        ResEntity resEntity = requests.postFromApi(api, headers, raw);

        //断言请求接口响应200
        Assert.assertEquals(resEntity.getCode(), 200, "请求状态200");
        //断言响应code=0,推送成功。
        Assert.assertEquals(resEntity.getBody().getString("code"), "0", "订单推送操作");
    }
}

main 方法测试一下,不难发现raw中的数据,就是在mock中配置好的。
在这里插入图片描述

  • 1
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在进行接口自动化测试时,我们通常需要对接口进行请求并获取返回结果。而获取返回结果的方式一般有两种,一种是通过 HTTP 响应获取,另一种是通过解析 JSON 格式的响应体获取。 对于第一种方式,我们可以利用 Java 提供的 HttpURLConnection 类或者 Apache 的 HttpClient 库进行操作。而对于第种方式,我们可以使用 JSON 解析库来解析响应体。 下面介绍通过 HttpURLConnection 获取响应体的方法: ```java import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; public class HttpUtil { public static String doGet(String urlStr) { try { URL url = new URL(urlStr); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line; StringBuilder result = new StringBuilder(); while ((line = in.readLine()) != null) { result.append(line); } in.close(); return result.toString(); } catch (Exception e) { e.printStackTrace(); } return null; } } ``` 上面的代码中,我们使用了 Java 提供的 HttpURLConnection 类来发送 GET 请求,并获取响应体。其中,URL 用于指定请求的地址,HttpURLConnection 用于建立与指定 URL 的连接,并发送请求。通过 BufferedReader 来读取响应体,并将其转换为字符串返回。 使用该方法可以轻松地获取到接口返回的响应体。接下来,我们可以通过 JSON 解析库来解析响应体,从而获取接口返回的具体数据。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值