如何在Springboot項目中添加testng+mockito+jacoco單元測試

 

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-pluginjacoco-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%

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章