Spring + jacoco + powerMock 单元测试搭建

一 什么是单元测试

什么是单元测试呢?单元测试就是针对最小的功能单元编写测试代码。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

红色标注的就是整个项目的行覆盖率,每个包你都能点进行看那些代码覆盖到了,哪些代码没覆盖到,根据这些来进行单测补充。
好的,下次会给大家详细介绍一下,怎么去写单测的代码!
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,230评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,261评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,089评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,542评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,542评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,544评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,922评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,578评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,816评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,576评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,658评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,359评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,920评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,859评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,381评论 2 342

推荐阅读更多精彩内容