接口测试 接口自动化测试实践的记录

simonpatrick · 2015年11月27日 · 最后由 梁勇彪 回复于 2016年10月24日 · 5463 次阅读
本帖已被设为精华帖!

接口测试实践的记录

在敏捷开发交付的流程中,自动化测试实际上被放在一个看起来挺重要的位置,而自动化测试中,接口测试是一个投入产出比比较高的
一种自动化测试的形式,而我自己也做了一个这样的脚手架一样的东西可以方便进行自动化测试,关键是在一些现有第三包的基础上做实现,其实一个脚手架不需要几个 JAVA 类就可以完成了,至少我自己的这个在 10 个文件以内.要论行数估计也没有多少代码量,主要时间其实都是在想怎么更方便的写自动化测试,怎么使用以后的开源代码了。

下面介绍一下我自己如何完成这个自动化接口测试
脚手架设计和实现的,以及我自己实现过程中的种种发现。主要从以下几个方面来讲:

  • 如何构建接口自动化测试的脚手架
  • 关于接口测试参考的一些资源
  • 关于接口测试的后续的一些想法

如何构建接口自动化测试的脚手架

接口测试本文中主要是指 HTTP 的请求,构建接口自动化测试脚手架的时候,首先先看看平常接口测试,测试人员时如何做的,我了解主要是以下几种方式:

  • 通过操作页面/APP 来触发接口调用
  • 使用诸如 SOAPYUI/JMETER/POSTMAN 或者其他的客户端工具来进行接口测试

我自己都使用过 SOAPUI/JMETER/POSTMAN,不能说使用的多么深入,但是常用的功能也都有用过,比如 SOAPUI 构建一个项目完整的接口自动化测试用例,大概有 200+ 以上的用例,可以支持不同的测试环境,检查点中可以检查数据库,使用 XPATH/XQUERY 来检查/获取指定的值,进行不同 API 的数据传递等等,这些工具 (指功能测试方面) 大体的逻辑我觉得是类似的,基本上都有:

  • 发起请求的客户端,需要测试人员构建,也有通过 WSDL/WADL 自己生成的,不过数据都是需要测试人员输入的
  • 根据表达式进行取值的 Resolver,就是可以根据 XPATH/XQUERY 语法,或者其他的语法来获取指定的值, 就是用来传递上下文数据的一种方式
  • 外部可以参数话数据,比如环境配置
  • 可以查看测试结果,这个其实可以理解为某种测试框架的一个功能,比如 JUNIT,TESTNG

接口自动化测试脚手架的构建

根据以上的分析如果自己需要实现的话,最主要需要实现一下其实就是请求的构建,请求构建包括了:

  • 发起请求的客户端
  • 请求数据的构建

对于发起请求的客户端就直接使用了 Spring RestTemplate,考虑的主要原因如下:

  • 使用相对比较方便,模块化比较清晰
  • 可以使用 HTTPClient 的实现
  • Spring RestTemplate 所在的包还有其他一些接口的支持,以后如果使用其他接口可以不需要换包也可以做

在实际的使用过程中,其实也遇到了一些问题,比如如下的内容:

  • HTTPS 的访问
  • 开发接口定义不够准确的问题,造成使用 RestTemplate 时候出现了一些不在开始预期范中的问题

如何解决这些问题,在后面再详细介绍,这里说明一下使用 RestTemplate 的一个主要流程:

  • 1. 构建请求,设置请求的 Header,URL,Accept,ContextType,Token 等等
  • 2. 调用请求获取返回的 Response, 这个 ResponseRestTemplate 中实际上封装了一个 ResponseEntity 的类,里面包括了请求状态,Body 之类 RestTemplate 有个好处就是如果给 RestTemplate 设定了 MessageConverter 的话,他可以自动把请求的返回类型直接转换,比如你发起请求的时候设置了 JOSN 的 Message Converter,他可以帮你把类,或者字符串自己转化为 JSON 来发送,同样如果是返回值是 JSON 的话,也可以帮你自己将 JSON 转换成你指定类型的 JAVA BEAN

说完这个流程,我们就说说如何通过 RestTemplate 构建一个简单的 HTTP 请求:

Map<String,String> urlVariable = new Map<String,String> ();
urlVariable.put("q","test");
JavaBean javaBean = restTemplate.getForObject("http://www.baidu.com",JavaBean.class,urlVariable);
JavaBean javaBean1 = restTemplate.postForObject("http://www.baidu.com",JavaBean.class,urlVariable);
ResponseEntity e =  restTemplate.getForEntity("http://www.baidu.com",JavaBean.class,urlVariable);

实际上使用 RestTemplate 还是挺简单的,不过为了让使测试更为方便一点,然后每个人的代码更统一点,自己重新封装了一下 RestTemplate 的使用,主要分为三个概念:

  • Service 的描述
  • 测试数据
  • 客户端调用

接口服务描述

Service 的描述实际上就是一个 JSON 文件,只不过自己规定了一下,格式类似于,这个文件描述了 API 的定义,当然 API 的 body 没有在这个里面,不过为了不把事情搞复杂,就暂时不放在这个里面.

{
  "apiDomainName": "applicationName",
  "contentType": "application/x-www-form-urlencoded",
  "headers": {
    "Accept": "application/json, text/javascript, */*"
  },
  "method": "POST",
  "pathParameters": [],
  "queryParameters": [
    "username",
  ],
  "resourceURL": "/application/subdomain"
}

测试数据类:

private Map<String, String> queryParameters = Maps.newHashMap();
private Map<String, String> pathParameters = Maps.newHashMap();
private Map<String, String> headers = Maps.newHashMap();
private T body;

而如何调用客户端就变成,而且其实每一个 API 的访问其实都可以这样子来做,

ResponseEntity response = RestTemplateHelper.build(serviceDescriptionPath,requestData).call();

说明一下的是:

  • serviceDescriptionPath 就是接口的描述
  • requestData 就是需要进行测试的数据

然后实际上接口的描述是开发还没有开发好的时候就已经定了的,所以这里的变量就变成如何构建 requestData 了

构建 RequestData

构建 requestData 实际上就是设计测试用例,那么这里也是使用 Excel 的方式,将不同的值填写到 excel 里面,不过为了减少 set 值这样的操作,这个脚手架就提供了一些工具,可以直接将数据设置到 RequestData 实例,具体的操作如下:

Excel 是如下格式的:

变量名 测试用例 1 测试用例 2
data.queryParameters(username) 1 1
data.queryParameters(year) 2015 2014
data.queryParameters(month) 10 11

说明一下,通过反射的方式,可以直接生成一个 requestData 的实例,同时 queryParameters 中值已经设置好了,这样调用代码中就不需要写类似于:

RequestData data = new RequestData();
data.queryParameters.put("username","1");
data.queryParameters.put("year","2015");

这里有兴趣的同学可以参考这个包:里面其实已经有很方便的通过反射去赋值了,

<dependency>
    <groupId>org.jodd</groupId>
    <artifactId>jodd-bean</artifactId>
    <version>3.6.6</version>
</dependency>

使用 TestNG 的 DataProvider

刚才讲述了如何发生生成数据,那么通过 Excel 的方式提供不同的数据,就可以通过 TestNG 的 DataProvider 了
所以测试数据通过,TestNG data provider 的实现在这里就不多少了,网上其实有很多内容了.

接口测试的代码看起来就是这个样子了

@DataProvider(name = "data")
  public Iterator<Object[]> getAPITestData(Method m) throws Exception {
      Map<String, Class> clazz = new HashMap<String, Class>();
      clazz.put("RequestData", RequestData.class);
      Iterator<Object[]> y = TestData.provider("testcase/api1.xls", m, clazzMap);

      return y;
  }

  @Test(dataProvider = "data")
  public void testAPITest(RequestData data) {
    ResponseEntity response = RestTemplateHelper.build(serviceDescriptionPath,requestData).call();
    Assert.assertEqual(response.getStatus,200); // response 的期望值实际可以通过dataprovider传入
  }

而且几乎所有的代码都差不多成这个样子了,那么获取可以写个代码生成的东西,当然最后通过了 JsonPath 写了一些获取 JSON 值的工具,这个暂时也就不说了.

那么代码生成吧

当封装好这些东西之后,发现所有的接口都类似了,然后就做了代码生成的工具了,代码生成器的入口实际上个就是那个服务描述文件开始的,
所以代码生成器的参数就是服务描述文件,在实际的使用的过程中,接口描述这个文件也可以自动生成,目前总共支持以下几种:

  • 手动编写描述文件
  • 抓取开发 API 规格网站接口的描述,自动生成描述文件
  • 解析 HAR 文件自动生成描述文件,解析 HAR 其实不难,就是繁琐一点字段有点多

后续想打通和 POSTMAN 的连接,可以接收 POSTMAN 的导出文件,然后也可以导出 POSTMANT 的,以后开 BUG 就什么也不说,直接放一个 POSTMAN 文件其实也挺帅的

至此一个接口测试的脚手架就大致完成了.总结起来就是:

  • 封装了 RestTemplate,让他接受一个接口的描述文件,一个请求的数据
  • 通过 Excel 传数据给请求的数据进行数据驱动
  • 相同类似的代码进行代码生成

最后其实这样子使用下来,接口构建几个简单一点的自动化测试用例,其实也就是几分钟的事情.

一些细节

在实现过程中,实际上还有一些特殊情况,比如说需要 token,认证信息,这些通过一个公用函数的方式就可以解决,然后在代码生成的时候
直接讲这个放在实际测试的接口前面调用. 后有就是上面说到的的:

  • HTTPS 的访问
  • 开发接口定义不够准确的问题,造成使用 RestTemplate 时候出现了一些不在开始预期范中的问题

HTTPS 的访问是通过如下代码解决的,创建一个略 SSL 的 httpclient 就可以了

public static RestTemplateClientHelper getHttpClientImplInstance(){
       RestTemplateClientHelper client = new RestTemplateClientHelper();
       HttpClient httpClient = getIgnoreSSLHttpClient();
       client.setTemplate(new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient)));
       return client;
   }

   /**
    * 获取忽略SSL的httpclient,支持https的请求
    * @return
    */
   private static HttpClient getIgnoreSSLHttpClient() {
       CloseableHttpClient httpClient = null;
       try {

           httpClient = HttpClients.custom().
                   setHostnameVerifier(new AllowAllHostnameVerifier()).
                   setSslcontext(new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                       public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                           return true;
                       }
                   }).build()).build();
       } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
           logger.error(e);
       }
       return httpClient;
   }

还有一个就是有时开发的接口返回类型 (accept type) 不能让 RestTemplate 处理,那么其实添加自己定义个 MessageConverter 就好了:
下面是一个修改阿里自己的 FastJSON 的 MessageConverter 的例子,
其实也没改什么,就是捕捉了一个异常,主要是不知道什么原因调用时候 readInternal 就抛出和编码格式有关系的异常,然后就捕捉了一下异常反正也就把那个问题就没有了,不过这个改法应该也是有问题的.

public class ModifiedFastJsonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
   ........
    public ModifiedFastJsonHttpMessageConverter() {
        super(new MediaType("application", "json", UTF8), new MediaType("application", "*+json", UTF8));
        this.charset = UTF8;
        this.features = new SerializerFeature[0];
    }

    ............

    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        InputStream in = inputMessage.getBody();
        byte[] buf = new byte[1024];

        while(true) {
            int bytes = in.read(buf);
            if(bytes == -1) {
                byte[] bytes1 = baos.toByteArray();
                try {
                    return JSON.parseObject(bytes1, 0, bytes1.length, this.charset.newDecoder(), clazz);
                }catch (Exception e){
                    return baos.toString("UTF-8");
                }
            }

            if(bytes > 0) {
                baos.write(buf, 0, bytes);
            }
        }
    }
      ........
  }  

后续的一些想法

后续希望在这个基础上再做点其他的一些事情:

  • 增加 POSTMAN 的代码生成的支持
  • 探索能不能通过 API 接口描述直接生成 JMETER 的 JMX 文件,可以讲基础的 JMETER 性能测试的基础代码也生成好
  • 整理一下放到 GITHUB 上面,其实整个脚手架自己也就是几个文件而已,:)
  • 建立一个 MOCK SERVER,方便模拟一些 API 调用的方式
  • 做一个简单点获取 JSON 中指定字段,然后传递给下一个 API 使用的工具

一些资源

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 21 条回复 时间 点赞

感觉走偏了。。。

设计思路还是挺好的. 采用的是标准方案. 代码生成貌似没提太多, 估计是还没做吧.
基本上和我们公司现在用的框架是一致的.
你的是 testng+restemplate+har 代码生成.
我的是 scalatest+playws+har 代码生成.
还是挺像的. 思路也很像

问题在于那个描述文件 其实本身就已经跟 har 很接近了. 将来用起来不会太方便.
同时 java 的样本代码也有点多, 可以考虑进一步精简.

总体不错, 挺赞的.

#2 楼 @seveniruby 代码生成其实已经做好了.如果有 HAR 就不用去写那个描述文件了,会根据 HAR 自己生成,如果接口还没有开发完成还没有实际使用过,这种情况下就只好自己去写描述文件了. 然后填写测试数据来做测试用例设计了。理想情况下 (按照需求说明文档的规范做的),开发好了,接口自动化测试也可以跑起来了。不过代码确实需要再精简,要更少代码更少代码。

#2 楼 @seveniruby 下次帮我看看我那个,感觉偏了😅

—— 来自 TesterHome 官方 安卓客户端

#7 楼 @chenhengjie123 另外这块我觉得和开发的规范有关系,如果一些返回字段实际是表示相同内容的 (业务上的意义),而这些字段名又都不同,传这个上下文感觉是会啰嗦的。

#3 楼 @simonpatrick 有个问题想问下,你一开始有提到进行上下文数据的传递,但你文中没有提到具体的实现方式。想问下你这一块的实现是放在了 Java 里面还是在 excel 中?

#5 楼 @chenhengjie123 目前这块做的不太好,后续需要放不少精力做这个。目前已经做的一些是这样的:

  • 数据创建的时候根据上下问去再造点数据,比如说有些测试可能是这样的测试用例说一个已经有过订单的人,这个时候其实用例里面应该说不是具体的一个人,而是满足条件的这个人,所以在 Excel 里面其实是放有过订单的这个类型,然后在用一段 sql 去获取满足条件这样的人,那么如果一般情况时这样的话,自己写代码的话可能就是先创建个类在去执行一段 sql 去取值,然后再赋值给这个人,我现在做的是,创建数据类是通过一个工厂方式去创建的,然后这些根据一些初始化的内容获得的东西,都会放在 beanPostConstruction 中去实现,这样 Excel 里面填写一些关键数据,然后通过这个 DataFactory 去创建 JAVA Bean 的时候会自动运行 beanPostConstruction 里面的东西,这样可以少写一些代码

-还有一种就是几个 API 调用之后,有些内容需要从前一个 API 获取,再给第二个或者第三个 API,这个还没有实现的很好
我自己大体的思路就是把 API 的返回都放到一个结果集里面,一个 MAP 里面,然后在根据一些传入的表达式来获取这些值,再来传递,不过还没有完全实现出来,一直没有想的太清楚这块,需要自己在写些代码试试

#6 楼 @simonpatrick 这块我也在想,但还没找到能兼顾灵活度和易用度的方案。
@seveniruby 思寒这块是怎么做的?

#8 楼 @simonpatrick 我现在遇到最常用的场景其实就是一些 id 的传递。create 接口执行成功时会返回 id ,然后其他接口要把 id 作为参数去获取相关的信息。

Jmeter 的实现是有一个 post processor 专门做这个事情(类似于你的 Resolver ,专门处理返回值),把返回值放到一些全局变量里,然后下一个接口再去调用这个变量。因为使用的是变量,所以字段名是否相同没有太大关系。

业务流程:
1、填写内容发布帖子
2、跳转到新发布的帖子
3、删除新发布的帖子

发帖
http://10.0.0.1/create/?content=HelloWorld
返回

<post>
    <id>001<id/>
</post>

查看帖子
http://10.0.0.1/post/?id=001
返回

<post>
    <content>HelloWorld<content/>
</post>

删除帖子
http://10.0.0.1/delete/?id=001
返回

<post>
    <status>deleted<status/>
</post>

用例 1:

  1. 发送http://10.0.0.1/create/?content=HelloWorld
  2. 验证 id 内容由数字组成
  3. 查询数据库,验证响应中 id 代表的帖子内容是 HelloWorld

用例 2:
前提:存在 id 是 002 的帖子内容是 WelcomeWorld

  1. 发送http://10.0.0.1/post/?id=002
  2. 验证 content 内容是 WelcomeWorld

用例 3:
前提:存在 id 是 003 的帖子

  1. 发送http://10.0.0.1/delete/?id=002
  2. 验证 status 内容是 deleted 3、验证数据库帖子是否

每次单条用例前,用单条 SQL 准备满足 “前提” 数据
或者直接准备一个满足 “前提” 的数据库
删除接口和查看接口就不用依赖发帖接口了

还是看不明白其中具体做法细节,原理比较容易懂

这个是什么的接口测试呢?没接触过,想了解一下。

做一个简单点获取 JSON 中指定字段,然后传递给下一个 API 使用的工具

在 excel 或者数据库里面维护测试数据加个字段应该能达到你的要求, 是否依赖 (Y/N),比如依赖哪个接口,目前我这边接口测试是这样做的,这样你所有的数据,可以全部批量生成了,response 的数据再提取出来给下一个接口参数。

此文甚好 容我细品

mark 一下,正好最近接手一个网关的平台,还没有具体看公司的方案,学习一下!

测试初手。。想问一下楼主 soapUI 作为自动化接口测试有什么优点和缺点吗? 为什么要抛弃这个工具而想到自己写一个框架呢?

@simonpatrick 楼主看到,请勾搭我一下下 49875183

@justcby SOAPUI 我也用过一段时间,他还是不错的。我也用过他做自动化的接口测试,我一个人负责功能和自动化,基本上也有 300+ 多个 case 的,20-30 多个接口,每个迭代两周,测试环境,集成环境都要测一遍,回归,新功能都要覆盖,基本上一个人也能搞定了,所以说效果还是不错的。我说说他的好处:

  • SOAPUI 如果用来做自动化测试的话,基本功能基本都可以完成,包括文件上传,上下文参数传递,环境配置,数据库验证,json 返回结果认证等等,甚至可以去出数据库的值来作为参数,而这些甚至不需要编程,不过你需要学习 SOAPUI 自己的一系列表达式的规则
  • SOAPUI 可以和 MAVEN 这些结合,但是我没有去研究如何做

不好的地方:

  • SOAPUI 的表达式学习你是要花点时间的
  • 复杂的数据初始化和验证的时候有点力不从心了

以上只是我个人的体会,其实用 SOAPUI 还是比较方便的,不过你至少要把他的官方文档中和你需要测试的东西的说明看一遍.

我自己觉得我为什么自己写代码的目的 (个人想法,如果有不同意见请跳过我说的) 是:

  • 做自动化测试目标不是让人不写代码,而是写代码,了解代码,去更深入的理解一些基础性的,原理性的东西
  • 自己工具更灵活一点,可以更方便的实现一些自己想要的东西,同时又比 SOAPUI 更轻量,SOAPUI 可以测一堆东西,不过我只要测 RESTFUL 类型的 API
陈恒捷 [该话题已被删除] 中提及了此贴 08月21日 01:50

#20 楼 @simonpatrick 多谢楼主回答 还有一些疑惑 就是当项目的每个任务开发是前后台同一个人做 这个时候怎么介入到接口测试? 或许当开发完成后台时前台也做好了。。。

你好,我现在使用的是 java+excel 的方式,和你的区别应该就是数据请求没有做封装。但是现在发现一个问题,每个请求的 test 和数据提供都差不多,区别在于数据设置或者是参数值输入的时候不一样,我想问一下你的代码生怎么处理的吗,是使用模板生成还是使用生成文件或者其他的,谢谢。

陈恒捷 接口测试的一些感悟 中提及了此贴 12月13日 10:29

整体思路包括利用 postman 的想法都差不多,区别就在于我们还什么都没开始做。。。。请教作者测试数据库数据的初始化有什么好方案么

11楼 已删除
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册