Spring + jacoco + powerMock 单元测试

2021-05-19  本文已影响0人  dynemm

一 什么是单元测试

什么是单元测试呢?单元测试就是针对最小的功能单元编写测试代码。Java程序最小的功能单元是方法,因此,对Java程序进行单元测试就是针对单个Java方法的测试。
其实在日常的工作中,大部分开发都很容易对项目的单测忽略,甚至因为工期紧张就没有写过单元测试,作者上一家公司就没有写单测的习惯,换了工作之后,这边对单测的要求还是比较高的,新项目行覆盖率需要达到80%,旧的项目行覆盖率需要达到60%,在经过几次磨练之后,接下来就重点的讲一讲如何写单测(其实我也写的不是很好),以及如何知道自己的项目的一个整体的单测覆盖情况,这里面有一些概念啊比如说行覆盖率、方法覆盖率等大家事先先了解一下

二 Spring + Jacoco + powerMock 单测

其实写单测很简单,我们最熟悉的就是用大家都很熟悉Junit去编写单测,但是这对一般的方法逻辑是可行的,但是遇到有外部依赖等复杂条件,用Junit去写就不是很方便了。
一般处理Spring 单测我们一般的做法就是如下启动Spring容器:


image.png

其实这样的做法有几个不好的地方哈:
1.试想,这每个测试方法都需要进行Spring 容器的初始化,这样整一个CI流程跑下来的话,时间就会很长
2.如果遇到有第三方调用的情况(没有进行mock的情况),本机环境和三方环境不通的情况,很容易就导致了单测的失败
那么有人就说了,那你第三方的调用数据进行mock不就行了嘛,其实是可以的哈,其实单测的话 从controller层 到 service层 到 dao、RPC层 都应该进行单测编写,但是实际开发时间都不会允许有这么长的时间去整,所以一般的关注点就是在service层,并且建议controller层不要写太多的业务代码,尽量都放在service层里面。那么service层的代码将那些重要的操作进行mock掉之后,就可以在不启动容器的情况下快速的进行跑单测,但这个不是绝对的哈,根据自己项目的风格来,该启容器还是得起的。
好嘛,讲了这么多,其实大家很多人都应该知道这些个理论啊,那么接下来就手把手教大家怎么集成并且手机

三 实战

首先父pom文件需要引入一些依赖和jacoco的pluin工具

父pom文件:
        <lombok-version>1.18.16</lombok-version>
        <org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
        <jacoco.version>0.8.0</jacoco.version>
        <powermock-api-version>1.7.4</powermock-api-version>
        <powermock-module-junit4.version>2.0.2</powermock-module-junit4.version>
        <mockito-all.version>${powermock-module-junit4.version}-beta</mockito-all.version>

    <dependencies>
        <dependency>
            <groupId>org.jacoco</groupId>
            <artifactId>org.jacoco.agent</artifactId>
            <version>${jacoco.version}</version>
            <classifier>runtime</classifier>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.0.0-M1</version>
            <type>maven-plugin</type>
            <scope>test</scope>
        </dependency>
    </dependencies>

<dependencyManagement>
            <!--单测相关-->
            <dependency>
                <artifactId>spring-boot-starter-test</artifactId>
                <groupId>org.springframework.boot</groupId>
                <version>1.5.22.RELEASE</version>
            </dependency>
            <dependency>
                <artifactId>junit</artifactId>
                <groupId>junit</groupId>
                <scope>test</scope>
                <version>4.12</version>
            </dependency>
            <dependency>
                <artifactId>spring-test</artifactId>
                <groupId>org.springframework</groupId>
                <version>4.3.18.RELEASE</version>
            </dependency>
            <!--power mock-->
            <dependency>
                <groupId>org.powermock</groupId>
                <artifactId>powermock-module-junit4</artifactId>
                <scope>test</scope>
                <version>${powermock-module-junit4.version}</version>
            </dependency>
            <dependency>
                <groupId>org.powermock</groupId>
                <artifactId>powermock-api-mockito</artifactId>
                <scope>test</scope>
                <version>${powermock-api-version}</version>
            </dependency>
            <dependency>
                <groupId>org.powermock</groupId>
                <artifactId>powermock-core</artifactId>
                <version>${powermock-api-version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.mockito</groupId>
                <artifactId>mockito-all</artifactId>
                <version>${mockito-all.version}</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

<build>
        <plugins>
            <!--      <plugin>-->
            <!--        &lt;!&ndash; The gmavenplus plugin is used to compile Groovy code. To learn more about this plugin,-->
            <!--        visit https://github.com/groovy/GMavenPlus/wiki &ndash;&gt;-->
            <!--        <groupId>org.codehaus.gmavenplus</groupId>-->
            <!--        <artifactId>gmavenplus-plugin</artifactId>-->
            <!--        <version>1.6</version>-->
            <!--        <executions>-->
            <!--          <execution>-->
            <!--            <goals>-->
            <!--              <goal>compile</goal>-->
            <!--              <goal>compileTests</goal>-->
            <!--            </goals>-->
            <!--          </execution>-->
            <!--        </executions>-->
            <!--      </plugin>-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M1</version>
                <configuration>
                    <reportsDirectory>../surefire-reports</reportsDirectory>
                    <systemPropertyVariables>
                        <jacoco-agent.destfile>jacoco.exec</jacoco-agent.destfile>
                    </systemPropertyVariables>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version> <!-- or newer version -->
                <configuration>
                    <source>1.8</source> <!-- depending on your project -->
                    <target>1.8</target> <!-- depending on your project -->
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok-version}</version>
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                        <!-- other annotation processors -->
                    </annotationProcessorPaths>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco.version}</version>
                <executions>
                    <execution>
                        <id>default-instrument</id>
                        <goals>
                            <goal>instrument</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>default-restore-instrumented-classes</id>
                        <goals>
                            <goal>restore-instrumented-classes</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

哪个模块需要打桩,就把dependencyManagement 里面的jar包引入到模块里面就可以了
接下来就是划重点的地方了,项目里面需要重新起一个模块用于进行单测结果的收集和聚合,如果想对service层的代码就行聚合的话,添加这个模块的依赖就行了,初次以外,如果有一些文件你觉得不需要进行单测手机,以免影响到你的整个项目的测试覆盖率,那么你可以自定义的去排除这些文件夹或者文件 话不多说 上代码

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>dietetic</artifactId>
        <groupId>com.dynelm</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>dynelm-dietetic-test</artifactId>

    <dependencies>
        <dependency>
            <artifactId>dynelm-dietetic-service</artifactId>
            <groupId>com.dynelm</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.0</version>
                <configuration>
                    <fileSets>
                        <fileSet>
                            <directory>..</directory>
                            <include>**/*.exec</include>
                        </fileSet>
                    </fileSets>


                    <!--                    <includes>-->
                    <!--                        <include>com/bytedance/cg/gcrm/contact/**</include>-->
                    <!--                    </includes>-->
                    <!--                    <excludes>-->
                    <!--                        &lt;!&ndash;thrift生成的文件过滤掉&ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/remote/**</exclude>-->
                    <!--                        &lt;!&ndash;mybatis生成的文件过滤掉&ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/repository/mapper/**</exclude>-->
                    <!--                        &lt;!&ndash; mq不支持 &ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/mq/**</exclude>-->
                    <!--                        &lt;!&ndash;登录过滤器过滤掉&ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/web/interceptor/**</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/listener/**</exclude>-->
                    <!--                        &lt;!&ndash;common定义去掉,不是业务逻辑&ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/web/common/**</exclude>-->
                    <!--                        &lt;!&ndash;启动器不单测&ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/web/LocalLifeHttpServiceApplication**</exclude>-->
                    <!--                        &lt;!&ndash;demo&ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/web/controller/DemoController.class</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/web/controller/TestController.class</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/service/impl/TestService.class</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/filter/TccLogbackFilter.class</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/es/**</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/adapter/**</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/rmq/consumer/**</exclude>-->
                    <!--                        &lt;!&ndash;这三个都带有导入性质的,写单测成本太高,保证controller和service可用即可&ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/repository/impl/LeadRepositoryImpl.class</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/repository/impl/IMaterialDataSummaryRepositoryImpl.class</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/repository/impl/BaseAccountRoleRepositoryImpl.class</exclude>-->
                    <!--                        &lt;!&ndash;bsm的不是所有方法都用,暂时规避调&ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/adapter/BsmAdapter.class</exclude>-->
                    <!--                        &lt;!&ndash; 账号同步的定时任务不测试 &ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/service/impl/AccountSaleBaseRefService.class</exclude>-->
                    <!--                        &lt;!&ndash; 数据导入脚本 &ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/listener/IndexOrNameDataListener.class</exclude>-->
                    <!--                        &lt;!&ndash;转换器不测试&ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/adapter/converter/**</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/infrastructure/repository/converter/**</exclude>-->
                    <!--                        &lt;!&ndash;工具类和配置不测试&ndash;&gt;-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/utils/**</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/entity/query/**</exclude>-->
                    <!--                        <exclude>com/bytedance/cg/locallife/base/conf/**</exclude>-->
                    <!--                        &lt;!&ndash;自动生成的不测试&ndash;&gt;-->
                    <!--                        <exclude>**.equals**</exclude>-->
                    <!--                        <exclude>**.hashCode**</exclude>-->
                    <!--                        <exclude>**.toString**</exclude>-->
                    <!--                        <exclude>**.canEqual**</exclude>-->
                    <!--                        &lt;!&ndash;dto对象过滤掉&ndash;&gt;-->
                    <!--                        <exclude>**/dto/**</exclude>-->
                    <!--                        <exclude>**Dto.class</exclude>-->
                    <!--                        <exclude>**/vo/**</exclude>-->
                    <!--                        <exclude>**/req/**</exclude>-->
                    <!--                    </excludes>-->
                </configuration>
                <executions>
                    <execution>
                        <phase>verify</phase>
                        <goals>
                            <goal>merge</goal>
                            <goal>report-aggregate</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

至此,整个收集的就搭建完了,我来测试一下,编写好单测后,输入命令行:mvn clean verify -Dmaven.test.failure.ignore=true 进行收集,我们会在你刚才新建的收集模块的target里面找到一个index.xml文件,右键运行就可以看到整个项目的单测情况,如下所示:


image.png

结果如图:


image.png
红色标注的就是整个项目的行覆盖率,每个包你都能点进行看那些代码覆盖到了,哪些代码没覆盖到,根据这些来进行单测补充。
好的,下次会给大家详细介绍一下,怎么去写单测的代码!
上一篇下一篇

猜你喜欢

热点阅读