Junit 单元测试生成工具Maven插件

junit-generator

介绍

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

需求

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

类关系图

Junit 单元测试生成工具Maven插件_第1张图片

主要技术说明
  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.javacoogroupId>
                    <artifactId>junit-generator-maven-pluginartifactId>
                    <version>1.1.0-SNAPSHOTversion>
                    <configuration>
                       
                        <overwrite>falseoverwrite>
                        
                        <backup>truebackup>
                        
                        <configurationFile>src/test/resources/junitGeneratorConfig.xmlconfigurationFile>
                        
                        <contexts>testContext,springTestContextcontexts>
                    configuration>
                plugin>
            plugins>
        build>
    
  2. 添加配置文件:junitGeneratorConfig.xml

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

            
    
    
    
    
    	
    		
    		
    		
    		
    			
                
    	
         
            
            
            
            
                
            
        
        
            
            
            
            
                
            
        
    
    
  3. 生成测试代码:

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

Junit 单元测试生成工具Maven插件_第2张图片

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

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

Junit 单元测试生成工具Maven插件_第3张图片

使用说明
  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文件定义:

     
    

    第二行为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>    
         
    	<context id="TestContext">   
    		
            <template templatePath="/template" templateName="test.ftl" templateHandlerName="spring">template>
    		
    		<classList>
                
    			<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. 关闭覆盖功能,如:false
  2. 开启备份功能,如:true
  3. 配置需要执行的上下文ID,如:testContext,springTestContext
  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;
     
     /**
      * 模板插件
      * 
  2. 此插件目的是为自定义模板生成规则提供入口,程序会根据插件提供的模板数据渲染指定路径下,指定模板名称的模板,并输出到指定目录
  3. *
  4. 插件机制基于Java SPI机制的扩展,原理及开发步骤见:https://gitee.com/javacoo/xkernel
  5. *
  6. 注意:目前只支持Freemarker模板引擎,开发手册见:http://freemarker.foofun.cn/
  7. * @author: [email protected] * @since: 2021/1/4 10:07 */
    @Spi("default") public interface TemplatePlugin { /** * 根据类对象获取模板数据 *
  8. 此数据用于填充模板
  9. * @author [email protected] * @date 2021/1/4 11:09 * @param sourceClass:类对象 * @return: java.util.Map */
    Map<String, Object> getTemplateData(Class sourceClass); /** * 根据类对象获取输出文件路径 *
  10. 指定测试类文件生成的路径
  11. * @author [email protected] * @date 2021/1/4 11:51 * @param sourceClass: 类对象 * @param outputFilePath: 输出路径 * @return: java.lang.String */
    String getOutFile(Class sourceClass,String outputFilePath); /** * 获取模板路径 *
  12. 外部模板所在路径
  13. * @author [email protected] * @date 2021/1/8 10:57 * @return: java.lang.String */
    String getTemplatePath(); /** * 获取模板名称 *
  14. 模板名称,带后缀
  15. * @author [email protected] * @date 2021/1/5 11:41 * @return: java.lang.String 模板名称 */
    String getTemplateName(); }

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

    AbstractTemplatePlugin

    package com.javacoo.junit.generator.internal.plugin.junit4;
    
    ...
    /**
     * 模板插件接口抽象实现类
     * 
  16. 定义了插件所需公共方法及流程
  17. * * @author: [email protected] * @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"; /** * 根据类对象获取输出文件路径 *
  18. * * @param sourceClass : 类对象 * @param outputFilePath: 输出路径 * @author [email protected] * @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(); } /** * 是否需要定义测试类变量 *
  19. * @author [email protected] * @date 2021/1/7 17:18 * @return: boolean */
    protected boolean needDefineVal(){ return true; } /** * 构建模板公共数据Map对象 *
  20. * @author [email protected] * @date 2021/1/7 13:54 * @param sourceClass: 目标class对象 * @return: java.util.Map */
    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:

    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模板插件默认实现
     * 
  21. * * @author: [email protected] * @since: 2021/1/4 11:18 */
    public class DefaultJUnit4TemplatePlugin extends AbstractTemplatePlugin { /** * 根据类对象获取模板数据 *
  22. * * @param sourceClass :类对象 * @author [email protected] * @date 2021/1/4 11:09 * @return: java.util.Map */
    @Override public Map<String, Object> getTemplateData(Class sourceClass) { Map<String, Object> data = buildCommonDataMap(sourceClass); return data; } /** * 获取模板路径 *
  23. * * @author [email protected] * @date 2021/1/8 10:57 * @return: java.lang.String */
    @Override public String getTemplatePath() { return BASE_TEMPLATE_PACKAGE+ JUnitVersionEnum.JUNIT4.getCode(); } /** * 获取模板名称 *
  24. * * @author [email protected] * @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};
      
      
      /**
      * ${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!''}
          }
      
      
      }
      
    2. 注册接口:在项目resources目录下创建:META-INF/ext目录,并创建一个文本文件:名称为接口的“全限定名”,内容格式为:实现名=实现类的全限定名,如。文件名为:com.javacoo.junit.generator.api.TemplatePlugin。内容如下:

      myTemplateHander=com.xxx.plugin.MyJUnit4TemplateHanderPlugin
      

      格式为:处理器名称=处理器实现类全路径类名
      Junit 单元测试生成工具Maven插件_第4张图片

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

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

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

一些信息
路漫漫其修远兮,吾将上下而求索
码云:https://gitee.com/javacoo
QQ群:164863067
作者/微信:javacoo
邮箱:[email protected]

你可能感兴趣的:(开源共享,单元测试,maven,java)