工作专题JavaWeb测试

接口自动化测试一体式解决方案(集成:Java+Testng+Ma

2018-07-13  本文已影响457人  J先生有点儿屁

接口自动化测试一体式解决方案

前戏叨逼叨:
测试多年工作经验,很少有写文章、博客之类的东西。
其实我这人不爱去写博客之类的东西,更多的是靠脑子的总结。不是脑子好用,其实就一句话:懒!就是懒!!!
早在几年前就有记录的想法,但当时确实因为工作原因 加上懒,就借口了自己。
好了,如今就要奔三的人,该面对的往往都要面对。
于是觉得是该写点东西记录下,倒不是说要总结什么的;只是想让自己经历过的事情,通过记录的形式加强印象;与此同时,希望能够给像自己这样的小白,在遇到类似问题时能够带来想法。
所以,第一次写类似测试技术博客,献丑。看官海涵~
第一篇不谈理论,实践篇。
叨逼叨结束!。

樱木镇楼

无聊的背景

测试工作多年,一路以来一直都会伴随着服务端的测试。
那么首当其冲的肯定是接口测试, 而接口测试中首先联想到的是接口自动化测试。
注意:服务端测试 != 接口测试 != 接口自动化测试 。这个公式是不等!找机会再写篇文章详细聊聊...
在经历过多中不同的平台后,毅然而然的觉得通过码代码的用例是最靠谱的。别相信那些带UI降低编写接口自动化用例难度的自动化平台,原因不详!(不想详细解释,入坑后自会明白...)
好了,今天介绍一款接口自动化测试的一体式解决方案。(有点夸张,其实吧还真是一体式。)这是入坑后,个人认为在Java大背景服务开发下比较理想的解决方案。
最后,接口自动化测试的框架和平台形形色色,不用评论哪个好,哪个差。只有最合适项目团队的才是最好的。(废话...)

本文代码github:https://github.com/Jsir07/TestHub  

话不多说,先上Jenkins上自助运行用例,查看报告流程截图。也可结合持续集成自动触发测试服务。
Jenkins自助运行流程

一、方案介绍

①. 选型:Java + Maven + Testng + ExtentReports + Git + Retrofit2 + Jenkins

②. 功能介绍:

  1. 实现持续集成测试,自助式测试,一站式测试平台。
  2. 通过Retrofit2作为等常用接口定义与请求方法,使用方便简洁。同时可分离接口定义、实现请求、响应验证。
  3. 参数化驱动用例运行方式,目前使用本地配置文件;可扩展为造数据。
  4. 还有...自己体会...

二、环境安装与配置

(一)开发环境:

  1. JDK1.7 及以上
  2. IDEA 社区版(壕->pro)
  3. Maven 不限
  4. Git 不限
  5. Jenkins 不限

(二)部分环境安装细节:

1. JDK 安装请查阅。https://www.cnblogs.com/ottox/p/3313540.html
2. Maven 安装与配置。
3. IDEA 安装请查阅,下载社区版本即可。 https://www.jetbrains.com/idea/
4. Git 安装。

参考:https://git-scm.com/book/zh/v2/%E8%B5%B7%E6%AD%A5-%E5%AE%89%E8%A3%85-Git

5. Jenkins本地安装,只要安装不用创建任务,后面会有任务创建。

参考:https://www.cnblogs.com/c9999/p/6399367.html

若遇网站需要翻墙,具体下载安装请自行百度。

三、框架搭建

(一) 项目基础工程

搭工程,建立基本工程框架,采用maven结构框架工程。 话不多说,先搭建工程。方式:

1.1 IDEA 上,File ->New ->Project -> maven. 选maven后,不选任何模板,直接Next。
image.png
1.2 填写对应项目信息后,next。
image.png
1.3 继续填写 对应信息后,Finish。
image.png

(二)Maven pom.xml文件配置 与多环境切换

2.1 依赖配置

需要使用到的依赖有testng、extentreports、retrofit、fastjson、okhttp等..

 <dependencies>
    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.47</version>
    </dependency>
    
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>3.10.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/logging-interceptor  日志拦截器-->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>logging-interceptor</artifactId>
        <version>3.10.0</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.testng/testng -->
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.14.2</version>
        <!--<scope>test</scope>-->
        <!--作用范围,默认是test。验证部分被抽象,不仅test作用域需使用-->
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/com.aventstack/extentreports -->
    <dependency>
        <groupId>com.aventstack</groupId>
        <artifactId>extentreports</artifactId>
        <version>3.1.5</version>
        <scope>provided</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.vimalselvam/testng-extentsreport -->
    <dependency>
        <groupId>com.vimalselvam</groupId>
        <artifactId>testng-extentsreport</artifactId>
        <version>1.3.1</version>
    </dependency>
    
    <dependency>
        <groupId>com.squareup.retrofit2</groupId>
        <artifactId>retrofit</artifactId>
        <version>2.4.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.squareup.retrofit2/converter-gson 对象转换器-->
    <dependency>
        <groupId>com.squareup.retrofit2</groupId>
        <artifactId>converter-gson</artifactId>
        <version>2.3.0</version>
    </dependency>
</dependencies>
2.2 配置多环境切换部分

采用maven环境切换方式。

2.3 配置环境切换-文件部分

如图:

image.png

(三) testNg配置部分

3.1. 在src/resources/下创建testNg目录.

src/resources/testNg 目录是存放测试集合的目录,可根据测试模块创建对应模块文件夹。

3.1.1 每个文件夹可以是独立模块,每个模块下可以有模块的测试集合。
3.1.2.为了能够让所有接口统一运行测试,需建立一个所有的测试集合,测试集合一般放在src/resources/testNg 目录下。

型如:

  <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

  <suite name="接口测试集合" verbose="1" preserve-order="true">
    <parameter name="report.config" value="src/main/resources/config/report/extent-config.xml"/>
    <parameter name="system.info" value="reporter.config.MySystemInfo"/>

    <suite-files>
      <suite-file path="search/SearchTags-TestSuite.xml"/>
    </suite-files>

    <listeners>
        <listener class-name="reporter.Listener.MyExtentTestNgFormatter"/>
    </listeners>
    </suite>
3.1.3 说明
testng层级 接口映射关系
test 接口具体的testcase
suite 具体接口所有用例集合
总suite 所有接口测试集合

需要说明的是,在testng接口层级管理上,每个项目可以有各自的方式,只要符合你的是好的。

    <parameter name="report.config" value="src/main/resources/config/report/extent-config.xml"/>
    <parameter name="system.info" value="reporter.config.MySystemInfo"/>
    
    <listeners>
        <listener class-name="reporter.Listener.MyExtentTestNgFormatter"/>
    </listeners>

(四) TestCase测试用例

4.1 Test用例类
4.2 测试用例怎么写?

其实网上有很多关于TestNg的文章,这边就不做过多介绍了。

官网:https://testng.org/doc/index.html
也可查阅:https://www.yiibai.com/testng/

写接口用例,不在与形式,形式网上很多。
最重要的是编写的层次与心得。回头有空再整理下。

(五) extentreports报告

extentreports是什么?网上有很多关于使用extentreports替代TestNg自带报告。原因是什么?
漂亮。先上张图。


官网很重要:http://extentreports.com/. 其实官网已经给了很多demo了,这里我根据自己的经验进行了配置。

testNg原有报告有点丑,信息整理有点乱。ExtentReports是用于替换testNg原有的报告。也可以使用ReportNg,个人偏好ExtentReports样式。

5.1 强制重写ExtentTestNgFormatter类

强制重写EExtentTestNgFormatter类主要是以下两点原因:
①、为了能够在报告中展示更多状态类型的测试结果,例如:成功、失败、警告、跳过等测试状态结果。
②、因为不支持cdn.rawgit.com访问,故替css访问方式。

方式如下:下载ExtentReportes源码,找到ExtentTestNgFormatter类。

5.2 导入MyExtentTestNgFormatter监听类

在测试集合.xml文件中导入Listener监听类。

<listeners>
        <listener class-name="reporter.Listener.MyExtentTestNgFormatter"/>
</listeners>
5.3 配置报告信息

extent reporters支持报告的配置。目前支持的配置内容有title、主题等。

5.4 添加系统信息

不多说,上图。


可用于添加系统信息,例如:db的配置信息,人员信息,环境信息等。根据项目实际情况添加。

(六). retrofit2.0--Http接口测试驱动原力

其实Java的Http客户端有很多,例如HTTPClient、OKHttp、retrofit等。。。
为什么那么多Http客户端会选择retrofit?用一个图见证他的实力

如此多的星星可知

真正的原因
接口定义与实现分离
retrofit2.0可将Http接口定义与请求实现分离;通过制定interface定义接口。
网上有很多关于retrofit2.0的教程,这里就不再班门弄斧了,度娘即可。参考:https://blog.csdn.net/carson_ho/article/details/73732076

附上本项目方式。

6.1 具体Http Api的定义interface。新建ISearch interface。
public interface ISearch {
    @GET("j/search_tags")
    Call<MovieResponseVO> searchTags(@Query("type") String type, @Query("source") String source);
}
6.2 HttpBase基础类提供原动力。

HttpBase类中提供了Retrofit基础。
同时,我考虑到了日常控制台和测试报告上都需要看到对应请求信息,故此在HttpClient中默认加入了日志拦截器;日志拦截器的实现方法里,用Reportes.log记录到日志中。
并且,考虑到实际项目中每个Http请求都会有对应类似RequestHeader、RequestBody的加密签名等,预留了拦截器。
可在HttpBase构造方法时传入对应拦截器。
对应的拦截器可以通过实现接口Interceptor,做对应项目需求操作。
先看代码。

public class HttpBase {
    public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    private Retrofit retrofit;
    private String host;

    /**
     * 构造方法(1个参数)
     * 只传Host,默认没有使用拦截器。
     *
     * @param host 访问域名host
     */
    public HttpBase(String host) {
        init(host, null);
    }

    /**
     * 构造方法(2个参数)
     * 只传Host,默认使用日志拦截器。
     *
     * @param host        访问域名host
     * @param interceptor 自定义拦截器
     */
    public HttpBase(String host, Interceptor interceptor) {
        init(host, interceptor);
    }

    /**
     * 初始化方法
     *
     * @param host        访问域名host
     * @param interceptor 自定义拦截器
     */
    private void init(String host, Interceptor interceptor) {
        OkHttpClient.Builder client = getHttpClient(interceptor);
        retrofit = new Retrofit.Builder()
                .baseUrl(host)
                .client(client.build())
                .addConverterFactory(RespVoConverterFactory.create())
                .build();
    }

    /**
     * 获取HttpClient.Builder 方法。
     * 默认添加了,基础日志拦截器
     *
     * @param interceptor 拦截器
     * @return HttpClient.Builder对象
     */
    private OkHttpClient.Builder getHttpClient(Interceptor interceptor) {
        HttpLoggingInterceptor logging = getHttpLoggingInterceptor();
        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true);
        if (interceptor != null) {
            builder.addInterceptor(interceptor);
        }
        builder.addInterceptor(logging);
        return builder;
    }

    /**
     * 日志拦截器
     *
     * @return
     */
    private HttpLoggingInterceptor getHttpLoggingInterceptor() {
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                Reporter.log("RetrofitLog--> " + message, true);
            }
        });
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);//Level中还有其他等级. 设置打印内容级别到Body。
        return logging;
    }

    /**
     * retrofit构建方法
     *
     * @param clazz 泛型类
     * @param <T>   泛型类
     * @return 泛型类
     */
    public <T> T create(Class<T> clazz) {
        return retrofit.create(clazz);
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }
}
6.3 集成HttpBase的Http Api接口请求方法类

这里需要说明下,为什么需要有这个类的存在?
其实在Retrofit已经可以用4行的代码实现Http请求了,如下:

        HttpBase httpBase = new HttpBase(host);
        ISearch iSearch = httpBase.create(ISearch.class);
        Call<MovieResponseVO> call = iSearch.searchTags(type, source);
        Response<MovieResponseVO> response = call.execute();

看了上面的4行代码,每次都需要写也是挺麻烦的。
所以抽出来,让编写测试用例验证更简洁点。
抽取后的代码如下:

public class HttpSearch extends HttpBase {
    private ISearch iSearch;

    public HttpSearch(String host) {
        super(host);
        iSearch = super.create(ISearch.class);
    }

    public Response<MovieResponseVO> searchTags(String type, String source) throws IOException {
        Call<MovieResponseVO> call = iSearch.searchTags(type, source);
        return call.execute();
    }

//    同模块下,新增的接口可添加到这里。
//    public Response<MovieResponseVO> searchTags(String type, String source) throws IOException {
//        Call<MovieResponseVO> call = iSearch.searchTags(type, source);
//        return call.execute();
//    }
}
6.4 使用JsonSchema验证基础响应体

Http响应体非Json格式,可跳过该步骤。

这里引入了JsonSchema来做基础验证,减少了Http响应返回带来的大量对象基础验证。
方式如下:

      <!--json schema start-->
      <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-core</artifactId>
          <version>2.9.6</version>
      </dependency>

      <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.9.6</version>
      </dependency>

      <dependency>
          <groupId>com.github.fge</groupId>
          <artifactId>json-schema-validator</artifactId>
          <version>2.2.6</version>
      </dependency>
      <!--json schema end-->
/**
* JsonSchema工具类
*/
public class JsonSchemaUtils {
  /**
   * 从指定路径读取Schema信息
   *
   * @param filePath Schema路径
   * @return JsonNode型Schema
   * @throws IOException 抛出IO异常
   */
  private static JsonNode readJSONfile(String filePath) throws IOException {
      InputStream stream = JsonSchemaUtils.class.getClassLoader().getResourceAsStream(filePath);
      return new JsonNodeReader().fromInputStream(stream);
  }

  /**
   * 将Json的String型转JsonNode类型
   *
   * @param str 需要转换的Json String对象
   * @return 转换JsonNode对象
   * @throws IOException 抛出IO异常
   */
  private static JsonNode readJSONStr(String str) throws IOException {
      return new ObjectMapper().readTree(str);
  }

  /**
   * 将需要验证的JsonNode 与 JsonSchema标准对象 进行比较
   *
   * @param schema schema标准对象
   * @param data   需要比对的Schema对象
   */
  private static void assertJsonSchema(JsonNode schema, JsonNode data) {
      ProcessingReport report = JsonSchemaFactory.byDefault().getValidator().validateUnchecked(schema, data);
      if (!report.isSuccess()) {
          for (ProcessingMessage aReport : report) {
              Reporter.log(aReport.getMessage(), true);
          }
      }
      Assert.assertTrue(report.isSuccess());
  }

  /**
   * 将需要验证的response 与 JsonSchema标准对象 进行比较
   *
   * @param schemaPath JsonSchema标准的路径
   * @param response   需要验证的response
   * @throws IOException 抛出IO异常
   */
  public static void assertResponseJsonSchema(String schemaPath, String response) throws IOException {
      JsonNode jsonSchema = readJSONfile(schemaPath);
      JsonNode responseJN = readJSONStr(response);
      assertJsonSchema(jsonSchema, responseJN);
  }
}

这里已经将最后抽成简单方法供使用,只需传入schemaPath路劲、以及需要验证的对象。

  {
  "$id": "http://example.com/example.json",
  "type": "object",
  "properties": {
    "tags": {
      "$id": "/properties/tags",
      "type": "array",
      "items": {
        "$id": "/properties/tags/items",
        "type": "string",
        "title": "The 0th Schema ",
        "default": "",
        "examples": [
          "热门",
          "最新"
        ]
      }
    }
  },
  "required": [
    "tags" 
  ]
}
6.5 TestCase测试用例编写。
public class SearchTagsTest {
    private static Properties properties;
    private static HttpSearch implSearch;
    private static String SCHEMA_PATH = "parameters/search/schema/SearchTagsMovie.json";

    @BeforeSuite
    public void beforeSuite() throws IOException {
        InputStream stream = this.getClass().getClassLoader().getResourceAsStream("env.properties");
        properties = new Properties();
        properties.load(stream);
        String host = properties.getProperty("douban.host");
        implSearch = new HttpSearch(host);
        stream = this.getClass().getClassLoader().getResourceAsStream("parameters/search/SearchTagsParams.properties");
        properties.load(stream);
        stream = this.getClass().getClassLoader().getResourceAsStream("");
        stream.close();
    }

    @Test
    public void testcase1() throws IOException {
        String type = properties.getProperty("testcase1.req.type");
        String source = properties.getProperty("testcase1.req.source");
        Response<MovieResponseVO> response = implSearch.searchTags(type, source);
        MovieResponseVO body = response.body();
        Assert.assertNotNull(body, "response.body()");
//        响应返回内容想通过schema标准校验
        JsonSchemaUtils.assertResponseJsonSchema(SCHEMA_PATH, JSONObject.toJSONString(body));
//        再Json化成对象
        Assert.assertNotNull(body.getTags(), "tags");
    }

    @Test
    public void testcase2() throws IOException {
        String type = properties.getProperty("testcase2.req.type");
        String source = properties.getProperty("testcase2.req.source");
        Response<MovieResponseVO> response = implSearch.searchTags(type, source);
        MovieResponseVO body = response.body();
        Assert.assertNotNull(body, "response.body()");
        JsonSchemaUtils.assertResponseJsonSchema(SCHEMA_PATH, JSONObject.toJSONString(body));
        Assert.assertNotNull(body.getTags(), "tags");
    }
}

至此,TestNg测试用例部分全部完成。

四、Jenkins部分配置

Jenkins的安装上面已有说明,这里不重复。

(一) Jenkins插件

1.插件列表

需要使用到的插件有:

2. Jenkins插件安装

怎么安装插件?
Jenkins-》系统管理-》插件管理-》搜索插件-》安装即可

3. 插件说明

(二)Jenkins新建任务配置

在插件安装完后,开始任务的新建配置。

(三)General配置

(四) 构建配置-maven配置

在Jenkins使用Maven构建项目自动化测试前,先通过本地使用maven测试是否通过。
这里本来要将参数化构建,但参数化构建前先说明下是如何利用maven构建测试的。

image.png

(五) 配置构建-maven配置信息。

回到Jenkins界面配置maven信息。

  • maven参数化替换使用的占位符是 %xxx%
  • -P%env% 指定maven运行的环境,该环境信息与pom.xml 配置的信息一直。同时,-P%env% 用于参数化构建传参使用,后面会有介绍。
  • -DxmlFileName=%xmlFileName% 指定maven test 运行的测试集合对象。用于参数化构建传参使用,后面介绍。

(六) 参数化构建过程 配置

(七) 源码管理配置

这个配置网上有很多详细文档,这里不重复。具体度娘查看。


image.png

(八) 构建触发器

> 这个配置可根据实际项目需求配置。个人建议: 接口自动化测试中的自动化最核心的是结合持续构建。   
> 所以建议配置“其他工程构建后触发”,填入所需测试的服务端项目名称即可。当然要在一个Jenkins中。
image.png

(九) 构建信息配置

> 上面已经配置了“调用顶层Maven目标”,然后还需要配置Groovy script。  
> 配置Groovy script的目的是让Http Reported插件css能用,同时不用担心jenkins重启。

(十) 构建后操作信息配置

9.1. publish html reports
加入publish html reports步骤。
- HTML directory to archive: 报告路径。 填写extentreports默认输出路径:test-output\
- Index page[s] : 报告索引名称。填写extentreports默认报告名称:report.html
- Keep past HTML reports: 保留报告,勾选!不多说。
9.2 publish html reports
publish testng results 配置。默认**/testng-results.xml 即可。 
为什么要testng默认报告? 因为需要统计分析时查看。 当然这个是可选的。
image.png
9.3. 钉钉通知器配置
怎么玩转钉钉消息?查看https://blog.csdn.net/workdsz/article/details/77531802
- 填入access token。
image.png
4. 构建后操作信息配置 钉钉通知器配置 二次开发 - 可选
http://www.51testing.com/html/25/n-3723525.html

(十一) 构建测试

五、工程目录讲解 与 接口测试用例编写步骤

(一) 工程目录讲解

 先上图说明

(二) 接口测试用例编写步骤

1. 目标:具体Htpp接口定义的熟悉、理解。

注意:Http 请求行、请求头、请求体;响应行、响应头、响应体

2. 具体Htpp接口的定义 - Host
3. 具体Htpp接口的定义 - interface
4. 具体Htpp接口的定义实现 - implement
5. 编写测试用例 -xxxTest
6. 具体接口测试用例suite集合制作
7. 所有接口测试用例suite集合制作

写在最后

其实,接口自动化测试平台的搞起来不难。
推动平台接入到持续集成,将测试变成一种服务,更快更及时的服务于项目,才是重点。
正所谓:wiki一定,开发未动,接口已行。
而,服务端测试才挑战。知识储备的深度决定了,测试的深度。

个人GitHub: https://github.com/Jsir07/TestHub
欢迎Watch + Fork
end...

上一篇 下一篇

猜你喜欢

热点阅读