目录
一.前言
在上一章节跟着操作把代码成功运行起来,就可以跟着本章节进行接口自动化代码编写,接口自动化使用的测试框架是TestNG,大家跟着我的步骤一步步往下操作即可(* ̄︶ ̄)
二. 引入TestNg
- 点击hima-admin,进入pom.xml,引入TestNg依赖
<!-- 接口自动化测试相关-->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.4.0</version>
<!-- <scope>test</scope>-->
</dependency>
三. 创建接口自动化目录结构
- 引入成功后,我们创建相关的目录结构,开始编写接口自动化代码。
下方图片是我创建的接口自动化相关目录结构:
注意: 大家也可根据自己业务要求进行分层,整体思路就是用例、逻辑和数据要分离,不要写在一起。
hima-admin下方创建testng软件包,后期所有接口用例均在testng软件包下方维护
testcas文件夹: 里边写的文件用于编写接口用例
data文件夹: 里边写的文件用于存放用例所需的数据,实现代码和数据分离
utils文件夹: 用于存放各种自己编写的工具类
config文件夹: 用于存放一些基本配置
common文件夹: 用于存放通用类(暂时没有用到)
下方图片是目录结构详细版本
三. 引入okhttp3
到这一步了,想要开始编写代码了,但是怎么写,不知道如何下手,先别着急,我们先理理,平时大家在测试接口是怎么测得呢? 以postman为例子:
1. 登录接口地址是什么(url)
2. 请求方式是什么
3. 入参具体有哪些
4. 点击发送进行接口请求
5. 接口返回对应状态码和message
代码实现是一样的道理,那我们用什么才能够把对应的数据进行发送,并且数据要组装成什么样的类型呢,才会像postman一样,点击发送后给出对应的返回信息,这时我们需要引入okhttp3,,okhttp3是一个请求网络工具,想要详细了解,大家自行百度即可。
- 在hima-admin下的pom.xml文件添加okhttps依赖
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
在com.hima.testng.utils下,直接把下方对应的HttpClientUtils代码放入,这个工具类我也是直接在网上copy下来,删除或者修改了一小部分代码,直接使用的。
package com.hima.testng.utils;
import cn.hutool.core.lang.func.Supplier2;
import cn.hutool.json.JSONUtil;
import com.hima.testng.config.HttpClientConfig;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import static okhttp3.ConnectionSpec.CLEARTEXT;
/**
* @version 1.0
* @Author:张爱婷
* @Package:com.hima.common.utils
* @Date:2024/12/19 下午4:22
*/
public class HttpClientUtils {
private static final Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);
private static final Map<String, OkHttpClient> map_clients = new ConcurrentHashMap<>();
private static OkHttpClient okHttpClient2;
private HttpClientUtils() {
}
/**
* 饿汉式单例
*/
private static OkHttpClient getInstance(final HttpClientConfig config) {
if (map_clients.containsKey(md5Key(config))) {
return map_clients.get(md5Key(config));
}
OkHttpClient okHttpClient = createHttpClient(config);
map_clients.put(md5Key(config), okHttpClient);
return okHttpClient;
}
/**
* 懒汉式单例
*/
private static OkHttpClient getOkHttpClient(HttpClientConfig config) {
if (okHttpClient2 == null) {
okHttpClient2 = createHttpClient(config);
}
return okHttpClient2;
}
private static String md5Key(HttpClientConfig config) {
return getMD5InHex(config.getMasterUrl() + "/" + config.getUsername() + "/" + config.getPassword() + "/");
}
private static String getMD5InHex(String data) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(data.getBytes());
byte[] result = messageDigest.digest();
return new BigInteger(1, result).toString(16);
} catch (NoSuchAlgorithmException e) {
return null;
}
}
private static OkHttpClient createHttpClient(final HttpClientConfig config) {
try {
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
// Follow any redirects
httpClientBuilder.followRedirects(true);
httpClientBuilder.followSslRedirects(true);
if (config.isTrustCerts()) {
httpClientBuilder.hostnameVerifier((s, sslSession) -> true);
}
TrustManager[] trustManagers = buildTrustManagers();
httpClientBuilder.sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0]);
if (config.getConnectionTimeout() > 0) {
httpClientBuilder.connectTimeout(config.getConnectionTimeout(), TimeUnit.MILLISECONDS);
}
if (config.getRequestTimeout() > 0) {
httpClientBuilder.readTimeout(config.getRequestTimeout(), TimeUnit.MILLISECONDS);
}
if (config.getWebsocketPingInterval() > 0) {
httpClientBuilder.pingInterval(config.getWebsocketPingInterval(), TimeUnit.MILLISECONDS);
}
if (config.getMaxConcurrentRequestsPerHost() > 0) {
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequestsPerHost(config.getMaxConcurrentRequestsPerHost());
httpClientBuilder.dispatcher(dispatcher);
}
if (config.getMaxConnection() > 0) {
ConnectionPool connectionPool = new ConnectionPool(config.getMaxConnection(), 60, TimeUnit.SECONDS);
httpClientBuilder.connectionPool(connectionPool);
}
// Only check proxy if it's a full URL with protocol
if (config.getMasterUrl().toLowerCase().startsWith(HttpClientConfig.HTTP_PROTOCOL_PREFIX) || config.getMasterUrl().startsWith(HttpClientConfig.HTTPS_PROTOCOL_PREFIX)) {
try {
URL proxyUrl = getProxyUrl(config);
if (proxyUrl != null) {
httpClientBuilder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyUrl.getHost(), proxyUrl.getPort())));
if (config.getProxyUsername() != null) {
httpClientBuilder.proxyAuthenticator((route, response) -> {
String credential = Credentials.basic(config.getProxyUsername(), config.getProxyPassword());
return response.request().newBuilder().header("Proxy-Authorization", credential).build();
});
}
}
} catch (MalformedURLException e) {
throw new RuntimeException("Invalid proxy server configuration", e);
}
}
if (config.getUserAgent() != null && !config.getUserAgent().isEmpty()) {
httpClientBuilder.addNetworkInterceptor(chain -> {
Request agent = chain.request().newBuilder().header("User-Agent", config.getUserAgent()).build();
return chain.proceed(agent);
});
}
if (config.getTlsVersions() != null && config.getTlsVersions().length > 0) {
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(config.getTlsVersions())
.build();
httpClientBuilder.connectionSpecs(Arrays.asList(spec, CLEARTEXT));
}
return httpClientBuilder.build();
} catch (Exception e) {
throw new RuntimeException("创建OKHTTPClient错误", e);
}
}
/**
* 生成安全套接字工厂,用于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[]{};
}
}
};
}
private static URL getProxyUrl(HttpClientConfig config) throws MalformedURLException {
URL master = new URL(config.getMasterUrl());
String host = master.getHost();
if (config.getNoProxy() != null) {
for (String noProxy : config.getNoProxy()) {
if (host.endsWith(noProxy)) {
return null;
}
}
}
String proxy = config.getHttpsProxy();
if (master.getProtocol().equals("http")) {
proxy = config.getHttpProxy();
}
if (proxy != null) {
return new URL(proxy);
}
return null;
}
/** -----------------------------------------------具体请求方法----------------------------------------------------**/
/**
* 发送post请求
*
* @param isJsonPost true等于json的方式提交数据,类似postman里post方法的raw,false等于普通的表单提交
* @param httpClientConfig httpclient配置类
* @param paramMap 参数map
* @return 返回的数据
*/
public static String doPost(boolean isJsonPost, HttpClientConfig httpClientConfig, Map<String, Object> paramMap) {
Response response = null;
try {
OkHttpClient client = getInstance(httpClientConfig);
RequestBody requestBody;
String json = JSONUtil.toJsonStr(paramMap);
requestBody = RequestBody.create(MediaType.parse("application/json"), json);
HashMap<String, String> extraHeaders = new HashMap<>();
extraHeaders.put("istoken", "false");
extraHeaders.put("repeatsubmit", "false");
extraHeaders.put("Content-Type", "application/json;charset=UTF-8");
Request request = new Request.Builder()
.headers(extraHeaders == null ? new Headers.Builder().build() : Headers.of(extraHeaders))//extraHeaders 是用户添加头
.url(httpClientConfig.getMasterUrl())
.post(requestBody)
.build();
response = client.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
return response.body().string();
} else if (response.body() != null) {
logger.info(response.body().string());
return null;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
response.close();
}
}
return null;
}
/**
* 发送post请求(发送json格式的不是form表单格式的)
*
* @param httpClientConfig httpclient配置类
* @param map 参数map
* @return 返回的数据
*/
public static String doPost(HttpClientConfig httpClientConfig, Map<String, String> map) {
Response response = null;
try {
OkHttpClient client = getInstance(httpClientConfig);
String json = "";
if (map != null) {
json = JSONUtil.toJsonStr(map);
}
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), json);
Request.Builder builder = new Request.Builder();
Request request = builder.url(httpClientConfig.getMasterUrl()).post(requestBody).build();
response = client.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
return response.body().string();
} else if (response.body() != null) {
logger.info(response.body().string());
return null;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
response.close();
}
}
return null;
}
/**
* 携带token的post请求
*
* @param isJsonPost true等于json的方式提交数据,类似postman里post方法的raw,false等于普通的表单提交
* @param httpClientConfig httpclient配置类
* @param paramMap 参数map
* @param token token
* @return 返回的数据
*/
public static String doPostByToken(boolean isJsonPost, HttpClientConfig httpClientConfig, Map<Object, Object> paramMap, String token) {
Response response = null;
try {
OkHttpClient client = getInstance(httpClientConfig);
RequestBody requestBody;
if (isJsonPost) {
String json = "";
if (paramMap != null) {
json = JSONUtil.toJsonStr(paramMap);
}
requestBody = RequestBody.create(MediaType.parse("application/json"), json);
} else {
FormBody.Builder formBody = new FormBody.Builder();
if (paramMap != null) {
//paramMap.replaceAll((key, value) -> String.valueOf(value));
for (Map.Entry<Object, Object> entry : paramMap.entrySet()) {
String key = (String) entry.getKey();
String value = entry.getValue().toString();
Supplier2<FormBody.Builder, String, String> add = formBody::add;
}
}
requestBody = formBody.build();
}
Request.Builder builder = new Request.Builder();
Request request = builder.url(httpClientConfig.getMasterUrl()).header("Authorization", token).post(requestBody).build();
response = client.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
return response.body().string();
} else if (response.body() != null) {
logger.info(response.body().string());
return null;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
response.close();
}
}
return null;
}
/*发送PUT请求*/
public static String doPutByToken(boolean isJsonPost, HttpClientConfig httpClientConfig, Map<Object, Object> paramMap, String token) {
Response response = null;
try {
OkHttpClient client = getInstance(httpClientConfig);
RequestBody requestBody;
if (isJsonPost) {
String json = "";
if (paramMap != null) {
json = JSONUtil.toJsonStr(paramMap);
}
requestBody = RequestBody.create(MediaType.parse("application/json"), json);
} else {
FormBody.Builder formBody = new FormBody.Builder();
if (paramMap != null) {
//paramMap.replaceAll((key, value) -> String.valueOf(value));
for (Map.Entry<Object, Object> entry : paramMap.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue().toString();
Supplier2<FormBody.Builder, String, String> add = formBody::add;
}
}
requestBody = formBody.build();
}
Request.Builder builder = new Request.Builder();
Request request = builder.url(httpClientConfig.getMasterUrl()).header("Authorization", token).put(requestBody).build();
response = client.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
return response.body().string();
} else if (response.body() != null) {
logger.info(response.body().string());
return null;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
response.close();
}
}
return null;
}
// 删除
public static String doDeleteByToken(HttpClientConfig httpClientConfig, Object uniqueValue, String token) {
Response response = null;
try {
OkHttpClient client = getInstance(httpClientConfig);
Request.Builder builder = new Request.Builder();
Request request = builder.url(httpClientConfig.getMasterUrl() + uniqueValue).header("Authorization", token).delete().build();
response = client.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
return response.body().string();
} else if (response.body() != null) {
logger.info(response.body().string());
return null;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
response.close();
}
}
return null;
}
// get请求
public static String doListByToken(HttpClientConfig httpClientConfig, String token) {
Response response = null;
try {
OkHttpClient client = getInstance(httpClientConfig);
Request.Builder builder = new Request.Builder();
Request request = builder.url(httpClientConfig.getMasterUrl()).header("Authorization", token).get().build();
response = client.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
return response.body().string();
} else if (response.body() != null) {
logger.info(response.body().string());
return null;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
response.close();
}
}
return null;
}
/**
* 返回请求头
*
* @param config httpclient配置类
* @param paramMap 参数map
* @return 响应头
*/
public static Headers doLoginRequest(boolean isJsonPost, HttpClientConfig config, Map<String, String> paramMap) {
Response response = null;
try {
OkHttpClient client = getInstance(config);
RequestBody requestBody;
if (isJsonPost) {
String json = "";
if (paramMap != null) {
json = JSONUtil.toJsonStr(paramMap);
}
requestBody = RequestBody.create(MediaType.parse("application/json"), json);
} else {
FormBody.Builder formBody = new FormBody.Builder();
if (paramMap != null) {
paramMap.forEach(formBody::add);
}
requestBody = formBody.build();
}
Request request = new Request.Builder().url(config.getMasterUrl()).post(requestBody).build();
response = client.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
return response.headers();
} else if (response.body() != null) {
return null;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
((Response) response).close();
}
}
return null;
}
}
在com.hima.testng.config下,直接把下方对应的HttpClientConfig代码放入.
package com.hima.testng.config;
import lombok.Data;
import okhttp3.TlsVersion;
import org.springframework.context.annotation.Configuration;
import static okhttp3.TlsVersion.TLS_1_2;
/**
* @version 1.0
* @Author:张爱婷
* @Package:com.hima.common.config
* @Date:2024/12/20 上午9:31
*/
@Data
@Configuration //告诉SpringBoot这是一个配置类 == 配置文件
public class HttpClientConfig {
private String masterUrl;
private boolean trustCerts;
private String username;
private String password;
private int connectionTimeout;
private int requestTimeout;
private int websocketPingInterval;
private int maxConcurrentRequestsPerHost;
private int maxConnection;
private String httpProxy;
private String httpsProxy;
private String proxyUsername;
private String proxyPassword;
private String userAgent;
private TlsVersion[] tlsVersions = new TlsVersion[]{TLS_1_2};
private String[] noProxy;
public static final String HTTP_PROTOCOL_PREFIX = "http://";
public static final String HTTPS_PROTOCOL_PREFIX = "https://";
/** token 暂存 质量管理系统*/
public String qmToken ;
public String token;
//省略get、set方法
// @Bean //给容器中添加组件。以方法名作为组件id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
// public TlsVersion[] tlsVersions(){
// return new TlsVersion[]{TlsVersion.valueOf("TLSv1.3")};
// }
}
至此,基于若依框架进行接口自动化框架粗糙版搭建完成(TestNG+OKHTTP),可以进行相关的用例编写及请求发送,能够实现基本的接口自动化功能了。下来实战吧
四. 开始编写第一条接口用例
用例1: 输入正确的用户名和错误的密码,验证密码错误场景接口验证。
上边的用例大家都应该很清楚,非常基础,是验证登录异常的场景,也是测试登录需求必须要验证的场景。
注意: 我这边是以若依这个框架本身的登录举例,并且关掉了验证码功能,大家关掉验证码功能后,刷新退出。
(1) testcase文件夹下方创建对应的项目文件夹(例:demoproject),并创建登录用例
点击相关项目文件夹,选择新建->java类,创建LoginTest类,创建方法public void loginUserName() {},在方法中编写测试用例。
(2) 在方法上方直接添加@Test注解,选择testng的@Test,这就表明该方法是一个测试方法。里边写的内容就是测试用例,并且可以点击左侧按钮直接运行啦。
(3) 编写用例代码,直接复制下方代码即可。
1. 登录接口(默认端口号是80,我改过端口号,所以是82)
2.入参数据
3.给接口发送请求(true表示入参是json格式)
4. 对接口返回的信息进行断言,看是否和需求一致。
package com.hima.testng.testcase.demoproject;
import com.hima.testng.config.HttpClientConfig;
import com.hima.testng.data.BaseData;
import com.hima.testng.data.demoproject.InterAutoData;
import com.hima.testng.data.demoproject.LoginData;
import com.hima.testng.utils.HttpClientUtils;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.HashMap;
import java.util.Map;
/**
* @version 1.0
* @Author:张爱婷
* @Package:com.hima.testng.testcase.demoproject
* @Date:2025/1/17 上午10:35
* @DESC:
*/
public class LoginTest {
@Test(description = "输入正确的用户名和错误的密码,验证密码错误场景接口验证。")
public void loginUserName() {
// 1. 登录接口(默认端口号是80,我改过端口号,所以是82)
HttpClientConfig config = new HttpClientConfig();
config.setMasterUrl("http://127.0.0.1:82/dev-api/login");
//2.入参数据,由于HttpClientUtils.doPost要求的入参数据类型是map,所以我们入参编写为map类型
Map<String, Object> loginmap = new HashMap<>() ;
loginmap.put("username","admin");
loginmap.put("password","admin1234");
loginmap.put("uuid","");
loginmap.put("code","");
//3.给接口发送请求(true表示入参是json格式)
String logintest = HttpClientUtils.doPost(true, config, loginmap);
//4. 对接口返回的信息进行断言,看是否和需求一致。
Assert.assertEquals(logintest, "{\"msg\":\"用户不存在/密码错误\",\"code\":500}");
}
}
(4) 点击方法左侧的运行按钮,成功运行。
是不是很简单,这样一条接口自动化用例就完成了,不过大家有没有觉得很乱,数据,断言都在一起,下一章节,我们会把数据和用例代码进行分离。
标签:return,String,框架,TestNG,若依,import,null,config,response From: https://blog.csdn.net/qq_38932687/article/details/145199255