1、前言
在日常开发中,当开发某一个模块或者功能时,首先要考虑的是业务逻辑和业务场景,然后会根据业务逻辑和场景进行代码的编写,这其中可能会牵涉到很多的逻辑判断,必要时可能需要与数据库做交互。
但有时开发人员自己也不知道是否把所有的业务场景都包含进去,是否有些逻辑判断可能压根都没有用到,所以一般在开发之后都需要开发人员先对自己所编写的代码进行自测,测试功能是否通畅等,有些可能需要检测测试代码是否覆盖到所有的逻辑分支上。但是在自测功能的时候,如果功能中有需要跟数据库做交互的部分,那这种自测其实做起来比较麻烦,因为如果与数据库做交互需要配置一系列的东西,实施起来比较的麻烦。所以就诞生了testng+mockito+jacoco单元测试模式。
其中testng的作用类似于Junit4,但是包含了Junit4的所有功能,并在它的基础上衍生了更多的功能。详见:https://blog.csdn.net/asd43211234/article/details/105732874
mockito的作用主要是做一些数据模拟工作,比如模拟某个对象,模拟与数据库交互,模拟某些方法的返回值等。
jacoco的作用主要是检测单元测试是否覆盖了整个要测试的类,哪些逻辑分支没有测试到等,并生成测试报告,通过测试报告可以更直观的看到代码覆盖率的情况。
2、准备工作
1、导入testng、mockito、jacoco的依赖,在要测试的类所在的module模块或者所有module的父module的pom文件中加入以下依赖:
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.28.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-testng</artifactId>
<version>2.0.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.9</version>
<scope>test</scope>
</dependency>
2、去掉spring-boot-starter-test依赖,如果要测试的module中引入了spring-boot-starter-test,则需要把之前引入的spring-boot-starter-test依赖注释掉。
3、需要在业务实现模块中添加testng.xml文件和testng-1.0.dtd文件
testng.xml
<!DOCTYPE suite SYSTEM "testng-1.0.dtd" >
<suite name="TestSuite" verbose="1">
<test name="apiTest">
<packages>
<package name="全限定包路径.*"></package>
</packages>
</test>
</suite>
testng-1.0.dtd
<!--
Here is a quick overview of the main parts of this DTD. For more information,
refer to the <a href="http://testng.org">main web site</a>.
A <b>suite</b> is made of <b>tests</b> and <b>parameters</b>.
A <b>test</b> is made of three parts:
<ul>
<li> <b>parameters</b>, which override the suite parameters
<li> <b>groups</b>, made of two parts
<li> <b>classes</b>, defining which classes are going to be part
of this test run
</ul>
In turn, <b>groups</b> are made of two parts:
<ul>
<li> Definitions, which allow you to group groups into
bigger groups
<li> Runs, which defines the groups that the methods
must belong to in order to be run during this test
</ul>
Cedric Beust & Alexandru Popescu
@title DTD for TestNG
@root suite
-->
<!-- A suite is the top-level element of a testng.xml file -->
<!ELEMENT suite (groups?,(listeners|packages|test|parameter|method-selectors|suite-files)*) >
<!-- Attributes: -->
<!--
@attr name The name of this suite (as it will appear in the reports)
@attr junit Whether to run in JUnit mode.
@attr verbose How verbose the output on the console will be.
This setting has no impact on the HTML reports.
@attr parallel Whether TestNG should use different threads
to run your tests (might speed up the process)
Do not use "true" and "false" values, they are now deprecated.
@attr parent-module A module used to create the parent injector of all guice injectors used
in tests of the suite
@attr guice-stage The stage with which the parent injector is created
@attr configfailurepolicy Whether to continue attempting Before/After
Class/Methods after they've failed once or just skip remaining.
@attr thread-count An integer giving the size of the thread pool to use
if you set parallel.
@attr annotations If "javadoc", TestNG will look for
JavaDoc annotations in your sources, otherwise it will
use JDK5 annotations.
@attr time-out The time to wait in milliseconds before aborting the
method (if parallel="methods") or the test (parallel="tests")
@attr skipfailedinvocationcounts Whether to skip failed invocations.
@attr data-provider-thread-count An integer giving the size of the thread pool to use
for parallel data providers.
@attr object-factory A class that implements IObjectFactory that will be used to
instantiate the test objects.
@attr allow-return-values If true, tests that return a value will be run as well
-->
<!ATTLIST suite
name CDATA #REQUIRED
junit (true | false) "false"
verbose CDATA #IMPLIED
parallel (false | true | none | methods | tests | classes | instances) "none"
parent-module CDATA #IMPLIED
guice-stage (DEVELOPMENT | PRODUCTION | TOOL) "DEVELOPMENT"
configfailurepolicy (skip | continue) "skip"
thread-count CDATA "5"
annotations CDATA #IMPLIED
time-out CDATA #IMPLIED
skipfailedinvocationcounts (true | false) "false"
data-provider-thread-count CDATA "10"
object-factory CDATA #IMPLIED
group-by-instances (true | false) "false"
preserve-order (true | false) "true"
allow-return-values (true | false) "false"
>
<!-- A list of XML files that contain more suite descriptions -->
<!ELEMENT suite-files (suite-file)* >
<!ELEMENT suite-file ANY >
<!ATTLIST suite-file
path CDATA #REQUIRED
>
<!--
Parameters can be defined at the <suite> or at the <test> level.
Parameters defined at the <test> level override parameters of the same name in <suite>
Parameters are used to link Java method parameters to their actual value, defined here.
-->
<!ELEMENT parameter ANY>
<!ATTLIST parameter
name CDATA #REQUIRED
value CDATA #REQUIRED >
<!--
Method selectors define user classes used to select which methods to run.
They need to implement <tt>org.testng.IMethodSelector</tt>
-->
<!ELEMENT method-selectors (method-selector*) >
<!ELEMENT method-selector ((selector-class)*|script) >
<!ELEMENT selector-class ANY>
<!ATTLIST selector-class
name CDATA #REQUIRED
priority CDATA #IMPLIED
>
<!ELEMENT script ANY>
<!ATTLIST script
language CDATA #REQUIRED
>
<!--
A test contains parameters and classes. Additionally, you can define additional groups ("groups of groups")
-->
<!ELEMENT test (method-selectors?,parameter*,groups?,packages?,classes?) >
<!--
@attr name The name of this test (as it will appear in the reports)
@attr junit Whether to run in JUnit mode.
@attr verbose How verbose the output on the console will be.
This setting has no impact on the HTML reports.
Default value: suite level verbose.
@attr parallel Whether TestNG should use different threads
to run your tests (might speed up the process)
Do not use "true" and "false" values, they are now deprecated.
@attr thread-count An integer giving the size of the thread pool to be used if
parallel mode is used. Overrides the suite level value.
@attr annotations If "javadoc", TestNG will look for
JavaDoc annotations in your sources, otherwise it will
use JDK5 annotations.
@attr time-out the time to wait in milliseconds before aborting
the method (if parallel="methods") or the test (if parallel="tests")
@attr enabled flag to enable/disable current test. Default value: true
@attr skipfailedinvocationcounts Whether to skip failed invocations.
@attr preserve-order If true, the classes in this tag will be run in the same order as
found in the XML file.
@attr allow-return-values If true, tests that return a value will be run as well
-->
<!ATTLIST test
name CDATA #REQUIRED
junit (true | false) "false"
verbose CDATA #IMPLIED
parallel (false | true | none | methods | tests | classes | instances) #IMPLIED
thread-count CDATA #IMPLIED
annotations CDATA #IMPLIED
time-out CDATA #IMPLIED
enabled (true | false) #IMPLIED
skipfailedinvocationcounts (true | false) "false"
preserve-order (true | false) "true"
group-by-instances (true | false) "false"
allow-return-values (true | false) "false"
>
<!--
Defines additional groups ("groups of groups") and also which groups to include in this test run
-->
<!ELEMENT groups (define*,run?,dependencies?) >
<!ELEMENT define (include*)>
<!ATTLIST define
name CDATA #REQUIRED>
<!-- Defines which groups to include in the current group of groups -->
<!ELEMENT include ANY>
<!ATTLIST include
name CDATA #REQUIRED
description CDATA #IMPLIED
invocation-numbers CDATA #IMPLIED>
<!-- Defines which groups to exclude from the current group of groups -->
<!ELEMENT exclude ANY>
<!ATTLIST exclude
name CDATA #REQUIRED>
<!-- The subtag of groups used to define which groups should be run -->
<!ELEMENT run (include?,exclude?)* >
<!ELEMENT dependencies (group*)>
<!ELEMENT group ANY>
<!ATTLIST group
name CDATA #REQUIRED
depends-on CDATA #REQUIRED>
<!-- The list of classes to include in this test -->
<!ELEMENT classes (class*,parameter*) >
<!ELEMENT class (methods|parameter)* >
<!ATTLIST class
name CDATA #REQUIRED >
<!-- The list of packages to include in this test -->
<!ELEMENT packages (package*) >
<!-- The package description.
If the package name ends with .* then subpackages are included too.
-->
<!ELEMENT package (include?,exclude?)*>
<!ATTLIST package
name CDATA #REQUIRED >
<!-- The list of methods to include/exclude from this test -->
<!ELEMENT methods (include?,exclude?,parameter?)* >
<!-- The list of listeners that will be passed to TestNG -->
<!ELEMENT listeners (listener*) >
<!ELEMENT listener ANY>
<!ATTLIST listener
class-name CDATA #REQUIRED >
4、需要在业务实现模块的pom.xml文件中添加插件maven-surefire-plugin和jacoco-maven-plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.10</version>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>testng.xml</suiteXmlFile>
</suiteXmlFiles>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<includes>
<include>com/saicmotor/telematics/icar/service/vehicle/api/impl/**</include>
</includes>
</configuration>
</plugin>
3、编写测试类
public class ResponseErrorLogApiImplTest {
//要进行功能测试和查看代码覆盖率的实现类
@InjectMocks
private ResponseErrorLogApiImpl responseErrorLogApi;
//与数据库交互的service方法
@Mock
private TbResponseErrorLogService tbResponseErrorLogService;
@BeforeTest
public void initMocks() throws Exception {
System.out.println("BeforeTest");
// 2 初始化当前测试类所有@Mock注解模拟对象
MockitoAnnotations.initMocks(this);
}
//导入testng下的test注解包
@Test
public void testSaveErrorLog(){
LogRequest logRequest = new LogRequest();
logRequest.setXXX(XXX);
logRequest.setYYY(YYY);
//模拟保存数据库成功
when(tbResponseErrorLogService.save(any())).thenReturn(true);
//要测试的功能
Result result = responseErrorLogApi.saveErrorLog(logRequest);
//比对期望值与实际值是否一致
Assert.assertEquals(ResultCode.CODE_500,result.getCode());
}
}
运行Test即可测试某个方法的的结果是否为期望的结果。
4、查看代码覆盖率
在Terminal进入到业务逻辑模块的路径执行mvn clean test或者使用mvn 命令指定具体mvn XXXModule clean test编译整个模块下的Test类。
编译完成后到target目录下找到如下目录,打开index.html,选择对应的包名后选择@InjectMocks标注的类名即可看到代码覆盖率。
从上图可以看到代码覆盖率为88%,逻辑分支覆盖率为83%