使用 testng 做接口自动化测试 0.1 搭建框架

代码仓库

https://gitee.com/bzrj/testng-study/tree/v0.1/

参考资料

testng

官网

testng 官网: https://testng.org/

testng github: https://github.com/testng-team/testng

博客

接口自动化测试

TestNg+Allure框架搭建

java版集成Allure报告--注解的强大

单篇长文TestNG从入门到精通

TestNG 易百教程

TestNG 系列博客 df0128的专栏

okhttp

官网

okhttp 官网: https://square.github.io/okhttp/

okhttp github: https://github.com/square/okhttp

博客

OkHttp官方教程解析-彻底入门OkHttp使用

HttpClient、OKhttp、RestTemplate接口调用对比,选择一个优秀的 HTTP Client 的重要性

基本使用——OkHttp3详细使用教程

使用OkHttp3发起POST或GET请求

网络请求框架OkHttp3全解系列(一):OkHttp的基本使用

网络请求框架OkHttp3全解系列 - (二)OkHttp的工作流程分析

网络请求框架OkHttp3全解系列 - (三)拦截器详解1:重试重定向、桥、缓存(重点)

你想要的系列:网络请求框架OkHttp3全解系列 - (四)拦截器详解2:连接、请求服务(重点)

OKhttp源码解析详解系列

OkHttp工具类封装,更优雅的调用接口

拦截http请求打印入参出参(Okhttp3拦截器)

简单的 Get 和 Post 请求

package com.laolang.thresh.test;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.testng.Assert;
import org.testng.annotations.Test;

@Slf4j
public class OkHttpSimpleTest {

    private static final String BASE_URL = "http://localhost:8092";

    private static final MediaType mediaType = MediaType.parse("application/json;charset=UTF-8");

    @Test
    public void testGet() throws IOException {
        OkHttpClient client = new OkHttpClient();
        String url = BASE_URL + "/admin/system/dict/groupList";
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        try (Response response = client.newCall(request).execute()) {
            ResponseBody body = response.body();
            Assert.assertNotNull(body);
            String bodyString = body.string();
            log.info("result:{}", bodyString);
            log.info("result:{}", JSONUtil.toJsonPrettyStr(JSONUtil.parseObj(bodyString)));
        }
    }

    @Test
    public void testPost() throws IOException {
        OkHttpClient client = new OkHttpClient();
        String url = BASE_URL + "/admin/system/dict/list";
        JSONObject jsonParam = new JSONObject();
        jsonParam.set("name", "用户性别");

        RequestBody requestBody = RequestBody.create(JSONUtil.toJsonStr(jsonParam), mediaType);
        Request request = new Request.Builder()
                .post(requestBody)
                .url(url)
                .build();
        try (Response response = client.newCall(request).execute()) {
            ResponseBody body = response.body();
            Assert.assertNotNull(body);
            String bodyString = body.string();
            log.info("result:{}", bodyString);
            log.info("result:{}", JSONUtil.toJsonPrettyStr(JSONUtil.parseObj(bodyString)));
        }
    }
}

缓存

Java开发利器Guava Cache之使用篇

Guava 本地缓存介绍, 以及缓存主动刷新缓存功能

效果

D:\program\java\jdk8\jdk\bin\java.exe -ea -Dfile.encoding=UTF-8 -javaagent:C:\Users\laolang\.m2\repository/org/aspectj/aspectjweaver/1.9.6/aspectjweaver-1.9.6.jar -Didea.test.cyclic.buffer.size=1048576 -javaagent:D:\program\java\ideaIC-2023.3.4.win\lib\idea_rt.jar=13010:D:\program\java\ideaIC-2023.3.4.win\bin -classpath D:\program\java\ideaIC-2023.3.4.win\lib\idea_rt.jar;D:\program\java\ideaIC-2023.3.4.win\plugins\testng\lib\testng-rt.jar;D:\program\java\jdk8\jdk\jre\lib\charsets.jar;D:\program\java\jdk8\jdk\jre\lib\deploy.jar;D:\program\java\jdk8\jdk\jre\lib\ext\access-bridge-64.jar;D:\program\java\jdk8\jdk\jre\lib\ext\cldrdata.jar;D:\program\java\jdk8\jdk\jre\lib\ext\dnsns.jar;D:\program\java\jdk8\jdk\jre\lib\ext\jaccess.jar;D:\program\java\jdk8\jdk\jre\lib\ext\jfxrt.jar;D:\program\java\jdk8\jdk\jre\lib\ext\localedata.jar;D:\program\java\jdk8\jdk\jre\lib\ext\nashorn.jar;D:\program\java\jdk8\jdk\jre\lib\ext\sunec.jar;D:\program\java\jdk8\jdk\jre\lib\ext\sunjce_provider.jar;D:\program\java\jdk8\jdk\jre\lib\ext\sunmscapi.jar;D:\program\java\jdk8\jdk\jre\lib\ext\sunpkcs11.jar;D:\program\java\jdk8\jdk\jre\lib\ext\zipfs.jar;D:\program\java\jdk8\jdk\jre\lib\javaws.jar;D:\program\java\jdk8\jdk\jre\lib\jce.jar;D:\program\java\jdk8\jdk\jre\lib\jfr.jar;D:\program\java\jdk8\jdk\jre\lib\jfxswt.jar;D:\program\java\jdk8\jdk\jre\lib\jsse.jar;D:\program\java\jdk8\jdk\jre\lib\management-agent.jar;D:\program\java\jdk8\jdk\jre\lib\plugin.jar;D:\program\java\jdk8\jdk\jre\lib\resources.jar;D:\program\java\jdk8\jdk\jre\lib\rt.jar;E:\gitee\bzrj\testng-study\thresh-test\target\test-classes;E:\gitee\bzrj\testng-study\thresh-test\target\classes;C:\Users\laolang\.m2\repository\org\testng\testng\6.14.3\testng-6.14.3.jar;C:\Users\laolang\.m2\repository\com\beust\jcommander\1.72\jcommander-1.72.jar;C:\Users\laolang\.m2\repository\org\apache-extras\beanshell\bsh\2.0b6\bsh-2.0b6.jar;C:\Users\laolang\.m2\repository\io\qameta\allure\allure-testng\2.27.0\allure-testng-2.27.0.jar;C:\Users\laolang\.m2\repository\io\qameta\allure\allure-java-commons\2.27.0\allure-java-commons-2.27.0.jar;C:\Users\laolang\.m2\repository\io\qameta\allure\allure-model\2.27.0\allure-model-2.27.0.jar;C:\Users\laolang\.m2\repository\io\qameta\allure\allure-test-filter\2.27.0\allure-test-filter-2.27.0.jar;C:\Users\laolang\.m2\repository\org\aspectj\aspectjweaver\1.9.6\aspectjweaver-1.9.6.jar;C:\Users\laolang\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;C:\Users\laolang\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\laolang\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\laolang\.m2\repository\org\projectlombok\lombok\1.18.16\lombok-1.18.16.jar;C:\Users\laolang\.m2\repository\org\apache\commons\commons-lang3\3.9\commons-lang3-3.9.jar;C:\Users\laolang\.m2\repository\com\squareup\okhttp3\okhttp\4.9.0\okhttp-4.9.0.jar;C:\Users\laolang\.m2\repository\com\squareup\okio\okio\2.8.0\okio-2.8.0.jar;C:\Users\laolang\.m2\repository\org\jetbrains\kotlin\kotlin-stdlib-common\1.3.72\kotlin-stdlib-common-1.3.72.jar;C:\Users\laolang\.m2\repository\org\jetbrains\kotlin\kotlin-stdlib\1.3.72\kotlin-stdlib-1.3.72.jar;C:\Users\laolang\.m2\repository\org\jetbrains\annotations\13.0\annotations-13.0.jar;C:\Users\laolang\.m2\repository\cn\hutool\hutool-all\5.8.11\hutool-all-5.8.11.jar;C:\Users\laolang\.m2\repository\com\google\guava\guava\23.0\guava-23.0.jar;C:\Users\laolang\.m2\repository\com\google\code\findbugs\jsr305\1.3.9\jsr305-1.3.9.jar;C:\Users\laolang\.m2\repository\com\google\errorprone\error_prone_annotations\2.0.18\error_prone_annotations-2.0.18.jar;C:\Users\laolang\.m2\repository\com\google\j2objc\j2objc-annotations\1.1\j2objc-annotations-1.1.jar;C:\Users\laolang\.m2\repository\org\codehaus\mojo\animal-sniffer-annotations\1.14\animal-sniffer-annotations-1.14.jar com.intellij.rt.testng.RemoteTestNGStarter -usedefaultlisteners false -socket13009 @w@C:\Users\laolang\AppData\Local\Temp\idea_working_dirs_testng.tmp -temp C:\Users\laolang\AppData\Local\Temp\idea_testng.tmp
2024-06-16 20:15:37.255 [main] INFO  c.l.t.t.testng.listener.ThreshTestngSuiteListener - test suite start
2024-06-16 20:15:37.261 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - DictQueryTest01 start
2024-06-16 20:15:37.330 [main] INFO  com.laolang.thresh.test.testng.base.BaseTest - param: data/dict/dictquerytest01.json
2024-06-16 20:15:37.499 [main] INFO  com.laolang.thresh.test.testng.base.BaseTest - array:[{"methodName":"typeList","config":{"request":{"url":"/admin/system/dict/typeList","method":"get"},"auth":{"username":"superAdmin","password":"123456"}}},{"methodName":"groupList","config":{"request":{"url":"/admin/system/dict/groupList","method":"get"},"auth":{"username":"superAdmin","password":"123456"}}},{"methodName":"list","config":{"request":{"url":"/admin/system/dict/list","method":"post","paramType":"body","param":{"name":"用户性别"}},"auth":{"username":"superAdmin","password":"123456"}}}]
2024-06-16 20:15:37.501 [main] INFO  c.l.thresh.test.module.system.dict.DictQueryTest - database init
2024-06-16 20:15:37.504 [main] INFO  com.laolang.thresh.test.testng.base.BaseTest - methodName: typeList
2024-06-16 20:15:37.507 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - typeList start
2024-06-16 20:15:38.111 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================
2024-06-16 20:15:38.111 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request start
2024-06-16 20:15:38.111 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: http://localhost:8092/auth/login POST
2024-06-16 20:15:38.111 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: {"username":"superAdmin","password":"123456"}
2024-06-16 20:15:38.111 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request end

2024-06-16 20:15:38.174 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response start
2024-06-16 20:15:38.175 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: code: 200 time:58ms
2024-06-16 20:15:38.176 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: content:{"code":"200","success":true,"msg":"操作成功","body":{"token":"eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImZjODdjMDc1LWYzMzgtNDE5Ni04YzE3LWQxOWEyOTYyMGQ4NSJ9.8vHnG5KiYcKyjcD9kR9rF61imzOnv3un1LxWg-J_6KV8Qmtl04FM5R6oT16skeE3uOgKBaRGq2Lfwj627SZJAQ"},"tid":""}
2024-06-16 20:15:38.176 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response end
2024-06-16 20:15:38.176 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================

2024-06-16 20:15:38.181 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================
2024-06-16 20:15:38.181 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request start
2024-06-16 20:15:38.181 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: http://localhost:8092/admin/system/dict/typeList GET
2024-06-16 20:15:38.181 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImZjODdjMDc1LWYzMzgtNDE5Ni04YzE3LWQxOWEyOTYyMGQ4NSJ9.8vHnG5KiYcKyjcD9kR9rF61imzOnv3un1LxWg-J_6KV8Qmtl04FM5R6oT16skeE3uOgKBaRGq2Lfwj627SZJAQ
2024-06-16 20:15:38.181 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
2024-06-16 20:15:38.182 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request end

2024-06-16 20:15:38.191 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response start
2024-06-16 20:15:38.191 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: code: 200 time:9ms
2024-06-16 20:15:38.192 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: content:{"code":"200","success":true,"msg":"操作成功","body":{"types":[{"type":"gender","desc":"用户性别"},{"type":"module","desc":"模块"}]},"extra":null,"tid":"14913643360157760"}
2024-06-16 20:15:38.192 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response end
2024-06-16 20:15:38.192 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================

2024-06-16 20:15:38.192 [main] INFO  c.l.t.test.util.http.function.ResponseTestCode - test result:true
2024-06-16 20:15:38.193 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - typeList success
2024-06-16 20:15:38.268 [main] INFO  com.laolang.thresh.test.testng.base.BaseTest - methodName: groupList
2024-06-16 20:15:38.268 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - groupList start
2024-06-16 20:15:38.270 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================
2024-06-16 20:15:38.270 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request start
2024-06-16 20:15:38.270 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: http://localhost:8092/admin/system/dict/groupList GET
2024-06-16 20:15:38.270 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImZjODdjMDc1LWYzMzgtNDE5Ni04YzE3LWQxOWEyOTYyMGQ4NSJ9.8vHnG5KiYcKyjcD9kR9rF61imzOnv3un1LxWg-J_6KV8Qmtl04FM5R6oT16skeE3uOgKBaRGq2Lfwj627SZJAQ
2024-06-16 20:15:38.270 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request end

2024-06-16 20:15:38.279 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response start
2024-06-16 20:15:38.279 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: code: 200 time:9ms
2024-06-16 20:15:38.279 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: content:{"code":"200","success":true,"msg":"操作成功","body":{"groups":[{"group":"system","desc":"系统"}]},"extra":null,"tid":"14913643365859392"}
2024-06-16 20:15:38.280 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response end
2024-06-16 20:15:38.280 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================

2024-06-16 20:15:38.280 [main] INFO  c.l.t.test.util.http.function.ResponseTestCode - test result:true
2024-06-16 20:15:38.280 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - groupList success
2024-06-16 20:15:38.283 [main] INFO  com.laolang.thresh.test.testng.base.BaseTest - methodName: list
2024-06-16 20:15:38.283 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - list start
2024-06-16 20:15:38.285 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================
2024-06-16 20:15:38.285 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request start
2024-06-16 20:15:38.285 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: http://localhost:8092/admin/system/dict/list POST
2024-06-16 20:15:38.285 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImZjODdjMDc1LWYzMzgtNDE5Ni04YzE3LWQxOWEyOTYyMGQ4NSJ9.8vHnG5KiYcKyjcD9kR9rF61imzOnv3un1LxWg-J_6KV8Qmtl04FM5R6oT16skeE3uOgKBaRGq2Lfwj627SZJAQ
2024-06-16 20:15:38.285 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: {"name":"用户性别"}
2024-06-16 20:15:38.285 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request end

2024-06-16 20:15:38.400 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response start
2024-06-16 20:15:38.400 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: code: 200 time:115ms
2024-06-16 20:15:38.400 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: content:{"code":"200","success":true,"msg":"操作成功","body":{"pageInfo":{"total":"1","list":[{"id":"1","name":"用户性别","type":"gender","typeDesc":null,"groupCode":"system","groupDesc":null,"status":"0","statusDesc":null}],"pageNum":1,"pageSize":1,"size":1,"startRow":"0","endRow":"0","pages":1,"prePage":0,"nextPage":0,"isFirstPage":true,"isLastPage":true,"hasPreviousPage":false,"hasNextPage":false,"navigatePages":8,"navigatepageNums":[1],"navigateFirstPage":1,"navigateLastPage":1}},"extra":null,"tid":"14913643366907968"}
2024-06-16 20:15:38.400 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response end
2024-06-16 20:15:38.401 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================

2024-06-16 20:15:38.402 [main] INFO  c.l.t.test.util.http.function.ResponseTestCode - test result:true
2024-06-16 20:15:38.402 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - list success
2024-06-16 20:15:38.404 [main] INFO  c.l.thresh.test.module.system.dict.DictQueryTest - database clean
2024-06-16 20:15:38.404 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - DictQueryTest01 finish
2024-06-16 20:15:38.414 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - DictQueryTest02 start
2024-06-16 20:15:38.415 [main] INFO  com.laolang.thresh.test.testng.base.BaseTest - param: data/dict/dictquerytest02.json
2024-06-16 20:15:38.415 [main] INFO  com.laolang.thresh.test.testng.base.BaseTest - array:[{"methodName":"typeList","config":{"request":{"url":"/admin/system/dict/typeList","method":"get"},"auth":{"username":"superAdmin","password":"123456"}}},{"methodName":"groupList","config":{"request":{"url":"/admin/system/dict/groupList","method":"get"},"auth":{"username":"superAdmin","password":"123456"}}},{"methodName":"list","config":{"request":{"url":"/admin/system/dict/list","method":"post","paramType":"body","param":{"name":"模块"}},"auth":{"username":"superAdmin","password":"123456"}}}]
2024-06-16 20:15:38.416 [main] INFO  c.l.thresh.test.module.system.dict.DictQueryTest - database init
2024-06-16 20:15:38.416 [main] INFO  com.laolang.thresh.test.testng.base.BaseTest - methodName: typeList
2024-06-16 20:15:38.416 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - typeList start
2024-06-16 20:15:38.418 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================
2024-06-16 20:15:38.418 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request start
2024-06-16 20:15:38.418 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: http://localhost:8092/admin/system/dict/typeList GET
2024-06-16 20:15:38.418 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImZjODdjMDc1LWYzMzgtNDE5Ni04YzE3LWQxOWEyOTYyMGQ4NSJ9.8vHnG5KiYcKyjcD9kR9rF61imzOnv3un1LxWg-J_6KV8Qmtl04FM5R6oT16skeE3uOgKBaRGq2Lfwj627SZJAQ
2024-06-16 20:15:38.418 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request end

2024-06-16 20:15:38.426 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response start
2024-06-16 20:15:38.426 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: code: 200 time:8ms
2024-06-16 20:15:38.426 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: content:{"code":"200","success":true,"msg":"操作成功","body":{"types":[{"type":"gender","desc":"用户性别"},{"type":"module","desc":"模块"}]},"extra":null,"tid":"14913643375624256"}
2024-06-16 20:15:38.426 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response end
2024-06-16 20:15:38.426 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================

2024-06-16 20:15:38.426 [main] INFO  c.l.t.test.util.http.function.ResponseTestCode - test result:true
2024-06-16 20:15:38.427 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - typeList success
2024-06-16 20:15:38.428 [main] INFO  com.laolang.thresh.test.testng.base.BaseTest - methodName: groupList
2024-06-16 20:15:38.428 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - groupList start
2024-06-16 20:15:38.430 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================
2024-06-16 20:15:38.430 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request start
2024-06-16 20:15:38.430 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: http://localhost:8092/admin/system/dict/groupList GET
2024-06-16 20:15:38.430 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImZjODdjMDc1LWYzMzgtNDE5Ni04YzE3LWQxOWEyOTYyMGQ4NSJ9.8vHnG5KiYcKyjcD9kR9rF61imzOnv3un1LxWg-J_6KV8Qmtl04FM5R6oT16skeE3uOgKBaRGq2Lfwj627SZJAQ
2024-06-16 20:15:38.430 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request end

2024-06-16 20:15:38.438 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response start
2024-06-16 20:15:38.438 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: code: 200 time:8ms
2024-06-16 20:15:38.438 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: content:{"code":"200","success":true,"msg":"操作成功","body":{"groups":[{"group":"system","desc":"系统"}]},"extra":null,"tid":"14913643376410688"}
2024-06-16 20:15:38.438 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response end
2024-06-16 20:15:38.438 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================

2024-06-16 20:15:38.439 [main] INFO  c.l.t.test.util.http.function.ResponseTestCode - test result:true
2024-06-16 20:15:38.439 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - groupList success
2024-06-16 20:15:38.440 [main] INFO  com.laolang.thresh.test.testng.base.BaseTest - methodName: list
2024-06-16 20:15:38.441 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - list start
2024-06-16 20:15:38.443 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================
2024-06-16 20:15:38.443 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request start
2024-06-16 20:15:38.443 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: http://localhost:8092/admin/system/dict/list POST
2024-06-16 20:15:38.443 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: Authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImZjODdjMDc1LWYzMzgtNDE5Ni04YzE3LWQxOWEyOTYyMGQ4NSJ9.8vHnG5KiYcKyjcD9kR9rF61imzOnv3un1LxWg-J_6KV8Qmtl04FM5R6oT16skeE3uOgKBaRGq2Lfwj627SZJAQ
2024-06-16 20:15:38.443 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: {"name":"模块"}
2024-06-16 20:15:38.443 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: request end

2024-06-16 20:15:38.461 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response start
2024-06-16 20:15:38.461 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: code: 200 time:18ms
2024-06-16 20:15:38.461 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: content:{"code":"200","success":true,"msg":"操作成功","body":{"pageInfo":{"total":"1","list":[{"id":"2","name":"模块","type":"module","typeDesc":null,"groupCode":"system","groupDesc":null,"status":"0","statusDesc":null}],"pageNum":1,"pageSize":1,"size":1,"startRow":"0","endRow":"0","pages":1,"prePage":0,"nextPage":0,"isFirstPage":true,"isLastPage":true,"hasPreviousPage":false,"hasNextPage":false,"navigatePages":8,"navigatepageNums":[1],"navigateFirstPage":1,"navigateLastPage":1}},"extra":null,"tid":"14913643377590336"}
2024-06-16 20:15:38.462 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: response end
2024-06-16 20:15:38.462 [main] INFO  c.l.t.t.util.http.interceptor.LoggingInterceptor - HttpClient: ===========================================

2024-06-16 20:15:38.462 [main] INFO  c.l.t.test.util.http.function.ResponseTestCode - test result:true
2024-06-16 20:15:38.462 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - list success
2024-06-16 20:15:38.464 [main] INFO  c.l.thresh.test.module.system.dict.DictQueryTest - database clean
2024-06-16 20:15:38.464 [main] INFO  c.l.thresh.test.testng.listener.ThreshTestListener - DictQueryTest02 finish
2024-06-16 20:15:38.466 [main] INFO  c.l.t.t.testng.listener.ThreshTestngSuiteListener - test suite finish

===============================================
Suite
Total tests run: 6, Failures: 0, Skips: 0
===============================================


Process finished with exit code 0

okhttp 封装

HttpClient

这是封装后要使用的类

  1. 目前只封装了同步请求,没有封装异步请求
  2. 只考虑了 GET 方法和 POST 方法
package com.laolang.thresh.test.util.http;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.laolang.thresh.test.config.ThreshTestConfig;
import com.laolang.thresh.test.util.http.function.ResponseTest;
import com.laolang.thresh.test.util.http.interceptor.LoggingInterceptor;
import com.laolang.thresh.test.util.http.pojo.HttpResult;
import java.io.IOException;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.commons.lang3.exception.ExceptionUtils;

/**
 * OkHttp 封装
 */
public class HttpClient {

    private static volatile OkHttpClient okHttpClient = null;
    private Map<String, String> headerMap;
    private Map<String, String> paramMap;
    private String requestBodyJsonStr;
    private String url;
    private Request.Builder request;
    private static final MediaType jsonMediaType = MediaType.parse("application/json;charset=UTF-8");
    private List<ResponseTest> responseTestList;

    public static HttpClient builder() {
        return new HttpClient();
    }

    private HttpClient() {
        if (Objects.isNull(okHttpClient)) {
            synchronized (HttpClient.class) {
                if (Objects.isNull(okHttpClient)) {
                    TrustManager[] trustManagers = buildTrustManagers();

                    OkHttpClient.Builder builder = new OkHttpClient.Builder()
                            .connectTimeout(15, TimeUnit.SECONDS)
                            .writeTimeout(20, TimeUnit.SECONDS)
                            .readTimeout(20, TimeUnit.SECONDS)
                            .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
                            .hostnameVerifier((hostName, session) -> true)
                            .retryOnConnectionFailure(true);
                    if (ThreshTestConfig.LOG_HTTP_CLIENT) {
                        builder.addInterceptor(new LoggingInterceptor());
                    }
                    okHttpClient = builder.build();
                    addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
                }
            }

        }
    }

    /**
     * 添加url
     */
    public HttpClient url(String url) {
        this.url = url;
        return this;
    }

    /**
     * 添加参数
     *
     * @param key   参数名
     * @param value 参数值
     */
    public HttpClient addParam(String key, String value) {
        if (paramMap == null) {
            paramMap = new LinkedHashMap<>(16);
        }
        paramMap.put(key, value);
        return this;
    }

    /**
     * 添加请求头
     *
     * @param key   参数名
     * @param value 参数值
     */
    public HttpClient addHeader(String key, String value) {
        if (headerMap == null) {
            headerMap = new LinkedHashMap<>(16);
        }
        headerMap.put(key, value);
        return this;
    }

    /**
     * 设置请求体, 此参数优先于 paramMap
     *
     * @param json json 格式的 requestBody
     */
    public HttpClient setRequestBody(String json) {
        this.requestBodyJsonStr = json;
        return this;
    }

    /**
     * 添加请求结果测试
     */
    public HttpClient addResponstTest(ResponseTest function) {
        if (CollUtil.isEmpty(responseTestList)) {
            responseTestList = new ArrayList<>();
        }
        responseTestList.add(function);
        return this;
    }

    /**
     * 初始化get方法
     */
    public HttpClient get() {
        request = new Request.Builder().get();
        StringBuilder urlBuilder = new StringBuilder(url);
        if (paramMap != null) {
            urlBuilder.append("?");
            try {
                for (Map.Entry<String, String> entry : paramMap.entrySet()) {
                    urlBuilder.append(URLEncoder.encode(entry.getKey(), "utf-8")).
                            append("=").
                            append(URLEncoder.encode(entry.getValue(), "utf-8")).
                            append("&");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            urlBuilder.deleteCharAt(urlBuilder.length() - 1);
        }
        request.url(urlBuilder.toString());
        return this;
    }

    /**
     * 初始化post方法
     *
     * @param isJsonPost true等于json的方式提交数据,类似postman里post方法的raw
     *                   false等于普通的表单提交
     */
    public HttpClient post(boolean isJsonPost) {
        int a = 1;
        RequestBody requestBody;
        if (isJsonPost) {
            if (StrUtil.isNotBlank(requestBodyJsonStr)) {
                requestBody = RequestBody.create(requestBodyJsonStr, jsonMediaType);
            } else {
                String json = "";
                if (paramMap != null) {
                    json = JSONUtil.toJsonStr(paramMap);
                }
                requestBody = RequestBody.create(json, jsonMediaType);
            }
        } else {
            FormBody.Builder formBody = new FormBody.Builder();
            if (paramMap != null) {
                paramMap.forEach(formBody::add);
            }
            requestBody = formBody.build();
        }
        request = new Request.Builder().post(requestBody).url(url);
        return this;
    }

    /**
     * 同步请求
     */
    public HttpResult sync() {
        setHeader(request);
        try {
            Response response = okHttpClient.newCall(request.build()).execute();
            assert response.body() != null;
            String body = response.body().string();
            HttpResult result = new HttpResult();
            result.setResponseBody(body);
            Map<String, Boolean> responseTestResultList = null;
            boolean success = true;
            String errorMsg = null;
            if (CollUtil.isNotEmpty(responseTestList)) {
                responseTestResultList = new HashMap<>(16);
                for (ResponseTest function : responseTestList) {
                    boolean test = function.test(body);
                    responseTestResultList.put(function.testName(), test);
                    if (!test) {
                        success = false;
                        if (Objects.isNull(errorMsg)) {
                            errorMsg = function.testName() + " test failed";
                        } else {
                            errorMsg = errorMsg + "\n" + function.getClass().getName() + " test failed";
                        }
                    }
                }
            }
            result.setRequestSuccess(success);
            result.setErrorMsg(errorMsg);
            result.setResponseBody(body);
            result.setResponseTestResultList(responseTestResultList);
            return result;
        } catch (IOException e) {
            e.printStackTrace();
            return new HttpResult(false, null, null, ExceptionUtils.getMessage(e));
        }
    }

    /**
     * 为request添加请求头
     */
    private void setHeader(Request.Builder request) {
        if (headerMap != null) {
            try {
                for (Map.Entry<String, String> entry : headerMap.entrySet()) {
                    request.addHeader(entry.getKey(), entry.getValue());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 生成安全套接字工厂,用于https请求的证书跳过
     */
    private static SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) {
        SSLSocketFactory ssfFactory = null;
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            ssfFactory = sc.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ssfFactory;
    }

    private static TrustManager[] buildTrustManagers() {
        return new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[]{};
                    }
                }
        };
    }
}

LoggingInterceptor

这是日志拦截器

package com.laolang.thresh.test.util.http.interceptor;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;

/**
 * OkHttp 日志拦截器
 */
@Slf4j
public class LoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        logForRequest(request);
        TimeInterval timer = DateUtil.timer();
        Response response = chain.proceed(request);
        return logForResponse(response, timer);
    }

    private Response logForResponse(Response response, TimeInterval timer) {
        try {
            Response.Builder builder = response.newBuilder();
            Response clone = builder.build();
            log.info("HttpClient: response start");
            log.info("HttpClient: code: {} time:{}", clone.code(), timer.intervalMs() + "ms");

            ResponseBody body = clone.body();
            if (body != null) {
                MediaType mediaType = body.contentType();
                if (mediaType != null && isText(mediaType)) {
                    String content = body.string();
                    log.info("HttpClient: content:{}", content);
                    body = ResponseBody.create(content, mediaType);
                    return response.newBuilder().body(body).build();
                }
            }
        } catch (Exception e) {
            log.warn("print reponse error", e);
        } finally {
            log.info("HttpClient: response end");
            log.info("HttpClient: ===========================================\n");
        }
        return response;
    }

    private void logForRequest(Request request) {
        String url = request.url().toString();
        String method = request.method();
        Headers headers = request.headers();
        log.info("HttpClient: ===========================================");
        log.info("HttpClient: request start");
        log.info("HttpClient: {} {}", url, method);
        if (headers.size() > 0) {
            for (String name : headers.names()) {
                log.info("HttpClient: {}:{}", name, headers.get(name));
            }
        }

        RequestBody requestBody = request.body();
        if (requestBody != null) {
            MediaType mediaType = requestBody.contentType();
            if (mediaType != null && isText(mediaType)) {
                log.info("HttpClient: {}", bodyToString(request));
            }
        }
        log.info("HttpClient: request end\n");
    }

    private String bodyToString(final Request request) {
        final Request copy = request.newBuilder().build();
        final Buffer buffer = new Buffer();
        try {
            copy.body().writeTo(buffer);
        } catch (IOException e) {
            return "something error,when show requestBody";
        }
        return buffer.readUtf8();
    }


    private boolean isText(MediaType mediaType) {
        if (mediaType.type().equals("text")) {
            return true;
        }
        if (mediaType.subtype().equals("json") ||
                mediaType.subtype().equals("xml") ||
                mediaType.subtype().equals("html") ||
                mediaType.subtype().equals("webviewhtml")) {
            return true;
        }
        return false;
    }
}

ResponseTest

上面两个类已经足够满足简单的 okhttp 使用场景, 下面的几个类主要是为了自动化测试封装的
ResponseTest 是测试响应结果的接口, HttpClient 在请求成功后,会依次调用com.laolang.thresh.test.util.http.HttpClient#responseTestList,只要有一次调用失败,这次调用就属于失败
HttpResult 主要封装本次请求结果, 就类似于 postman 的请求结果视图

ResponseTest

package com.laolang.thresh.test.util.http.function;

/**
 * http 响应测试接口
 */
public interface ResponseTest {

    /**
     * 测试响应
     *
     * @param body 响应体
     * @return 测试结果
     */
    boolean test(String body);

    /**
     * 测试名称
     */
    String testName();

}

ResponseTestCode

package com.laolang.thresh.test.util.http.function;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 测试响应业务编码是否为 200
 */
@Slf4j
public class ResponseTestCode implements ResponseTest {

    private static final String SUCCESS_CODE = "200";

    @Override
    public boolean test(String body) {
        if (StrUtil.isBlank(body)) {
            return false;
        }
        JSONObject o = JSONUtil.parseObj(body);
        String code = o.getStr("code");
        if (StrUtil.isBlank(code)) {
            return false;
        }
        boolean testResult = StrUtil.equals(code, SUCCESS_CODE);
        log.info("test result:{}", testResult);
        return testResult;
    }

    @Override
    public String testName() {
        return "test code is 200";
    }
}

HttpResult

package com.laolang.thresh.test.util.http.pojo;

import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * http 响应封装
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
public class HttpResult {

    /**
     * 请求是否成功
     */
    private Boolean requestSuccess;
    /**
     * 响应内容
     */
    private String responseBody;
    /**
     * 测试结果
     */
    private Map<String, Boolean> responseTestResultList;
    /**
     * 错误信息
     */
    private String errorMsg;


}

testng 参数化测试

BaseTest

此类作为测试基类,作用如下:

  1. test/resource/testng.xml 中提取测试参数,参数值为一个 json 文件路径,位于 src/test/data
  2. 如果当前用户没有登录,则登录并获取 token , 然后缓存起来
  3. 进一步封装 okhttp 的使用, 简化测试类代码
package com.laolang.thresh.test.testng.base;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.laolang.thresh.test.cache.ThreshTestCache;
import com.laolang.thresh.test.config.ThreshTestConfig;
import com.laolang.thresh.test.testng.pojo.MethodParam;
import com.laolang.thresh.test.util.http.HttpClient;
import com.laolang.thresh.test.util.http.function.ResponseTest;
import com.laolang.thresh.test.util.http.function.ResponseTestCode;
import com.laolang.thresh.test.util.http.pojo.HttpResult;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Parameters;

/**
 * 测试基类
 */
@Slf4j
public class BaseTest {

    /**
     * 测试响应业务编码
     */
    private final ResponseTest responseTestCode = new ResponseTestCode();

    /**
     * 测试数据配置
     */
    private final Map<String, JSONObject> methodParamMap = new HashMap<>(16);

    /**
     * 加载测试数据
     *
     * @param param 参数来源 <code>thresh-test/testng.xml:9</code>
     */
    @BeforeClass
    @Parameters({"param"})
    public void beforeClass(String param) {
        log.info("param: {}", param);
        JSONArray array = JSONUtil.parseArray(FileUtil.readUtf8String(param));
        if (array.isEmpty()) {
            return;
        }

        array.forEach(o -> {
            JSONObject obj = (JSONObject) o;
            methodParamMap.put(obj.getStr("methodName"), obj.getJSONObject("config"));
        });
        log.info("array:{}", JSONUtil.toJsonStr(array));
    }

    /**
     * 提供测试方法数据
     *
     * @param method 测试方法
     * @return 测试数据
     */
    @DataProvider(name = "dataProvider")
    public Object[][] dataProvider(Method method) {
        Object[][] param = null;
        log.info("methodName: {}", method.getName());
        if (!methodParamMap.containsKey(method.getName())) {
            return param;
        }

        JSONObject data = methodParamMap.get(method.getName());
        MethodParam methodParam = MethodParam.parseData(data);

        return new Object[][]{
                {methodParam}
        };
    }

    /**
     * 请求 http
     *
     * @param methodParam 测试数据
     */
    protected HttpResult doRequest(MethodParam methodParam) {
        return doRequest(methodParam, null);
    }

    /**
     * 请求 http
     *
     * @param methodParam   测试数据
     * @param responseTests 响应结果测试
     */
    protected HttpResult doRequest(MethodParam methodParam, LinkedList<ResponseTest> responseTests) {
        HttpClient client = HttpClient.builder().url(ThreshTestConfig.BASE_URL + methodParam.getUrl());
        client.addHeader("Authorization", "Bearer " + getTokenByUsername(methodParam));
        if (methodParam.isGET()) {
            client.get();
        }
        if (methodParam.isPost()) {
            client.setRequestBody(methodParam.getRequestBody());
            client.post(methodParam.hasRequestBody());
        }
        client.addResponstTest(responseTestCode);
        if (CollUtil.isNotEmpty(responseTests)) {
            for (ResponseTest responseTest : responseTests) {
                client.addResponstTest(responseTest);
            }
        }
        HttpResult httpResult = client.sync();
        Assert.assertTrue(httpResult.getRequestSuccess(), httpResult.getErrorMsg());
        return httpResult;
    }

    /**
     * 根据用户名获取 token
     *
     * @param methodParam 测试数据
     */
    private String getTokenByUsername(MethodParam methodParam) {
        String token = ThreshTestCache.INSTANCE.getToken(methodParam.getUsername());

        if (StrUtil.isNotBlank(token)) {
            return token;
        }

        JSONObject param = new JSONObject();
        param.set("username", methodParam.getUsername());
        param.set("password", methodParam.getPassword());
        HttpResult httpResult = HttpClient.builder()
                .url(ThreshTestConfig.LOGIN_URL)
                .setRequestBody(JSONUtil.toJsonStr(param))
                .post(true)
                .sync();

        Assert.assertTrue(httpResult.getRequestSuccess());

        JSONObject body = JSONUtil.parseObj(httpResult.getResponseBody());
        token = body.getJSONObject("body").getStr("token");

        ThreshTestCache.INSTANCE.setToken(methodParam.getUsername(), token);

        return token;
    }


}

测试类示例

目前这个类除了指定测试顺序和请求接口之外并没有做其他的事

package com.laolang.thresh.test.module.system.dict;

import com.laolang.thresh.test.testng.base.BaseTest;
import com.laolang.thresh.test.testng.pojo.MethodParam;
import lombok.extern.slf4j.Slf4j;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

@Slf4j
public class DictQueryTest extends BaseTest {

    @BeforeClass
    public void beforeClass() {
        log.info("database init");
    }

    @AfterClass
    public void afterClass() {
        log.info("database clean");
    }

    @Test(dataProvider = "dataProvider")
    public void typeList(MethodParam param) {
        doRequest(param);
    }


    @Test(dependsOnMethods = {"typeList"}, dataProvider = "dataProvider")
    public void groupList(MethodParam param) {
        doRequest(param);
    }


    @Test(dependsOnMethods = {"groupList"}, dataProvider = "dataProvider")
    public void list(MethodParam param) {
        doRequest(param);
    }
}

测试文件

[
  {
    "methodName": "typeList",
    "config": {
      "request": {
        "url": "/admin/system/dict/typeList",
        "method": "get"
      },
      "auth": {
        "username": "superAdmin",
        "password": "123456"
      }
    }
  },
  {
    "methodName": "groupList",
    "config": {
      "request": {
        "url": "/admin/system/dict/groupList",
        "method": "get"
      },
      "auth": {
        "username": "superAdmin",
        "password": "123456"
      }
    }
  },
  {
    "methodName": "list",
    "config": {
      "request": {
        "url": "/admin/system/dict/list",
        "method": "post",
        "paramType": "body",
        "param": {
          "name": "用户性别"
        }
      },
      "auth": {
        "username": "superAdmin",
        "password": "123456"
      }
    }
  }
]

之后的更新

  1. 目前测试项目和接口 demo 项目是在一个工程中的, 后续要分开
  2. 写一个 crud , 然后针对这个 crud 做一个实质的自动化测试流程
  3. 测试的编排太过简单,通常来讲,在一个业务流程中,后端某个接口返回的业务状态码会影响后续的接口是否执行以及参数的变化,有两种做法:
    3.1 简单的做就是穷举出所有业务场景,然后每个场景写一个 json 文件作为测试数据文件
    3.2 比较高级的做法则是实用脚本控制, 例如 javascript, 使用 java8 内嵌的 Nashorn 引擎,在每个请求前后都添加一个脚本,然后使用此脚本来做请求参数的控制(比如 header, 或者来自上一个请求的响应), 响应测试(比如测试业务状态码是否正确)
    3.3 另一中语言的选择是使用 groovy 或者 kotlinDSL
  4. 通常来讲,测试用例的代码主要是查看业务流程是否正确,数据库更新是否正确,这些流程都是可以封装起来的,然后使用脚本来控制, 然而总有例外情况,总有某些接口的测试比较复杂,使用内嵌脚本处理比较麻烦,解决办法则是:允许 java 测试用例和脚本编排同时存在, 以 java 代码为优先
  5. 生成测试报告. 一个完整的自动化测试必然会包含测试报告, 目前的想法是:
    5.1 参考 allure 自己写一个
    5.2 结合 allure 生成测试报告
  6. 结合 spring boot , 提高开发效率
  7. 添加数据库的基本操作
  8. 一些简单的测试流程是可以使用 DSL 描述的, 比如类似 plantuml 这种建模语言就很适合描述接口自动化测试流程
posted @ 2024-06-12 06:11  潼关路边的一只野鬼  阅读(32)  评论(0编辑  收藏  举报