乐趣区

关于软件测试:软件测试-Dubbo-接口测试原理及多种方法实践总结

1、什么是 Dubbo?

Dubbo 最开始是利用于淘宝网,由阿里巴巴开源的一款优良的高性能服务框架,由 Java 开发,起初奉献给了 Apache 开源基金会组织。

上面以官网的一个阐明来理解一下架构的演变过程,从而理解 Dubbo 的诞生起因:

繁多利用架构

当网站流量很小时,只需一个利用,将所有性能都部署在一起,以缩小部署节点和老本。此时,用于简化增删改查工作量的数据拜访框架 (ORM) 是要害。

垂直利用架构

当访问量逐步增大,繁多利用减少机器带来的加速度越来越小,晋升效率的办法之一是将利用拆成互不相干的几个利用,以晋升效率。此时,用于减速前端页面开发的 Web 框架 (MVC) 是要害。

分布式服务架构

当垂直利用越来越多,利用之间交互不可避免,将外围业务抽取进去,作为独立的服务,逐步造成稳固的服务中心,使前端利用能更疾速的响应多变的市场需求。此时,用于进步业务复用及整合的分布式服务框架 (RPC) 是要害。

流动计算架构

当服务越来越多,容量的评估,小服务资源的节约等问题逐步浮现,此时需减少一个调度核心基于拜访压力实时治理集群容量,进步集群利用率。此时,用于进步机器利用率的资源调度和治理核心 (SOA) 是要害。

2、Dubbo 架构简介

Dubbo 比拟有特点的中央就是这个注册核心,平时咱们测试较多的 HTTP 接口,间接申请接口,调用后端服务即可;而 Dubbo 是要先走注册核心获取服务的地位,上面来举个现实生活中的例子来阐明。

事实举例

好比大家平时约敌人一起进来吃饭,据说川菜馆“赠李白”不错,而后须要找这家饭店在哪(用小蓝或小黄 App),晓得了具体的地址才登程,至于是走路,打车还是骑车,就随便了。

这里 App 就相当于注册核心(Registry),咱们这群吃货就是消费者(Consumer),商家属于生产者(Provider)。商家把本人的信息注册在 App 上,消费者依据 App 查问到商家的信息,再依据信息找到商家进行生产。

2.1、Zookeeper 简介

之前常常有小伙伴问我 zk 干啥的?怎么用?上面就来简略理解一哈:

ZK,全称就是 zookeeper,是 Apache 软件基金会的一个软件我的项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。

上面的图示也能够清晰的阐明 zk 的部署和工作的一些形式(具体的技术细节需要的话能够针对 zk 专门搜寻学习):

Leader:集群工作的外围,事务申请的惟一调度和解决者,保障事务处理的程序性。对于有写操作的申请,需对立转发给 Leader 解决。Leader 需决定编号执行操作。

Follower:解决客户端非事务申请,转发事务申请转发给 Leader,参加 Leader 选举。

Observer 观察者:进行非事务申请的独立解决, 对于事务申请, 则转发给 Leader 服务器进行解决. 不参加投票。

3、什么是 Dubbo 接口?

所谓的 Dubbo 接口,其实就是一个个 Dubbo 服务中的办法,而测试 Dubbo 接口就相当于咱们测试人员充当消费者或者发明消费者去 ” 生产 ” 这个办法。

具体的形式有很多,代码、工具、命令皆可,在接下来的内容中来一一演示。

4、Dubbo 接口测试(发明消费者)

以下我将以本地的一个简略的 Dubbo 服务 demo 为例,演示 Dubbo 测试的各种办法。

interface 只有两个,别离是 OrderService 和 UserService

OrderService

package com.qinzhen.testmall.service;

import com.qinzhen.testmall.bean.UserAddress;
import java.util.List;

public interface OrderService {

/**

  • 初始化订单
  • @param userID
    */
    public List<UserAddress> initOrder(String userID);
    }

UserService

package com.qinzhen.testmall.service;

import com.qinzhen.testmall.bean.UserAddress;
import java.util.List;

/**

  • 用户服务
    */
    public interface UserService {

/**

  • 依照 userId 返回所有的播种地址
  • @param userId
  • @return
    */
    public List<UserAddress> getUserAddressList(String userId);

/**

  • 返回所有的播种地址
  • @param
  • @return
    */
    public List<UserAddress> getUserAddressList();
    }

JavaBean 对象 UserAddress 如下:

package com.qinzhen.testmall.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;

@AllArgsConstructor
@Data
public class UserAddress implements Serializable {

private Integer id;
private String userAddress; // 用户地址
private String userId; // 用户 ID
private String consignee; // 收货人
private String phoneNum; // 电话号码
private String isDefault; // 是否为默认地址 Y- 是 N- 否

public UserAddress(){

}
}

创立一个 provider 来实现 UserService 的 Interface:

实现办法中,依据 id 返回对应的用户地址信息即可:···package com.qinzhen.testmall.bootuserserviceprovider.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.qinzhen.testmall.bean.UserAddress;
import com.qinzhen.testmall.service.UserService;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Component
@Service // 裸露服务
public class UserServiceImpl implements UserService {

private UserAddress userAddress1 = new UserAddress(1, “ 杭州市西湖区 XX 公司 ”, “1”, “qz”, “12345678”, “Y”);
private UserAddress userAddress2 = new UserAddress(2, “ 杭州市西湖区花园 ”, “2”, “qz”, “12345678”, “N”);

@Override
public List<UserAddress> getUserAddressList(String userId) {
if (userId.equals(“1”)){
return Collections.singletonList(userAddress1);
}
else if (userId.equals(“2”)){
return Collections.singletonList(userAddress2);
}
else {
return Arrays.asList(userAddress1, userAddress2);
}
}

@Override
public List<UserAddress> getUserAddressList(){
return Arrays.asList(userAddress1, userAddress2);
}
}

···

4.1 Java consumer 代码

上面咱们编写 consumer 代码,让服务消费者去注册核心订阅服务提供者的服务地址,以 RPC 形式,获取近程服务代理,从而执行近程办法,代码也很简略,如下:

代码构造:

实现场景就是实现 OrderService 中的 initOrder() 办法,初始化订单,初始化中间接调用 userService 的 getUserAddressLis(java.lang.String) 办法, 具体代码如下:

package com.qinzhen.testmall.service.impl;

import com.qinzhen.testmall.bean.UserAddress;
import com.qinzhen.testmall.service.OrderService;
import com.qinzhen.testmall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

/**

  • 1、讲服务提供者注册到注册核心(裸露服务)
  • 1)导入 dubbo 依赖:操作 zookeeper 的客户端(curator)
  • 2、让服务消费者去注册核心订阅服务提供者的服务地址
    */
    @Service
    public class OrderServiceImpl implements OrderService {

@Autowired
UserService userService;

public List<UserAddress> initOrder(String userId) {
//1. 查问用户的收货地址
System.out.println(“ 用户 ID 为:” + userId);
List<UserAddress> userAddressList = userService.getUserAddressList(userId);
return userAddressList;
}
}

consumer MainApplication

package com.qinzhen.testmall;

import com.qinzhen.testmall.bean.UserAddress;
import com.qinzhen.testmall.service.OrderService;
import com.qinzhen.testmall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;

/**

  • 1、将服务提供者注册到注册核心(裸露服务)
  • 1)导入 dubbo 依赖:操作 zookeeper 的客户端(curator)
  • 2、让服务消费者去注册核心订阅服务提供者的服务地址
    */
    @Service
    public class MainApplication {
    public static void main(String[] args) throws IOException {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {“consumer.xml”});
    context.start();
    OrderService orderService = context.getBean(OrderService.class); // 获取近程服务代理
    List<UserAddress> userAddresses = orderService.initOrder(“3”);// 执行近程办法
    System.out.println(userAddresses);
    System.out.println(“ 调用实现。。。”);
    System.in.read();
    }
    }

consumer.xml:

<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xmlns:dubbo=”http://code.alibabatech.com/schema/dubbo”
xmlns:context=”http://www.springframework.org/schema/context”
xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.or…
http://code.alibabatech.com/s… http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.or… http://www.springframework.org/schema/context/spring-context.xsd”>

<context:component-scan base-package=”com.qinzhen.testmall.service.impl”></context:component-scan>

<dubbo:application name=”order-service-comsumer”></dubbo:application>

<dubbo:registry address=”zookeeper://127.0.0.1:2181″></dubbo:registry>

<!– 申明须要近程调用近程服务的接口,生成近程服务代理 –>
<dubbo:reference interface=”com.qinzhen.testmall.service.UserService” id=”userService”></dubbo:reference>

</beans>

实例演示:

首先确保 provider 已启动:

运行 consumer , 能够看到胜利调用到 dubbo 办法,获取地址列表信息:

4.2 telnet+invoke

咱们应用 telnet 命令能够间接拜访对应的服务,然而前提是你须要晓得服务对应的 ip+ 端口。

如下配置文件,咱们能够晓得服务裸露在本地的 20880 端口

dubbo.application.name=boot-user-service-provider
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

应用 telnet 命令进行拜访,如下呈现 Dubbo 字样时阐明连贯胜利:

% telnet localhost 20880
Trying ::1…
Connected to localhost.
Escape character is ‘^]’.

dubbo>

Dubbo 内建的 telnet 命令的阐明和用法如下

ls

ls: 显示服务列表

ls -l: 显示服务详细信息列表

ls XxxService: 显示服务的办法列表

ls -l XxxService: 显示服务的办法详细信息列表

dubbo>ls
com.qinzhen.testmall.service.UserService

dubbo>ls -l
com.qinzhen.testmall.service.UserService -> dubbo://192.168.2.xxx:20880/com.qinzhen.testmall.service.UserService?anyhost=true&application=boot-user-service-provider&bind.ip=192.168.2.xxx&bind.port=20880&dubbo=2.6.2&generic=false&interface=com.qinzhen.testmall.service.UserService&methods=getUserAddressList&pid=55472&qos.enable=false&side=provider×tamp=1615088321885

dubbo>dubbo>ls com.qinzhen.testmall.service.UserService
getUserAddressList
getUserAddressList

dubbo>dubbo>ls -l com.qinzhen.testmall.service.UserService
java.util.List getUserAddressList(java.lang.String)
java.util.List getUserAddressList()

invoke

invoke XxxService.xxxMethod(1234, “abcd”, {“prop” : “value”}): 调用服务的办法

invoke com.xxx.XxxService.XxxService.xxxMethod(1234, “abcd”, {“prop” : “value”}): 调用全门路服务的办法

invoke xxxMethod(1234, “abcd”, {“prop” : “value”}): 调用服务的办法(主动查找蕴含此办法的服务)

invoke xxxMethod({“name”:”zhangsan”,”age”:12,”class”:”org.apache.dubbo.qos.legacy.service.Person”}) : 当有参数重载,或者类型转换失败的时候,能够通过减少 class 属性指定须要转换类

当参数为 Map<Integer,T>,key 的类型为 Integer 时,倡议指定类型。例如 invoke com.xxx.xxxApiService({“3”:0.123, “class”:”java.util.HashMap”})

而后咱们应用 invoke 命令对 dubbo 办法 getUserAddressList()进行调用,如下:

dubbo>invoke getUserAddressList()
[{“consignee”:”qz”,”id”:1,”isDefault”:”Y”,”phoneNum”:”12345678″,”userAddress”:” 杭州市西湖区 xx 公司 ”,”userId”:”1″},{“consignee”:”qz”,”id”:2,”isDefault”:”N”,”phoneNum”:”12345678″,”userAddress”:” 杭州市西湖区 xx 花园 ”,”userId”:”2″}]

dubbo>invoke getUserAddressList(“1”)
[{“consignee”:”qz”,”id”:1,”isDefault”:”Y”,”phoneNum”:”12345678″,”userAddress”:” 杭州市西湖区 xx 公司 ”,”userId”:”1″}]
elapsed: 14 ms.

学习链接:

其余 Telnet 命令相干操作,须要可参考 Dubbo 官网:

https://dubbo.apache.org/zh/d… 1

4.3 JMeter

对于 JMeter 测试 Dubbo 接口的办法,可参考往期文章:

《基于 Jmeter 实现 Dubbo 接口的测试》6

4.4 Dubbo-admin

对于 Dubbo-admin 的装置调试,可参考文章:

《dubbo-admin+zookeeper 的环境搭建实操与 Could not extract archive 报错踩坑》

dubbo-admin+zookeeper 的环境搭建实操与 Could not extract archive 报错踩坑_TesterAllen 的博客 -CSDN 博客_extract_archive 1

4.5 泛化调用

测试 Dubbo 服务的时候,咱们须要服务端的同学给咱们提供 API,没有这个 API 咱们是测不了的,而为了解决这个问题,Dubbo 官网又给咱们提供了另外一个办法,就是泛化调用,来看看官网的解释:

泛化调用的应用

Dubbo 给咱们提供了一个接口 GenericService,这个接口只有一个办法,就是 $invoke,它承受三个参数,别离为办法名、办法参数类型数组和参数值数组;

上面咱们间接上代码演示:

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.junit.jupiter.api.Test;

public class TestDemo {

@Test
void testDubboGenericService(){
// 援用近程服务
// 该实例很分量,外面封装了所有与注册核心及服务提供方连贯,请缓存
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
// 弱类型接口名
reference.setApplication(new ApplicationConfig(“order-service-consumer”));
reference.setInterface(“com.qinzhen.testmall.service.UserService”);
reference.setRegistry(new RegistryConfig(“zookeeper://127.0.0.1:2181”));

// 申明为泛化接口
reference.setGeneric(true);

// 用 org.apache.dubbo.rpc.service.GenericService 能够代替所有接口援用
GenericService genericService = reference.get();

Object result = genericService.$invoke(“getUserAddressList”, new String[] {“java.lang.String”}, new Object[] {“2”});
System.out.println(result);

}
}

运行后咱们来看看后果,咦~ 也胜利拜访了:

泛化调用的原理

咱们通过 debug 跟入 Dubbo 的源码中,能够失去如下的调用链:

服务生产端:

服务提供端:

从下面的调用链能够晓得实现一次泛化调用,Dubbo 框架经验了很多过滤器,咱们别离选取两端链路中的最初一步的 Filter 来简略理解一下泛化调用做了哪些事.

简化后的调用关系就如下:

先来看 consumer 端的 GenericImplFilter,大略看下外围的解决步骤:

// 判断是否为泛化调用
if (invocation.getMethodName().equals(Constants.$INVOKE)
&& invocation.getArguments() != null
&& invocation.getArguments().length == 3
&& ProtocolUtils.isGeneric(generic)) {
// 获取泛化调用参数
Object[] args = (Object[]) invocation.getArguments()[2];
// 判断是否为 nativejava 形式
if (ProtocolUtils.isJavaGenericSerialization(generic)) {

for (Object arg : args) {
if (!(byte[].class == arg.getClass())) {
error(byte[].class.getName(), arg.getClass().getName());
}
}
// 判断是否为 bean 形式
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
for (Object arg : args) {
if (!(arg instanceof JavaBeanDescriptor)) {
error(JavaBeanDescriptor.class.getName(), arg.getClass().getName());
}
}
}
// 设置为泛化调用形式
((RpcInvocation) invocation).setAttachment(
Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));
}
// 发动近程调用
return invoker.invoke(invocation);

再来看 provider 端的 GenericFilter,大略的外围解决步骤如下:

package com.alibaba.dubbo.rpc.filter;

import …

/**

  • GenericInvokerFilter.
    */
    @Activate(group = Constants.PROVIDER, order = -20000)
    public class GenericFilter implements Filter {

@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
// 判断是否为泛化申请
if (inv.getMethodName().equals(Constants.$INVOKE)
&& inv.getArguments() != null
&& inv.getArguments().length == 3
&& !ProtocolUtils.isGeneric(invoker.getUrl().getParameter(Constants.GENERIC_KEY))) {
// 获取参数名称、参数类型、参数值
String name = ((String) inv.getArguments()[0]).trim();
String[] types = (String[]) inv.getArguments()[1];
Object[] args = (Object[]) inv.getArguments()[2];
try {
// 应用反射获取调用办法
Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
Class<?>[] params = method.getParameterTypes();
if (args == null) {
args = new Object[params.length];
}
// 获取泛化援用形式应用的泛化类型
String generic = inv.getAttachment(Constants.GENERIC_KEY);
// 泛化类型为空的话就应用 generic=true 的形式
if (StringUtils.isEmpty(generic)
|| ProtocolUtils.isDefaultGenericSerialization(generic)) {
args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
// 判断是否为 generic=nativejava 形式
} else if (ProtocolUtils.isJavaGenericSerialization(generic)) {
for (int i = 0; i < args.length; i++) {
if (byte[].class == args[i].getClass()) {
try {
UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i]);
args[i] = ExtensionLoader.getExtensionLoader(Serialization.class)
.getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)
.deserialize(null, is).readObject();
} catch (Exception e) {
。。。
}
} else {
。。。
}
}
// 判断是否为 generic=bean 形式
} else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof JavaBeanDescriptor) {
args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]);
} else {
。。。
}
}
}
// 传递申请,执行服务
Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
。。。
}

下面的代码很多,着重来提取一小段看一下:

Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
Class<?>[] params = method.getParameterTypes();

从下面的代码中咱们便能够得悉原来泛化调用中应用了 Java 的反射技术来获取对应的办法信息实现调用的

4.6 用 Python 来测试 Dubbo

咱们晓得 Dubbo 是个 Java 我的项目,测试 Dubbo 就是模仿消费者去调用 Dubbo 的 Java 办法,那不言而喻,用 Python 是必定没法去间接调用 Java 的,然而在日常的工作中,很多小伙伴可能是 Pyhton 技术栈的,或者因为一些测试条件限度亦或历史起因,必须要将 Dubbo 测试用 Python 实现以满足各种接口测试的一个组合。

  1. python-hessian 库

Dubbo 是反对 hessian+http 协定调用的,hessian 是一种二进制序列化的形式。

理解到能够通过这种形式实现,具体没有尝试过,还须要开发在我的项目中将序列化的形式改为 hessian,并且须要晓得 URL,有趣味的小伙伴能够去理解一下。

  1. telnetlib 库

telnetlib 是 Python3 自带的一个库,能够调用 telnet 命令,其实也就相当于下面说到的应用 telnet 形式拜访 dubbo 的办法

  1. 开发 dubbo 测试服务

咱们能够应用 Java 来开发一个 Dubbo 测试的 Web 服务,实现上就能够应用 Dubbo 的泛化调用,而后咱们再用 HTTP 拜访的模式去拜访这个服务,将咱们的测试参数信息传过来,剩下的就交给 Java 去解决就好了。

这样通过封装设计后,能够实现 Python 端的使用者在拜访 Dubbo 时就像在测试 HTTP 接口一样(例如 Python 的 request 库);另外服务的 IP、端口、注册核心等信息都不必呈现在测试的工程中,只须要用环境标签做辨别,在服务端进行申请转发即可,也保障了肯定安全性。

大体上的思路流程如下:

以上,期待与大家多交流学习。

退出移动版