美文网首页架构开发技巧Java web
junit-generator Junit 单元测试生成工具Ma

junit-generator Junit 单元测试生成工具Ma

作者: javacoo | 来源:发表于2021-01-11 17:30 被阅读0次

    junit-generator

    介绍

    一个基于JUnit,Freemarker,Mockito,Maven等技术实现的单元测试类脚手架生成工具Maven插件。

    需求

    我们在测试驱动开发过程中,总会写一大堆与业务无关的模板式的代码,为了减少开发者写单元测试的工作量,需要一个单元测试类脚手架代码的生成工具。

    类关系图

    类关系图

    主要技术说明

    1. Maven插件开发:见官网:http://maven.apache.org/guides/plugin/guide-java-report-plugin-development.html

    2. XML-DTD 约束文件定义:DTD 的目的是定义 XML 文档的结构,它使用一系列合法的元素来定义文档结构:详解见:

      https://www.cnblogs.com/mengdd/archive/2013/05/30/3107361.html

    3. FreeMarker模板引擎:中文官方参考手册:http://freemarker.foofun.cn/

    4. spi插件机制:见:https://gitee.com/javacoo/xkernel

    安装教程

    1. 配置pom

      在测试工程的pom.xml文件中添加如下配置:

      <build>
              <plugins>
                  <plugin>
                      <groupId>com.javacoo</groupId>
                      <artifactId>junit-generator-maven-plugin</artifactId>
                      <version>1.1.0-SNAPSHOT</version>
                      <configuration>
                         <!-- 是否覆盖 -->
                          <overwrite>false</overwrite>
                          <!-- 是否备份-->
                          <backup>true</backup>
                          <!-- 配置文件路径 -->
                          <configurationFile>src/test/resources/junitGeneratorConfig.xml</configurationFile>
                          <!-- 需要执行的上下文ID,多个逗号分隔 -->
                          <contexts>testContext,springTestContext</contexts>
                      </configuration>
                  </plugin>
              </plugins>
          </build>
      
    2. 添加配置文件:junitGeneratorConfig.xml

      在项目resources目录下添加junitGeneratorConfig.xml配置文件:如

      <?xml version="1.0" encoding="UTF-8"?>        
      <!DOCTYPE generatorConfiguration PUBLIC "-//javacoo.com//DTD Junit Generator Configuration 1.0//EN" "http://javacoo.com/dtd/junit-generator-config_1_0.dtd" >
      
      <!--junit生成配置-->
      <generatorConfiguration>
       <context id="TestContext">
           <!--junit 模板配置-->
           <template templatePath="/template" templateName="test.ftl" templateHandlerName="spring"></template>
           <!--junit 生成目标类集合-->
           <classList>
               <class className="com.javacoo.junit.generator.api.TestApi"/>
                  </classList>
       </context>
           <context id="springTestContext">
              <!--junit 模板配置-->
              <template templatePath="/template" templateName="test.ftl" templateHandlerName="spring"></template>
              <!--junit 生成目标类集合-->
              <classList>
                  <class className="com.javacoo.junit.generator.api.SpringTestApi"/>
              </classList>
          </context>
          <context id="junit5DefaultContext">
              <!--junit 模板配置-->
              <template templateHandlerName="defaultJUnit5"></template>
              <!--junit 生成目标类集合-->
              <classList>
                  <class className="com.javacoo.junit.generator.api.Junit5TestApi"/>
              </classList>
          </context>
      </generatorConfiguration>
      
    3. 生成测试代码:

      在IDE工具栏查看安装好插件,点击运行,如:

    输入图片说明

    或者执行命令:mvn com.javacoo:junit-generator-maven-plugin:1.1.0-SNAPSHOT:generate

    1. 生成结果:默认在测试工程 src/test/java 目录生成测试类包名文件夹及测试类,如:
    输入图片说明

    使用说明

    1. pom.xml 配置说明

      junit-generator-maven-plugin按照标准Maven插件配置即可。

      参数说明:

      ​ skip:是否跳过生成>非必填,是指是否跳过生成测试类文件,默认为false,不跳过,即生成。

      ​ overwrite:是否覆盖->非必填,是指是否覆盖已有的测试类文件,默认为false,不覆盖,即合并。

      ​ backup:是否备份->非必填,是指生成测试类前是否备份已有文件,默认为false,不备份(overwrite 为 false 时生效)。
      ​ contexts:需要执行的上下文节点,多个以逗号分隔->非必填,junitGeneratorConfig.xml中context节点id

      ​ configurationFile:配置文件路径->必填,相对测试项目根目录

    2. junitGeneratorConfig.xml配置说明

      第一行为标准XML文件定义:

      <?xml version="1.0" encoding="UTF-8"?> 
      

      第二行为junit-generator-maven-plugin特有DTD文件约束说明:

      <!DOCTYPE generatorConfiguration PUBLIC "-//javacoo.com//DTD Junit Generator Configuration 1.0//EN" "http://javacoo.com/dtd/junit-generator-config_1_0.dtd" >
      

      节点说明:

       <!-- generatorConfiguration:配置根节点,必须,整个配置文件唯一,定义生成单元测试相关配置 -->
      <generatorConfiguration>    
           <!-- context:配置上下文节点,必须,可多个,定义生成所需要的模板信息和目标类信息。
                属性说明:
                 id,配置上下文的唯一标识,必须,用于在插件配置中指定要执行的上下文节点 
           -->
       <context id="TestContext">   
           <!--template: 模板配置信息,非必须,context唯一,默认使用插件自带模板
                  属性说明:
                  templatePath:模板路径信息,非必填,相对测试工程目录的模板路径信息,如果填写,则在指定的模板路径查找模板。
                  templateName:模板名称,非必填,生成单元测试的模板文件名称。
                  templateHandlerName:模板处理器名称,非必填,默认采用插件自带处理器:default
                      插件自带处理器说明:
                          default:基于JUnit4的默认的处理器,用于生成普通类(非Spring项目)的单元测试。
                          spring:基于JUnit4的用于生成Spring工程,相关接口的单元测试。
                          defaultJUnit5:基于JUnit5的默认的处理器,用于生成普通类(非Spring项目)的单元测试。
                          springJUnit5:基于JUnit5的用于生成Spring工程,相关接口的单元测试。
                          mock:基于mockito生成相关接口的单元测试。
               -->
              <template templatePath="/template" templateName="test.ftl" templateHandlerName="spring"></template>
           <!--classList: 目标类集合节点,必须,context唯一,定义了需要生成单元测试的目标类信息 -->
           <classList>
                  <!--class: 目标类定义,必须,定义了需要生成单元测试的目标类信息 
                      属性说明:
                      className:类名称,必须,目标全类名。
                  -->
               <class className="com.javacoo.junit.generator.api.TestApi"/>
           </classList>
       </context>
      </generatorConfiguration>
      
    3. 插件自带模板处理器生成说明:

      • 基于JUnit4->default:基于JUnit4的默认的处理器,生成普通类(非Spring项目)的单元测试,只生成了类或者接口的公共方法的单元测试,如:
       @Test
          public void testAddAndGet(){
              //TODO: 检查生成的测试代码, 修改给定的方法调用参数 并 断言子句
              //准备参数并 调用测试方法
              long l = 0L;
              AtomicLong atomicLong = new AtomicLong(l);
              long l1 = 0L;
      
              long actualResult = atomicLong.addAndGet(l1);
              assertEquals("addAndGet方法", 0L, actualResult);
      
          }
      
      • 基于JUnit4->spring:基于JUnit4的用于生成Spring工程,相关接口的单元测试,只生成了类或者接口的公共方法的单元测试,如:
      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration(locations = {
              "classpath*:/spring/spring-mvc.xml"
      })
      public class SpringTestApiTest {
          @Autowired
          private SpringTestApi springTestApi;
      
          @BeforeClass
          public static void setUpClass(){
             //执行所有测试前的操作
      
          }
          @AfterClass
          public static void tearDownClass(){
             //执行完所有测试后的操作
      
          }
          @Before
          public void setUp(){
             //每次测试前的操作
      
          }
          @After
          public void tearDown(){
             //每次测试后的操作
          }
      
          @Test
          public void testMyTest3(){
              //TODO: 检查生成的测试代码, 修改给定的方法调用参数和断言子句
              //准备参数并 调用测试方法
              String str = "hello";
              String channelNo = "hello";
              StoreAreaRequest storeAreaRequest = new StoreAreaRequest(channelNo);
              List<com.javacoo.junit.generator.model.StoreAreaRequest> storeAreaRequests = new ArrayList<>();
              storeAreaRequests.add(storeAreaRequest);
              String str1 = "hello";
              String channelNo1 = "hello";
              StoreAreaRequest storeAreaRequest1 = new StoreAreaRequest(channelNo1);
              Map<java.lang.String, com.javacoo.junit.generator.model.StoreAreaRequest> storeAreaRequestMap = new HashMap<>();
              storeAreaRequestMap.put(str1, storeAreaRequest1);
              String isFaceCheck = "hello";
              BigDecimal approveAmt = BigDecimal.ZERO;
              FundLoanApproveRequest fundLoanApproveRequest = new FundLoanApproveRequest(approveAmt);
              FundLoanApproveDetailRequest fundLoanApproveDetailRequest = new FundLoanApproveDetailRequest(storeAreaRequests, storeAreaRequestMap, isFaceCheck, fundLoanApproveRequest);
              Map<java.lang.String, com.javacoo.junit.generator.model.FundLoanApproveDetailRequest> map = new HashMap<>();
              map.put(str, fundLoanApproveDetailRequest);
      
              ApprovePreQueryResponse actualResult = springTestApi.myTest3(map);
              assertNotNull(actualResult);
      
          }
          ...
      }
      

    最佳实践

    1. 关闭覆盖功能,如:<overwrite>false</overwrite>
    2. 开启备份功能,如:<backup>true</backup>
    3. 配置需要执行的上下文ID,如:<contexts>testContext,springTestContext</contexts>
    4. 在junitGeneratorConfig.xml中定义自己的模块的执行的上下文ID,与其他开发人员隔离
      ...

    插件开发

    1. 开发步骤

      1. 实现接口:com.javacoo.junit.generator.api.TemplatePlugin,接口定义如下:
       package com.javacoo.junit.generator.api.plugin;
       
       import java.util.Map;
       
       import com.javacoo.xkernel.spi.Spi;
       
       /**
        * 模板插件
        * <li>此插件目的是为自定义模板生成规则提供入口,程序会根据插件提供的模板数据渲染指定路径下,指定模板名称的模板,并输出到指定目录</li>
        * <li>插件机制基于Java SPI机制的扩展,原理及开发步骤见:https://gitee.com/javacoo/xkernel</li>
        * <li>注意:目前只支持Freemarker模板引擎,开发手册见:http://freemarker.foofun.cn/</li>
        * @author: duanyong@jccfc.com
        * @since: 2021/1/4 10:07
        */
       @Spi("default")
       public interface TemplatePlugin {
           /**
            * 根据类对象获取模板数据
            * <li>此数据用于填充模板</li>
            * @author duanyong@jccfc.com
            * @date 2021/1/4 11:09
            * @param sourceClass:类对象
            * @return: java.util.Map<java.lang.String,java.lang.Object>
            */
           Map<String, Object> getTemplateData(Class sourceClass);
           /**
            * 根据类对象获取输出文件路径
            * <li>指定测试类文件生成的路径</li>
            * @author duanyong@jccfc.com
            * @date 2021/1/4 11:51
            * @param sourceClass: 类对象
            * @param outputFilePath: 输出路径
            * @return: java.lang.String
            */
           String getOutFile(Class sourceClass,String outputFilePath);
           /**
            * 获取模板路径
            * <li>外部模板所在路径</li>
            * @author duanyong@jccfc.com
            * @date 2021/1/8 10:57
            * @return: java.lang.String
            */
           String getTemplatePath();
           /**
            * 获取模板名称
            * <li>模板名称,带后缀</li>
            * @author duanyong@jccfc.com
            * @date 2021/1/5 11:41
            * @return: java.lang.String 模板名称
            */
           String getTemplateName();
       
       }
       
      

      基于JUnit默认实现类代码片段如下:

      AbstractTemplatePlugin

      package com.javacoo.junit.generator.internal.plugin.junit4;
      
      ...
      /**
       * 模板插件接口抽象实现类
       * <li>定义了插件所需公共方法及流程</li>
       *
       * @author: duanyong@jccfc.com
       * @since: 2021/1/5 14:27
       */
      public abstract class AbstractTemplatePlugin implements TemplatePlugin {
          /**默认模板路径*/
          protected static final String BASE_TEMPLATE_PACKAGE = "/templates/";
          /**日期格式*/
          private final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
          /**返回变量名*/
          private final String RESULT_VAL_NAME = "actualResult";
          /**
           * 根据类对象获取输出文件路径
           * <li></li>
           *
           * @param sourceClass : 类对象
           * @param outputFilePath: 输出路径
           * @author duanyong@jccfc.com
           * @date 2021/1/4 11:51
           * @return: java.lang.String
           */
          @Override
          public String getOutFile(Class sourceClass,String outputFilePath) {
              Package sourcePackage = sourceClass.getPackage();
      
              //包路径
              StringBuilder packagePath = new StringBuilder().append(outputFilePath).append("/").append(sourcePackage.getName().replace(".","/")).append("/");
              //生成文件夹
              File filePath = new File(packagePath.toString());
              if (!filePath.exists()){
                  filePath.mkdirs();
              }
      
              //文件名称
              String fileName = new StringBuilder().append(sourceClass.getSimpleName().substring(0, 1).toUpperCase()).append(sourceClass.getSimpleName().substring(1)).toString();
              //输出文件路径
              StringBuilder outFile = packagePath.append(fileName).append("Test.java");
      
              return outFile.toString();
          }
          /**
           * 是否需要定义测试类变量
           * <li></li>
           * @author duanyong@jccfc.com
           * @date 2021/1/7 17:18
           * @return: boolean
           */
          protected boolean needDefineVal(){
              return true;
          }
          /**
           * 构建模板公共数据Map对象
           * <li></li>
           * @author duanyong@jccfc.com
           * @date 2021/1/7 13:54
           * @param sourceClass: 目标class对象
           * @return: java.util.Map<java.lang.String,java.lang.Object>
           */
          protected Map<String, Object> buildCommonDataMap(Class sourceClass) {
              // 定义模板数据
              Map<String, Object> data = new HashMap<>(6);
              //组装基础数据到模板数据Map对象
              populateBaseData(sourceClass, data);
              //组装方法数据到模板数据Map对象
              populateMethodMetaData(sourceClass, data);
      
              return data;
          }
          ...
      }
      
      

      DefaultJUnit4TemplatePlugin:

      ```java
      package com.javacoo.junit.generator.internal.plugin.junit4;
      
      import java.util.Map;
      
      import com.javacoo.junit.generator.enmus.JUnitVersionEnum;
      import com.javacoo.junit.generator.enmus.TemplateTypeEnum;
      
      /**
       * JUnit4模板插件默认实现
       * <li></li>
       *
       * @author: duanyong@jccfc.com
       * @since: 2021/1/4 11:18
       */
      public class DefaultJUnit4TemplatePlugin extends AbstractTemplatePlugin {
          /**
           * 根据类对象获取模板数据
           * <li></li>
           *
           * @param sourceClass :类对象
           * @author duanyong@jccfc.com
           * @date 2021/1/4 11:09
           * @return: java.util.Map<java.lang.String, java.lang.Object>
       */
          @Override
    public Map<String, Object> getTemplateData(Class sourceClass) {
              Map<String, Object> data = buildCommonDataMap(sourceClass);
      
              return data;
          }
      
          /**
           * 获取模板路径
           * <li></li>
           *
           * @author duanyong@jccfc.com
           * @date 2021/1/8 10:57
           * @return: java.lang.String
           */
          @Override
          public String getTemplatePath() {
              return BASE_TEMPLATE_PACKAGE+ JUnitVersionEnum.JUNIT4.getCode();
          }
      
          /**
           * 获取模板名称
           * <li></li>
           *
           * @author duanyong@jccfc.com
           * @date 2021/1/5 11:41
           * @return: java.lang.String 模板名称
           */
          @Override
          public String getTemplateName() {
              return TemplateTypeEnum.TEMPLATE_TYPE_ENUM_DEFAULT.getValue();
          }
      }
      
      ```
    
    1. 编写模板文件,如:基于JUnit4的普通类测试模板文件:DefaultTemplate.ftl

      package ${basePackage};
      
      import org.junit.*;
      import static org.junit.Assert.*;
      
      <#list importClasses as importClass>
      import ${importClass};
      </#list>
      
      /**
      * ${className}的测试类
      *
      * @author ${author!''}
      * @date ${date}
      */
      public class ${className}Test {
      
          @BeforeClass
          public static void setUpClass(){
              //执行所有测试前的操作
      
          }
          @AfterClass
       public static void tearDownClass(){
              //执行完所有测试后的操作
      
          }
          @Before
          public void setUp(){
              //每次测试前的操作
      
          }
          @After
          public void tearDown(){
              //每次测试后的操作
          }
      
      <#list methods as method>
          @Test
          public void test${method.methodName?cap_first}(){
          ${method.methodBody!''}
          }
      </#list>
      
      }
      
    1. 注册接口:在项目resources目录下创建:META-INF/ext目录,并创建一个文本文件:名称为接口的“全限定名”,内容格式为:实现名=实现类的全限定名,如。文件名为:com.javacoo.junit.generator.api.TemplatePlugin。内容如下:

      myTemplateHander=com.xxx.plugin.MyJUnit4TemplateHanderPlugin
      

      格式为:处理器名称=处理器实现类全路径类名


      3.png
    1. 使用:在junitGeneratorConfig.xml配置文件的template节点,配置属性 templateHandlerName="myTemplateHander"

    future

    • ​ 基于JUnit5的单元测试
      参数化单元测试
    •     支持Mock
      默认mockito实现
      

    项目地址:https://gitee.com/javacoo/junit-generator

    一些信息

    路漫漫其修远兮,吾将上下而求索
    码云:https://gitee.com/javacoo
    QQ群:164863067
    作者/微信:javacoo
    邮箱:xihuady@126.com
    

    相关文章

      网友评论

        本文标题:junit-generator Junit 单元测试生成工具Ma

        本文链接:https://www.haomeiwen.com/subject/ixwdaktx.html