Junit5+extentreports生成测试报告

1、依赖导入

网上大部分文章是讲Testng+extentreports生成测试报告的,extentreports官网给出的案例也是使用Testng的案例,所以整理下来自己的使用心得,同时s使用的是最新的Junit5

本来主要是讲Junit5+extentreports的整合,不会讲解太多的Junit5标签,如果要学习Junit5的新特性,可以参考这位博主的入门文章

在 Maven 工程里引入 JUnit 5 的依赖包,需注意的是当前JDK 环境要在 Java 8 以上。

<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter-engine</artifactId>
  <version>5.5.2</version>
  <scope>test</scope>
</dependency>

如何你系统使用的是Spring boot项目那么就导入Spring boot的启动包即可。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
  <!--排除旧的包-->
    <exclusions>
      <exclusion>
         <groupId>org.junit.vintage</groupId>
         <artifactId>junit-vintage-engine</artifactId>
         </exclusion>
       </exclusions>
</dependency>

extentreports工具包

<dependency>
  <groupId>com.relevantcodes</groupId>
  <artifactId>extentreports</artifactId>
  <version>2.41.2</version>
</dependency>

2、建立项目

2.1 ExampleController

package com.service.modules.controller;

import com.service.common.annotation.JsonFieldFilter;
import com.service.common.annotation.OperationLog;
import com.service.common.base.controller.BaseController;
import com.service.common.exception.BusinessException;
import com.service.common.page.Query;
import com.service.modules.biz.ExampleBiz;
import com.service.modules.entity.Example;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.Map;

/**
 * 项目开发样例,开发请参考改样例进行开发。
 *
 * @author lyl
 * @version 2020/10/19 0019 15:43:02
 */
@RestController
@RequestMapping("example")
@Api(tags = "ExampleController", description = "工程样例")
@Slf4j
public class ExampleController extends BaseController<ExampleBiz, Example, Long> {

    @RequestMapping(value = "", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "新增单个对象", notes = "系统自带默认方法")
    public ResponseEntity add(@Valid @RequestBody Example entity) {
        entity.setCreateTime(DateTime.now().toDate());
        entity.setUpdateTime(DateTime.now().toDate());
        baseBiz.insertSelective(entity);
        return ResponseEntity.ok(entity);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "查询单个对象", notes = "系统自带默认方法")
    @JsonFieldFilter(type = Example.class, exclude = "id")
    @OperationLog(description = "查询单个对象", businessModule = "工程样例")
    public ResponseEntity get(@PathVariable Long id) {

        // 异常处理参下面的处理方式
        if (id == 1) {
            try {
                int i = 1 / 0;
            } catch (Exception e) {
                throw new BusinessException("发生错误,这是模拟发生的一个错误。");
            }
        }
        return ResponseEntity.ok(baseBiz.selectById(id));
    }

    @RequestMapping(value = "", method = RequestMethod.PUT)
    @ResponseBody
    @ApiOperation(value = "更新单个对象", notes = "系统自带默认方法。不需要修改的字段可以为不传,将保持原来的值。")
    public ResponseEntity update(@Valid @RequestBody Example entity) {
        baseBiz.updateSelectiveById(entity);
        return ResponseEntity.ok(entity);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    @ResponseBody
    @ApiOperation(value = "移除单个对象", notes = "系统自带默认方法")
    public ResponseEntity remove(@PathVariable Long id) {
        return ResponseEntity.ok(baseBiz.deleteById(id));
    }

    @RequestMapping(value = "/all", method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "获取所有数据", notes = "系统自带默认方法")
    public ResponseEntity all() {
        return ResponseEntity.ok(baseBiz.selectListAll());
    }

    @ApiOperation(value = "分页获取数据", notes = "系统自带默认方法")
    @RequestMapping(value = "/page", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity list(@RequestParam Map<String, Object> params) {
        Query query = new Query(params);
        return ResponseEntity.ok(baseBiz.selectByQuery(query));
    }
}

比较懒,直接拷自己项目的数据了,以上代码并不能在你的程序中运行,Controller方法请自行识别。

2.2 InitHttpCase

InitHttpCase作为所有测试类的父类,用来减少通用代码的编写,也是因为比较懒。

package base;

import com.google.common.collect.Maps;
import com.relevantcodes.extentreports.ExtentReports;
import com.relevantcodes.extentreports.NetworkMode;
import com.relevantcodes.extentreports.ReporterType;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;

/**
 * @author lyl
 * @version 2020/11/14 0014 02:29:30
 */

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Slf4j
public class InitHttpCase {
    //测试报告输出目录
    private static final String REPORTS_LOCATION = "test-reports\\index.html";
    //测试报告对象
    private static ExtentReports extent;
    //主机地址
    protected static String host;

    @LocalServerPort
    protected int port;
    @Autowired
    protected TestRestTemplate restTemplate;
    //注册扩展模型,用于捕获测试状况
    @RegisterExtension
    public DefaultTestWatcher testWatcher = new DefaultTestWatcher(extent);

    @BeforeAll
    static void initAll() {
        try {
            InetAddress inetAddress = InetAddress.getLocalHost();
            host = inetAddress.getHostAddress();
            Map<String, String> info = Maps.newHashMap();
            info.put("Host Address", host);
            info.put("Host Name", inetAddress.getHostName());
            info.put("User Name", System.getenv().get("USERNAME"));
            extent = new ExtentReports(REPORTS_LOCATION, true, NetworkMode.OFFLINE);
            //生成数据库
            extent.startReporter(ReporterType.DB, REPORTS_LOCATION);
            //系统信息
            extent.addSystemInfo(info);
        } catch (UnknownHostException e) {
            log.error("测试初始化异常", e);
        }
    }

    @BeforeEach
    public void initEach() {
        InitHttpCase.host = String.format("localhost:%s", port);
    }

    @AfterAll
    public static void afterAll() {
        extent.close();
    }

    //使用postForObject请求接口
    public static String defaultPostForObject(RestTemplate template, MultiValueMap<String, Object> paramMap, String url) {
        return template.postForObject(url, paramMap, String.class);
    }

    //使用postForEntity请求接口
    public static String defaultPostForEntity(RestTemplate template, MultiValueMap<String, Object> paramMap, String url) {
        HttpHeaders headers = new HttpHeaders();
        return defaultExchange(template, paramMap, url, headers);
    }

    public static String defaultPostForEntity(RestTemplate template, MultiValueMap<String, Object> paramMap, String url, HttpHeaders headers) {
        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(paramMap, headers);
        ResponseEntity<String> response2 = template.postForEntity(url, httpEntity, String.class);
        return response2.getBody();
    }

    //使用exchange请求接口,它可以指定请求的HTTP类型
    public static String defaultExchange(RestTemplate template, MultiValueMap<String, Object> paramMap, String url) {
        HttpHeaders headers = new HttpHeaders();
        return defaultExchange(template, paramMap, url, headers);
    }

    public static String defaultExchange(RestTemplate template, MultiValueMap<String, Object> paramMap, String url, HttpHeaders headers) {
        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(paramMap, headers);
        ResponseEntity<String> response3 = template.exchange(url, HttpMethod.POST, httpEntity, String.class);
        return response3.getBody();
    }
}

这里要提下@RegisterExtension标签,这个标签是用来注册扩展模型的,使用的编程的方式,能够被子类继承的。用于注册扩展模型的还有@@ExtendWith(xxxx.class),这个注解是用在类上的,而且是不能被继承的。junit5之前用来定义扩展模型是@Rule,但在junit5之后就不复存在了。

2.3 DefaultTestWatcher

定义扩展模型,继承了TestWatcher,同时重写里面的方法,用来捕获日志断言执行情况。

package base;

import com.relevantcodes.extentreports.ExtentReports;
import com.relevantcodes.extentreports.ExtentTest;
import com.relevantcodes.extentreports.LogStatus;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestWatcher;

/**
 * @author lyl
 * @version 2020/11/15 0015 23:48:28
 */
public class DefaultTestWatcher implements TestWatcher {

    private ExtentReports extent;

    public DefaultTestWatcher(ExtentReports extent) {
        this.extent = extent;
    }

    @Override
    public void testSuccessful(ExtensionContext context) {
        ExtentTest test = extent.startTest(context.getDisplayName(), "Test Success");
        // step log
        test.log(LogStatus.PASS, "success");
        flushReports(extent, test);
    }

    @Override
    public void testFailed(ExtensionContext context, Throwable e) {
        ExtentTest test = extent.startTest(context.getDisplayName(), "Test Failed");
        // step log
        test.log(LogStatus.FAIL, e);
        flushReports(extent, test);
    }

    private void flushReports(ExtentReports extent, ExtentTest test) {
        extent.endTest(test);
        extent.flush();
    }
}

2.4 ExampleControllerTest

测试方法

package com.service.modules.controller;


import base.InitHttpCase;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.owasp.encoder.Encode;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

/**
 * ExampleController Tester.
 *
 * @author <lyl>
 * @version 1.0
 * @since <pre>11/14/2020</pre>
 */
@Slf4j
public class ExampleControllerTest extends InitHttpCase {

    /**
     * Method: add(@Valid @RequestBody Example entity)
     */
    @Test
    public void testAdd() {
        // 封装参数,千万不要替换为Map与HashMap,否则参数无法传递
        MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
    }

    /**
     * Method: get(@PathVariable Long id)
     */
    @Test
    @DisplayName("[testGet]正常返回")
    public void testGet() {
        String url = String.format("http://%s/example/{id}", host);
        ResponseEntity<String> responseEntity = this.restTemplate.getForEntity(url, String.class, 1);
        Assertions.assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
    }

    /**
     * Method: update(@Valid @RequestBody Example entity)
     */
    @Test
    public void testUpdate() {
    }

    /**
     * Method: remove(@PathVariable Long id)
     */
    @Test
    public void testRemove() {
    }

    /**
     * Method: all()
     */
    @Test
    @DisplayName("[testAll]正常返回")
    public void testAll() {
        String url = String.format("http://%s/example/all", host);
        ResponseEntity<String> responseEntity = this.restTemplate.getForEntity(url, String.class);
        Assertions.assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
    }

    /**
     * Method: list(@RequestParam Map<String, Object> params)
     */
    @Test
    public void testList() {
        String s1 = "keep-alive";
        String s2 = "text/html";
        Assertions.assertEquals(Encode.forJavaScriptSource(s1), s1);
        Assertions.assertEquals(Encode.forJavaScriptSource(s2), s2);
    }
} 

好了,这样整个测试项目就建立起来了。就这起来跑起来看生成测试报告了。

3、测试报告

3.1 运行情况

3.2 测试报告(1)

3.3 测试报告(2)

3.4 测试报告(3)

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值