第3章3.2 接口测试实战——京东系统质量保障技术实战(2)

发表于:2017-11-03 16:56

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:商城研发POP平台    来源:电子工业出版社

  3.2 接口测试实战
  随着各系统的服务化设计,特别是SOA 架构的流行,接口成为系统与系统间通信的桥梁。所以,接口测试的地位越来越重要。现代互联网的接口大致可分为HTTP接口和自研RPC(Remote Procedure Call,远程过程调用)接口。HTTP 接口可能更为普遍一些。
  3.2.1 HTTP 接口实战
  一般测试Web 应用时都会接触HTTP 接口。HTTP 接口常用的有GET 和POST请求,它们就是构建网络通信的基石。通常测试HTTP 接口的方式也特别多,大体分为工具类和代码类。
  Apache 的HttpClient 是一个工具类库。它可以很方便地帮助我们构建HTTP 请求和解析响应。
下面就用一个具体的例子说明如何进行测试。
  首先需要在Maven 工程中引入依赖。目前Apache 的HttpClient 类库版本已经升级到4 以上(例如4.5.1 版本)了。
还有一个是当响应为JSON 时,如何更好地转化为对象的fastjson 类库(例如1.2.7 版本)。
  其次进行GET 请求的封装。可以对返回的响应结果进行简单的断言操作。如代码示例3.2.1 所示为HTTP GET 请求封装。
  代码示例3.2.1
  private JSONObject getJsonRes(String url) throws IOException {
  CloseableHttpClient httpclient = HttpClients.createDefault();
  HttpGet httpget = new HttpGet(url);
  CloseableHttpResponse response = httpclient.execute(httpget);
  try {
  HttpEntity myEntity = response.getEntity();
  System.out.println(myEntity.getContentType());
  System.out.println(myEntity.getContentLength());
  return JSON.parseObject(EntityUtils.toString(myEntity));
  } finally {
  response.close();
  }
  }
  JSONObject obj = getJsonRes("http://www.sosoapi.com/demo/swagger/user/
  simple/1/info.htm");
  Assert.asserEquals("demo", obj.get("nickName") );
  经过以上几步,就快速完成了一个JSON 格式的HTTP GET 请求的测试了。
  当然,还有POST 方式,这会比GET 方式稍微复杂一些。HTTP POST 请求封装如代码示例3.2.2 所示。
  代码示例3.2.2
  private String postJsonRes(String url, HttpEntity entity) throws IOException
  {
  CloseableHttpClient httpClient = HttpClients.createDefault();
  HttpPost post = new HttpPost(url);
  post.setEntity(entity);
  CloseableHttpResponse response = httpClient.execute(post);
  System.out.println("Response code: " + response.getStatusLine().
  getStatusCode());
  try {
  String resString = EntityUtils.toString(response.getEntity());
  return JSON.toJSONString(resString);
  } finally {
  response.close();
  }
  }
  上面是POST 的封装方法,代码示例3.2.3 为HTTP 接口调用方式。
  代码示例3.2.3
  @DataProvider(name = "addDatas")
  public Object[][] addDatas() {
  return new Object[][]{
  {"http://www.sosoapi.com/demo/swagger/user/simple/add.htm",
  "1@abc.com", "test"}
  };
  }
  @Test(dataProvider = "addDatas")
  public void postTest(String url, String email, String nickName) throws
  IOException {
  List<NameValuePair> urlParams = new ArrayList<NameValuePair>();
  urlParams.add(new BasicNameValuePair("email", email));
  urlParams.add(new BasicNameValuePair("nickName", nickName));
  System.out.println(postJsonRes(url, new UrlEncodedFormEntity
  (urlParams)));
  }
  这样就完成了HTTP 的GET 和POST 请求了。  3.2.2 自研RPC 接口实战
  要学习接口测试,先要对RMI(Remote Method Invoke,远程方法调用)中的stub(桩)和skeleton(骨架)的概念有一点了解。RMI 的代理模式是通过代理对象将方法传递给实际对象的。stub 驻留客户端,承担着代理远程对象的实现者的角色,skeleton 类帮助远程对象与stub 连接进行通信。
  目前我们接触到的接口类型主要是Hessian、Dubbo 和HTTP 接口。Hessian 和Dubbo 都是远程方法调用的一种实现,客户端需要保留stub 来调用接口。这就是为什么我们在做Hessian 和Dubbo 类型的接口测试时,需要引入接口的jar 包。当然也可以在项目中引入被测接口的定义,如果有相关的自定义类,也一并复制过来(直接复制代码)。
  认识三种类型的接口:
  Hessian 是远程方法调用的一种,常被用来与Web Service 做比较,相对于WebService,其实现更简洁。Hessian 采用的是二进制RPC 协议(基于HTTP 协议),所以它很适合发送二进制数据,不太适用于复杂对象类型的传输。
  Dubbo 是一个远程方法调用框架。Dubbo 注册中心负责服务地址的注册与查找,相当于服务目录;Dubbo 监控中心负责统计各服务调用次数、调用时间等。
  HTTP 类型的接口是构建在Web 应用之上的,基于HTTP 协议传输文本。当一个URI 发起请求时,doGet 或者doPost 方法会被调用,获取相应的参数。测试HTTP接口时只需要通过URI 定位接口并传递参数,相对比较简单。
  下面针对三种类型接口的测试方式进行简要说明。
  Hessian 接口测试:通过接口URI 获取接口,如果复制接口定义及其自定义类,接口和自定义类的包名尽量跟开发包保持一致,不建议使用直接复制代码的方式,因为这样不便于维护,直接在POM 文件中引入接口和Hessian 依赖的jar 包。通常借助HessianSpringFactoryBean 获取接口。
  Dubbo 接口测试:如果通过Dubbo 的注册中心获取服务接口,那么在搭建测试环境时需要指定Dubbo 注册中心的地址,测试客户端也需要配置Dubbo 注册中心地址以及对外提供服务的接口名称。直连的方式在测试端需要引入Dubbo 框架相关的jar 包。
  HTTP 接口测试:相对来说最简洁,不需要引入接口的jar 包,通过HttpClient一系列的类来调用接口。
  三种类型的接口测试方法因框架的不同其配置文件有所差异,但总的来说都是获取接口、准备数据、执行用例、判断结果这些步骤。
  3.2.3 一款简单易用的接口测试框架
  在现在的接口测试过程当中,不同业务的接口通常采用不同的协议类型,如:Dubbo、Web Service、Hessian、HTTP(HyperText Transfer Protocol,超文本传输协议)等。不同的协议有不同的优点,如Dubbo 协议使得应用可通过高性能的RPC 实现服务的输出和输入,并且支持负载均衡、容灾和集群功能。Web Service 技术能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件,就可相互交换数据或集成。相比Web Service,Hessian 更简单、快捷。采用二进制RPC协议很适合于发送二进制数据。HTTP 协议支持客户/服务器模式,简单快速、灵活、无连接、无状态。不同的业务类型的服务会采取不同的接口协议,实现业务的需要。
  规模大的企业由于业务复杂,往往会用到多种协议。对于内部测试工程师来说,只关心服务接口是否能按预期工作,对协议的优点并不关心。但是协议本身的复杂性,对内部测试来说带来了很多不便。比如负载均衡可以让请求在性能最优的服务IP 上执行,但是在测试中往往需要针对某个指定的IP 进行测试。而且随着协议的增多,如果每个接口的调用都通过对应的协议方式,那么工作量会成倍增加。
  由于协议本身复杂性太高,通过协议直接调用接口服务极其复杂。现有的业务接口服务一般是把一个协议类型的接口关联到开发好的特定客户端中。客户端把调用者的请求转为协议格式传送到接口服务并把接口服务的响应结果返回。调用者通过特定的客户端调用,进行与目标接口服务的通信。Dubbo 架构如图3.2.1 所示。
节点角色说明:
  Provider:暴露服务的服务提供方。
  Consumer:调用远程服务的服务消费方。
  Registry:服务注册与发现的注册中心。
  Monitor:统计服务的调用次数和调用时间的监控中心。
  Container:服务运行容器。
  图3.2.1
  Consumer(用户或测试工程师)想要调用Provider(接口服务),首先需要去Registry(注册中心)订阅自己所需的服务。然后,注册中心返回服务提供者地址给用户或测试工程师。返回的地址是基于负载均衡算法得出的性能最优地址,但是不一定是测试需要的地址。最后,用户或测试工程师通过返回的地址请求接口服务。在此过程中,需要测试工程师编写专业代码实现请求过程,需要掌握较多的开发知识。
  目前不同的协议类型接口都有其特定的类似Dubbo 的模式,即使测试工程师掌握较多的开发知识,也需要对不同接口编写不同的代码来达到通信调用,这样会导致大量无效工作。
  这种情况对测试工程师来说通常有以下3 个缺点。
  调用服务提供者前需要访问注册中心客户端询问服务地址。访问注册中心需要开发代码,对测试工程师的知识要求高。
  . 不同协议类型接口,关联在不同的平台下。对不同协议接口测试,需要连接不同的平台,工作量大,不容易维护。
  . 注册中心返回的服务地址是根据负载均衡性能得出的最优的地址,但不一定是测试需要的。测试时,往往需要对某个特定IP 的服务进行调用。
  所以,构建一款简单易用的接口测试框架非常必要。该框架有以下3 个优点。
  . 不通过客户端调用服务接口。
  . 支持多种协议类型的接口调用。
  . 可以指定服务器执行接口方法。
  下面笔者介绍一下所在部门自主开发的简单接口测试框架,供大家参考。通过该框架,测试工程师只需通过拼装一个URL 在浏览器或HTTPClient 中请求到服务器就能返回对应结果。请求进入服务器端后,服务器端会根据请求内容利用Spring框架找到对应接口服务的实例。然后通过Java 反射API 获得对应方法列表,再通过该API 获得方法列表中的方法对象,并将这些对象存到本框架中的代理类中。有了方法对象后就能通过Invoke 方式执行接口服务了。在此过程中,只需要测试工程师了解被测试接口服务的基本信息和对应参数即可。由于浏览器程序提交请求时使用HTTP GET 方式,因此可以针对具体IP 进行请求,这样就做到了有针对性的测试。
  框架结构如图3.2.2 所示。
  服务请求:包含目标应用所在域名、BeanID、方法名、参数数据及校验密码的
  URL,该URL 通过浏览器或HttpClient 请求到服务器端。
  参数校验:参数校验模块用来校验测试工程师输入的参数是否与对应接口匹配,如果不匹配将返回错误信息。如果填写的参数正确,才能继续进行接下来的流程。
  容器缓存:容器缓存中存放了和项目的Bean 实例一一对应的代理类。测试工程师填写的参数正确后,会根据Bean ID 去容器缓存中查找对应的Bean 实例的代理类。如果不存在,则将生成并添加代理类到容器缓存中。如果存在,则将通过代理类获取并执行Bean 方法。
  获取接口服务模块的实例(Provider):Spring 框架在初始化时,会对项目中所有的类进行实例化,生成对应的Bean 实例。大致如下:首先读取项目的配置文件,然后将读取到的代表一个Bean 的信息放到一个对象中。这个对象的类就是BeanDefinition(包括ID、ClassName 和一个装PropertyDefinition 对象的List),还有一个辅助类就是属性值对象的类PropertyDefinition,这两个辅助类就是普通JavaBean对象,都有getter 和setter 方法。Spring 框架通过getter 方法提供了查询Bean 实例的方法。
  生成代理类:代理类主要有获取Bean 方法和动态执行Bean 方法的功能。获取到Bean 实例后,创建一个代理类实例。通过Java 反射API 查询到对应类及继承和实现的所有方法列表。通过Java 反射API 获得方法列表中的方法对象,然后把每个方法对象存储到代理类的缓存中。有了这些方法对象,就可以通过Java 反射API 中的Invoke 方法执行了。所以可以通过找到代理类,然后通过参数中的方法名到其缓存中找到方法对象,再通过方法对象中的Invoke 最终执行。最后将代理类添加到容器缓存中,以便重复使用提高性能。
  综上所述,测试工程师只需在浏览器中填写必要的接口信息和对应参数,就能执行服务器中的接口方法,对测试工程师知识技能要求较低。内部实现过程中有多个优点:
  . 测试工程师并不用写程序连接接口服务的客户端。
  . 由于此框架实现方式不需要接口协议的对应功能,没有通过接口协议方式调用,因此适用于所有Spring 框架下的接口方法。
  .此框架可以通过HTTP GET 方式指定特定服务器IP 执行测试。
  综上所述,本节先是介绍了HTTP 接口的测试过程,用Apache 的HttpClient 类库进行接口测试。然后介绍了关于自研RPC 的接口测试,因为不同的RPC 框架给出的接口测试方法不一样,但是测试的步骤或者说思想是相似的。最后给出了一个接口测试框架的解决方案,虽然框架不可能应用所有的场景,但是希望给大家带去一些思考。
  3.3 Mock 实战
  在测试过程中,对于某些不容易构造或者不容易获取的对象,我们常常会用一个虚拟的对象代替以便测试。在具体测试过程中,经常会碰到需要模拟数据或者接口的情况。因为环境问题或者系统复杂度问题,我们需要使用Mock 方式进行数据的伪造。所以,产生了两大类Mock 的场景。一种就是Mock 一个对象,写入一些预期的值,通过它进行自己想要的测试。另外一种就是Mock 一个服务,构造一个假的服务返回预期的结果,也是为了进行自己的测试。这两个场景构成了大部分的Mock 使用范围。
  3.3.1 对象Mock 实战
  对于对象的构造,比较流行的方式就是用Mockito 构造假的对象。Mockito 尝试
  用不一样的方式进行测试,能够代替EasyMock 框架,上手简单。
  来看一个具体使用的例子。
  首先,引入Mockito 的依赖,本文用的是1.8.5 版本。用来模拟list 对象的例子,如代码示例3.3.1 所示。
  代码示例3.3.1
  public class SimpleTest {
  @Test
  public void test() {
  //创建mock 对象,参数可以是类或者接口
  List<String> list = mock(List.class);
  //设置方法的预期返回值
  when(list.get(0)).thenReturn(“helloworld”);
  String result = list.get(0);
  //验证方法调用
  verify(list).get(0);
  //断言,list 的第一个元素是否是helloword 值
  assertEquals(“helloworld”, result);
  }
  }
  通过上面的例子,我们就构造了list 这样的对象,并且给第一个元素赋值helloworld。在最后断言的时候,也可以验证这个list 里面确实有这个值。所以通过这种方式,我们就可以进行对象构造了。可以是类,也可以是接口。
  除了构造对象,当然也可以对方法设定返回指定异常。
  when(list.get(1)).thenThrow(new RuntimeException(“test exception”));
  上述代码的意思就是当调用 list 的第二个元素时,抛出一个运行时异常。异常的消息是“test exception”。
  当然,上面只是列出了Mockito 的简单用法。对于比较复杂的用法,因为篇幅有限,请读者自行学习。因为Mockito 主要用于单元测试,一般是开发人员在用这个工具,测试人员接触的机会不多,所以可以根据兴趣了解它。
  3.3.2 接口Mock 实战
  在进行产品测试时,我们会碰到下面几种情况:
  第一,依赖接口不通。例如,在测试订单系统时,需要调用商品系统的接口获取商品信息。如果商品接口不通,则需要等待商品接口恢复之后才能继续测试。这种情况在测试环境中尤为突出。依赖接口能不能稳定一些呢?
  第二,异常数据模拟困难。例如,在测试商品系统时,当需要调用商家系统的接口认证商家为非法商家时,接口正常情况下是无法提供这样的数据的。可能这样的测试执行会比较困难。那如何简便地构造接口的异常数据呢?
  第三,依赖接口性能参数无法保障。在对商品系统进行压力测试时,需要商家接口及时返回数据,满足商品系统的调用频度。现在的做法是,找一台高性能机器,部署商家系统来满足商品的压测要求。在依赖接口多的情况下,这会带来很大的额外工作量。如何能够减轻这个工作量呢?
  可能大家想到用Mock 的方式,去模拟一个网络的接口。想要得到的是被测系统的输出。举例说明:Tesla 是被测系统,数据输入后经过Tesla,Tesla 要调用Stub接口,然后返回数据。就像之前举的例子:商品系统调用商家接口。如果Stub 接口挂了,需要用Mock 服务代替这个接口,返回定制好的数据,然后得到输出,完成数据的正常流转。
  那是不是有Mock 服务就完全解决了开头提到的三个问题呢?其实并没有。在笔者研究了业界众多的Mock 方案后,例如支持HTTP 协议的WireMock,支持WebService 的SoapUI 里面的MockService,Dubbo 需要自己写接口实现。从中看到这样的缺点:不同协议的方案从属于不同的平台,多个Mock 服务无法有效管理。
  Mock 服务的平台管理部分,各有各的方案,主要看便利性。而对于Mock 方案,不同的方案千差万别。看起来都是对于接口的模拟,如果不是从协议底层出发,那它的扩展性可能不会很大。
  现代RPC 的本质其实就是在网络上传输数据包,而这个数据包的特点是Header+Body。Header 就是协议头,分为定长或者变长,这个取决于协议的设计者。例如Dubbo 协议就是定长的。而有些协议是变长的。Body 就是消息体,其实就是对象的序列化过程,把序列化好的数据放入到Body 里面。现在流行的序列化方案有Hessian、Java-built-in、JSON、MsgPack 等。
  底层框架用NIO/Netty 架构。因为是异步通信,需要支持高性能、高并发,Netty框架就可以做到。
  建立通信之后,客户端发起请求,发送数据包到服务端。服务端拿到数据包之后进行解码操作。
  服务端构造响应结果,序列化完成后,加上协议头信息,返回给客户端。过程虽然简单,但是在实际开发过程中还是可能会碰到一些问题。下面就是笔者在实践时碰到过的一些问题供大家参考。
  第一,心跳包的处理。什么是心跳包,其实就是为了保持连接,客户端向服务端定时发起的检测包,里面没有任何的具体内容,只是用于告诉服务端,系统仍然存活。因为没有Body,所以要做特殊的处理,不能直接按照有响应体的数据包进行处理。
  第二,客户端可能是连着发请求,数据包是连在一起的,需要判断长度,到某个字节一次请求就结束了,跟着的应该是下一次请求了。这就需要对数据包进行合理切割。
  第三,数据包的协议头部分可能带有认证信息,例如requestID 之类的,需要在返回时把id 返回到客户端。因为一般客户端都会有认证的机制,如果id 不一样,则不解析这个响应。
  对于接口的Mock,从协议底层下手扩展性好,处理较方便,能够协助剥离依赖关系,更便捷、专注于自身系统的测试工作,达到事半功倍的效果。
  综上所述,本小节分场景介绍了两种Mock 方式。Mockito 在开发中使用广泛,接口的Mock在开发和测试中有着很好的运用。当然接口的Mock 在理解上有些复杂,对于单一应用场景下运用得较少。在此,主要是介绍一种思路,希望能引起读者的兴趣。
  3.4 分层测试的思考
  笔者所在团队一开始做自动化时并没有考虑到分层测试。因为那时候,能够自动化已经不太容易,要么是没有自动化的团队,要么只有不多的自动化脚本产出。这个时候是不需要思考分层的。但是,当自动化发展得越来越成熟时,碰到的问题也越来越多。有一个不可忽视的问题出现了,就是如何让自动化发挥更大的效能。这时分层测试就出现了。
  3.4.1 分层测试的理解
  分层测试的概念很早就被Martin Fowler 提出来了。
  按照他的观点,Unit 单元测试成本最低、收益最高,Service 集成测试成本较低、收益较高,UI 系统测试成本最高而收益最低。为什么这么说?因为Unit 单元测试需要的人员成本很低,测试的点非常细致,可以到方法级别。这样的覆盖是非常全面的。所以,从收益上来说,Unit 测试最高。
  Service 次之。因为Service 集成测试面对的基本上是接口层级,测试粒度上会比单元测试粗些。但是,Service 的测试会针对每个接口进行测试,收益也非常高。因为我们知道,随着系统复杂性的增加,我们都会通过接口方式进行模块间的解耦。这个时候接口测试就非常必要了。而Service 的测试正好对这个层级进行覆盖,所以价值也是非常高的。而且接口的变动会比UI 变动低,所以成本相对较低。
  UI 测试因为UI 的变动非常频繁,成本最高。收益也是根据脚本的覆盖程度有很大的差异。现在,很多团队在做UI 测试时,基本上放弃了脚本覆盖,或者脚本只是覆盖核心的功能。
  所以,总的来说,Unit 测试处于金字塔的底层,价值最大,Service 测试次之,而UI 测试又次之。
  3.4.2 京东怎么做分层测试
  在笔者所在的团队,也是经历了一个探索的时期,才找到了适合团队的分层测
  试方式。现在分享给大家。
  第一阶段,UI 测试的探索。此阶段,团队进行了一个UI 框架的开发。在此基础上,团队进行了UI 脚本的编写。把大部分核心功能进行了脚本的覆盖。当时效果不错。但是,随着时间的推移,UI 改变越来越大,很多核心逻辑也有较大改动。UI脚本渐渐跟不上业务的变更步伐了。

本文选自《京东系统质量保障技术实战》第三章,本站经机械工业出版社和作者的授权。
版权声明:51Testing软件测试网获机械工业出版社和作者授权连载本书部分章节。
任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责任。


相关文章
第3章自动化测试实战——京东系统质量保障技术实战(1)
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号