java基础 - 实现一个简单的Http接口功能自动化测试框架(HttpClient + TestNG)

这篇具有很好参考价值的文章主要介绍了java基础 - 实现一个简单的Http接口功能自动化测试框架(HttpClient + TestNG)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

已知现在已经用Spring boot框架搭建了一个简单的web服务,并且有现成的Controller来处理http请求,以之前搭建的图书管理服务为例,BookController的源码如下:

package org.example.controller;

import org.example.domain.Book;
import org.example.service.BookService;
import org.example.vo.ResultVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping(value = "/books")
public class BookController {

    @Autowired
    private BookService bookService;

    /**
     * 查询所有图书
     *
     * @return
     */
    @GetMapping(value = "/getAll")
    public ResultVo getAll() {
        List<Book> books = bookService.getAllBooks();
        return new ResultVo().success().data(books);
    }

    /**
     * 更新单个图书信息
     *
     * @param book
     * @return
     */
    @PostMapping(value = "/update")
    public ResultVo updateBookMessage(@RequestBody Book book) {
        Boolean result = bookService.updateBookMessage(book);
        ResultVo resultVo = new ResultVo().data(result);
        return result ? resultVo.success() : resultVo.fail();
    }
}

在搭建一个Http接口功能自动化测试框架之前,我们需要思考几个问题:
1、http请求的发送,使用什么实现?
2、接口返回的json数据,用什么校验?
3、测试数据如何统一管理(数据驱动)?比如测试url、post请求需要的请求体、接口预期的返回结果等。
4、失败用例是否支持重试?

1、http请求工具类

目前网上能查到的比较主流的技术方案包括: java.net包中原生的 HttpURLConnection类、apache名下的http组件 HttpClient,以及Spring框架的 RestTemplate。相比之下,HttpURLConnection使用较为复杂,RestTemplate又不熟,就先选择HttpClient。

首先还是要引入HttpClient依赖(slf4j打log用):

        <dependency>
            <groupId>org.apache.httpcomponents.client5</groupId>
            <artifactId>httpclient5</artifactId>
            <version>5.1</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
        </dependency>

工具类源码如下,暂且先满足get 和 post两类请求的发送:

package org.example.utils;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class HttpUtil {

    public static JSONObject doGet(String url) {

        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);

        try {
            CloseableHttpResponse response = httpClient.execute(httpGet);
            HttpEntity entity = response.getEntity();
            String responseText = EntityUtils.toString(entity);
            JSONObject responseJson = JSON.parseObject(responseText);
            response.close();
            httpClient.close();
            return responseJson;
        } catch (IOException | ParseException e) {
            throw new RuntimeException(e);
        }
    }

    public static JSONObject doPost(String url, JSONObject requestBody) {

        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);

        StringEntity requestEntity = new StringEntity(requestBody.toString(), StandardCharsets.UTF_8);
        httpPost.setEntity(requestEntity);
        httpPost.setHeader("Content-Type", "application/json");

        try {
            CloseableHttpResponse response = httpClient.execute(httpPost);
            HttpEntity httpEntity = response.getEntity();
            String responseText = EntityUtils.toString(httpEntity);
            JSONObject responseJson = JSON.parseObject(responseText);
            response.close();
            httpClient.close();
            return responseJson;
        } catch (IOException | ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

2、接口返回校验

这里主要是校验预期的返回体和实际返回体是否完全一致,也就是两个json数据的解析和比对。
json数据的解析工具很多,比如Google的Gson,阿里的fastjson,以及jackson等,这里随便选一个,使用fastjson;
json的比对,据调研有一个开源的org.skyscreamer名下的 jsonassert工具,不过还没有试用。这里自己写一个json diff的工具类,仅判断两份json数据是否相同,暂不考虑性能和其他功能拓展。

首先引入fastjson依赖:

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.32</version>
        </dependency>

json diff 工具类源码如下:

package org.example.utils;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Set;

public class JsonDiffUtil {

    private static final Logger LOG = LoggerFactory.getLogger(JsonDiffUtil.class);

    public static boolean compareJson(JSONObject expect, JSONObject actual) {
        return compareJson(expect, actual, true, false);
    }

    //sorted参数用于判断数组中json object的顺序是否一致
    public static boolean compareJson(JSONObject expect, JSONObject actual, boolean sorted) {
        return compareJson(expect, actual, true, sorted);
    }

    //reportError参数:遍历json数组时,不匹配的json对象不需要打印error日志,故使用此参数控制
    private static boolean compareJson(JSONObject expect, JSONObject actual, boolean reportError, boolean sorted) {
        if (expect == null || actual == null) {
            throw new RuntimeException("预期结果或返回结果不能为空");
        }
        if (expect.toString().equals(actual.toString())) {
            return true;
        }
        Set<String> keySet = expect.keySet();
        for (String key : keySet) {
            if (!actual.containsKey(key)) {
                if (reportError) {
                    LOG.error("key: {}不存在", key);
                    LOG.info("完整返回数据: {}", actual);
                }
                return false;
            }
            Object o1 = expect.get(key);
            Object o2 = actual.get(key);
            if (!isSameType(o1, o2)) {
                if (reportError) {
                    LOG.error("类型不匹配, key: {}, 预期值: {}, 实际值: {}", key, o1.getClass().getTypeName(), o2.getClass().getTypeName());
                    LOG.info("完整返回数据: {}", actual);
                }
                return false;
            }
            if (o1 instanceof JSONArray) {
                JSONArray ja1 = (JSONArray) o1;
                JSONArray ja2 = (JSONArray) o2;
                if (ja1.size() != ja2.size()) {
                    if (reportError) {
                        LOG.error("json数组长度不匹配, key: {}, 预期值: {}, 实际值: {}", key, o1, o2);
                        LOG.info("完整返回数据: {}", actual);
                    }
                    return false;
                }
                for (int i = 0; i < ja1.size(); i++) {
                    JSONObject jo1 = ja1.getJSONObject(i);
                    for (int j = 0; j < ja2.size(); j++) {
                        JSONObject jo2 = ja2.getJSONObject(j);
                        if (compareJson(jo1, jo2, false, sorted)) {
                            break;
                        }
                        if (j == ja2.size() - 1) {
                            LOG.error("key: {}, json数组中找不到预期的json对象{}", key, jo1);
                            LOG.info("完整返回数据: {}", actual);
                            return false;
                        }
                    }
                }
                if (sorted && !ja1.toString().equals(ja2.toString())) {
                    LOG.error("key: {}, json数组中对象顺序不一致, 预期值: {}, 实际值: {}", key, ja1, ja2);
                    LOG.info("完整返回数据: {}", actual);
                    return false;
                }
            } else if (o1 instanceof JSONObject) {
                if (!compareJson((JSONObject) o1, (JSONObject) o2, true, sorted)) {
                    return false;
                }
            } else {
                String s1 = String.valueOf(o1);
                String s2 = String.valueOf(o2);
                if (!s1.equals(s2)) {
                    if (reportError) {
                        LOG.error("key: {}, 预期值: {}, 实际值: {}", key, o1, o2);
                        LOG.info("完整返回数据: {}", actual);
                    }
                    return false;
                }
            }
        }
        return true;
    }

    private static boolean isSameType(Object o1, Object o2) {
        if (o1 == null && o2 == null) {
            return true;
        }
        if (o1 == null || o2 == null) {
            return false;
        }
        return o1.getClass().getTypeName().equals(o2.getClass().getTypeName());
    }
}

3、数据驱动

数据驱动是一个概念,可以简单理解为测试数据和测试用例的分离,也就是分开管理,然后数据通过参数的形式注入到测试用例中。
通常我们可以使用testNG框架的@DataProvider注解来实现这一功能。

首先导入testNG依赖(commons-io,用来处理IO的工具类):

        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.11</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.8.0</version>
        </dependency>

DataProvider工具类的源码如下:
它的作用是根据当前需要提供数据的Method,找到相关的测试数据(json文件),将文件内容解析为方法参数,传递给测试case。

package org.example.utils;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.apache.commons.io.FileUtils;
import org.testng.annotations.DataProvider;

import java.io.*;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;

public class DataProviderUtil {

    @DataProvider(name = "dataProvider")
    public static Object[] provide(Method method) throws IOException {

        String className = method.getDeclaringClass().getSimpleName();
        String methodName = method.getName();
        String fileName = "src/main/java/org/example/data/" + className + ".json";

        File file = new File(fileName);
        String data = FileUtils.readFileToString(file, StandardCharsets.UTF_8);

        if (!JSON.isValid(data)) {
            throw new RuntimeException("json格式校验失败, 文件名: " + fileName);
        }
        JSONArray jsonArray = JSON.parseArray(data);
        List<Object> list = jsonArray.stream().filter(obj -> methodName.equals(((JSONObject) obj).getString("caseName"))).collect(Collectors.toList());
        JSONObject jsonObject = (JSONObject) list.get(0);

        String url = jsonObject.getString("url");
        JSONObject requestBody = jsonObject.getJSONObject("requestBody");
        JSONObject expectResponse = jsonObject.getJSONObject("expectResponse");

        if (url == null) {
            throw new RuntimeException("参数 url 不能为空, 用例: " + className + "." + methodName);
        }
        if (expectResponse == null) {
            throw new RuntimeException("参数 expectResponse 不能为空, 用例: " + className + "." + methodName);
        }

        return new Object[][]{{url, requestBody, expectResponse}};
    }
}

然后测试用例方法的@Test注解中需要指定该类作为数据提供者,比如:
@Test(dataProvider = “dataProvider”, dataProviderClass = DataProviderUtil.class)

小结:
解决了以上三个核心问题之后,实际上我们已经可以轻松的编写测试用例了,比如要测试BookController相关的http接口,建立一个BookControllerTest作为测试类,里面为每个接口设计一个case,源码如下:

package org.example.testcase;

import com.alibaba.fastjson2.JSONObject;
import org.example.utils.DataProviderUtil;
import org.example.utils.HttpUtil;
import org.example.utils.JsonDiffUtil;
import org.testng.Assert;
import org.testng.annotations.Test;

public class BookControllerTest {

    @Test(dataProvider = "dataProvider", dataProviderClass = DataProviderUtil.class)
    public void getAllTest(String url, JSONObject requestBody, JSONObject expectResponse) {
        JSONObject response = HttpUtil.doGet(url);
        Assert.assertTrue(JsonDiffUtil.compareJson(expectResponse, response));
    }

    @Test(dataProvider = "dataProvider", dataProviderClass = DataProviderUtil.class)
    public void updateTest(String url, JSONObject requestBody, JSONObject expectResponse) {
        JSONObject response = HttpUtil.doPost(url, requestBody);
        Assert.assertTrue(JsonDiffUtil.compareJson(expectResponse, response));
    }
}

4、testNG对失败重试的支持

失败用例的重试,需要定义两个类,分别需要实现 IAnnotationTransformer监听器接口,来指定重试处理类;以及 IRetryAnalyzer接口,来制定重试规则。源码如下:

package org.example.listener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.*;
import org.testng.annotations.ITestAnnotation;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class RetryListener implements IAnnotationTransformer {

    private static final Logger LOG = LoggerFactory.getLogger(RetryListener.class);

    @Override
    public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
        IRetryAnalyzer analyzer = annotation.getRetryAnalyzer();
        if (analyzer == null) {
            annotation.setRetryAnalyzer(Retry.class);
        }
    }

    public static class Retry implements IRetryAnalyzer {

        private int currentRetryCount = 0;

        @Override
        public boolean retry(ITestResult result) {
            //重试2次
            if (currentRetryCount < 2) {
                currentRetryCount++;
                LOG.info("准备第" + currentRetryCount + "次重试");
                return true;
            }
            return false;
        }
    }

}

然后需要在制定测试规则的 testng.xml文件中,使用 listener 标签添加该监听器。
testng.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">

<suite name="Default Suite">
    <test name="Default Test">
        <classes>
            <class name="org.example.testcase.BookControllerTest">
                <methods>
                    <include name="getAllTest"/>
                    <include name="updateTest"/>
                </methods>
            </class>
        </classes>
    </test>
    <listeners>
        <listener class-name="org.example.listener.RetryListener"/>
    </listeners>
</suite>

到这里,重试功能就添加完毕了。运行效果如下,最后一次重试失败,则最终定性为失败,之前的按 ignored处理。
testng实现接口测试,java,http,自动化
最后,整个项目的结构图:
testng实现接口测试,java,http,自动化文章来源地址https://www.toymoban.com/news/detail-752817.html

到了这里,关于java基础 - 实现一个简单的Http接口功能自动化测试框架(HttpClient + TestNG)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 用Java包com.sun.net.httpserver下面的类实现一个简单的http服务器demo

    java的com.sun.net.httpserver包下的类提供了一个高层级的http服务器API,可以用来构建内嵌的http服务器。支持http和https。这些API提供了一个RFC 2616 (HTTP 1.1)和RFC 2818 (HTTP over TLS)的部分实现。 https://docs.oracle.com/en/java/javase/19/docs/api/jdk.httpserver/com/sun/net/httpserver/package-summary.html 下面来实

    2024年02月07日
    浏览(23)
  • 简单实现接口自动化测试(基于python)

    本文从一个简单的登录接口测试入手,一步步调整优化接口调用姿势,然后简单讨论了一下接口测试框架的要点,最后介绍了一下我们目前正在使用的接口测试框架pithy。期望读者可以通过本文对接口自动化测试有一个大致的了解。 为什么要做接口自动化测试? 在当前互联网

    2024年02月13日
    浏览(25)
  • (基于python)简单实现接口自动化测试

    本文从一个简单的登录接口测试入手,一步步调整优化接口调用姿势,然后简单讨论了一下接口测试框架的要点,最后介绍了一下我们目前正在使用的接口测试框架pithy。期望读者可以通过本文对接口自动化测试有一个大致的了解。 为什么要做接口自动化测试? 在当前互联网

    2024年02月08日
    浏览(26)
  • 基于Python简单实现接口自动化测试(详解)

    本文从一个简单的登录接口测试入手,一步步调整优化接口调用姿势,然后简单讨论了一下接口测试框架的要点,最后介绍了一下我们目前正在使用的接口测试框架pithy。期望读者可以通过本文对接口自动化测试有一个大致的了解。 为什么要做接口自动化测试? 在当前互联网

    2024年01月20日
    浏览(25)
  • 简单实现接口自动化测试(基于python+unittest)

    本文通过从Postman获取基本的接口测试Code简单的接口测试入手,一步步调整优化接口调用,以及增加基本的结果判断,讲解Python自带的Unittest框架调用,期望各位可以通过本文对接口自动化测试有一个大致的了解。 为什么要做接口自动化测试? 在当前互联网产品迭代频繁的背景

    2024年02月07日
    浏览(35)
  • 【Java】一个简单的接口例子(帮助理解接口+多态)

    要求: 请实现笔记本电脑使用USB 鼠标、 USB 键盘的例子 1. USB 接口:包含打开设备、关闭设备功能 2. 笔记本类:包含开机功能、关机功能、使用 USB 设备功能 3. 鼠标类:实现 USB 接口,并具备点击功能 4. 键盘类:实现 USB 接口,并具备输入功能 (不需要具体实现,给出框架即

    2024年02月16日
    浏览(20)
  • Powershell脚本自动化登录网站的简单实例,命令行方式实现Http(s)的GET、POST请求

    自动化登录网站的流程比较简单,如果不懂 Python、JavaScript、C++ 等编程语言,又没有安装这些编程语言环境软件,我们还要新的点子:用Windows系统自带的 Powershell 运行自编的脚本来实现。 PowerShell 是一种功能强大的自动化工具,除了可以使用 DOS 批处理命令之外,还可以进行

    2024年02月10日
    浏览(28)
  • 接口自动化测试框架(Java 实现)

    需求点 需求分析 通过 yaml 配置接口操作和用例 后续新增接口和测试用例只需要编写 yaml 文件即可实现。 可以支持接口间的参数传递 具有参数依赖的接口可以进行变量的抽取和参数赋值。 支持全局、用例维度的变量存储 比如时间截命名法中要用到的时间截后缀。 支持用例

    2024年01月18日
    浏览(28)
  • 基于 Python 实现一个简单的 HTTP 服务器

    文章这个题目,让我想起了大学时上《Unix 网络编程》这门专业课的家庭作业,题目几乎一模一样。 HTTP 服务器工作在服务端,主要功能包括处理来自客户端的请求,管理网络资源,以及生成和发送响应给客户端。在实际应用中,HTTP 服务器不仅限于传输 HTML 文档;它还可以传

    2024年03月22日
    浏览(34)
  • 【Java】接口自动化测试是什么,如何实现?

    接口自动化测试是一种自动检查接口功能、性能和可靠性的测试方法。它可以帮助开发人员快速发现接口中的问题,提高开发速度和质量。 接口自动化测试的实现主要包括以下几个步骤: 选择合适的工具:首先,你需要选择一个合适的自动化测试工具,如Postman、JMeter、Res

    2024年02月08日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包