精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

外部依賴太多,如何寫 Java 單元測試?

開發 后端
Mockito是一個非常優秀的模擬框架,可以使用它簡潔的API來編寫漂亮的測試代碼,它的測試代碼可讀性高同時會產生清晰的錯誤日志。

[[405509]]

本文轉載自微信公眾號「碼農私房話」,作者Liew 。轉載本文請聯系碼農私房話公眾號。

事出有因

在日常的開發中,很多人習慣性地寫完需求代碼后,嗖的一聲用 Postman 模擬真實請求或寫幾個 JUnit 的單元測試跑功能點,只要沒有問題就上線了,但其實這存在很大風險,一方面無法驗證業務邏輯的不同分支,另外一方面需嚴重依賴中間件資源才能運行測試用例,占用大量資源。

秣馬厲兵

Mockito是一個非常優秀的模擬框架,可以使用它簡潔的API來編寫漂亮的測試代碼,它的測試代碼可讀性高同時會產生清晰的錯誤日志。

添加 maven 依賴

  1. <dependency> 
  2.     <groupId>org.mockito</groupId> 
  3.     <artifactId>mockito-core</artifactId> 
  4.     <version>3.3.3</version> 
  5.     <scope>test</scope> 
  6. </dependency> 

 

注意:Mockito 3.X 版本使用了 JDK8 API,但功能與 2.X 版本并沒有太大的變化。

指定 MockitoJUnitRunner

  1. @RunWith(MockitoJUnitRunner.class) 
  2. public class MockitoDemoTest { 
  3.  
  4.     //注入依賴的資源對象 
  5.     @Mock 
  6.     private MockitoTestService mockitoTestService; 
  7.     @Before 
  8.     public void before(){ 
  9.         MockitoAnnotations.initMocks(this); 
  10.     } 

從代碼中觀察到,使用 @Mock 注解標識哪些對象需要被 Mock,同時在執行測試用例前初始化 MockitoAnnotations.initMocks(this) 告訴框架使 Mock 相關注解生效。

驗證對象行為 Verify

  1. @Test 
  2. public void testVerify(){ 
  3.     //創建mock 
  4.     List mockedList = mock(List.class); 
  5.     mockedList.add("1"); 
  6.     mockedList.clear(); 
  7.     //驗證list調用過add的操作行為 
  8.     verify(mockedList).add("1"); 
  9.     //驗證list調用過clear的操作行為 
  10.     verify(mockedList).clear(); 
  11.     //使用內建anyInt()參數匹配器,并存根 
  12.     when(mockedList.get(anyInt())).thenReturn("element"); 
  13.     System.out.println(mockedList.get(2)); //此處輸出為element 
  14.     verify(mockedList).get(anyInt()); 

存根 stubbing

stubbing 完全是模擬一個外部依賴、用來提供測試時所需要的數據。

  1. @Test 
  2. public void testStub(){ 
  3.     //可以mock具體的類,而不僅僅是接口 
  4.     LinkedList mockedList = mock(LinkedList.class); 
  5.     //存根(stubbing) 
  6.     when(mockedList.get(0)).thenReturn("first"); 
  7.     when(mockedList.get(1)).thenThrow(new RuntimeException()); 
  8.     //下面會打印 "first" 
  9.     System.out.println(mockedList.get(0)); 
  10.     //下面會拋出運行時異常 
  11.     System.out.println(mockedList.get(1)); 
  12.     //下面會打印"null" 因為get(999)沒有存根(stub) 
  13.     System.out.println(mockedList.get(999)); 
  14.     doThrow(new RuntimeException()).when(mockedList).clear(); 
  15.     //下面會拋出 RuntimeException: 
  16.     mockedList.clear(); 
  • 存根(stub)可以覆蓋,測試方法可以覆蓋全局設置的通用存根。
  • 一旦做了存根,無論這個方法被調用多少次,方法將總是返回存根的值。

存根的連續調用

  1. @Test 
  2. public void testStub() { 
  3.     when(mock.someMethod("some arg")) 
  4.     .thenThrow(new RuntimeException()) 
  5.     .thenReturn("foo"); 
  6.     mock.someMethod("some arg"); //第一次調用:拋出運行時異常 
  7.     //第二次調用: 打印 "foo" 
  8.     System.out.println(mock.someMethod("some arg")); 
  9.     //任何連續調用: 還是打印 "foo" (最后的存根生效). 
  10.     System.out.println(mock.someMethod("some arg")); 
  11.     //可供選擇的連續存根的更短版本: 
  12.     when(mock.someMethod("some arg")).thenReturn("one""two""three"); 
  13.     when(mock.someMethod(anyString())).thenAnswer(new Answer() { 
  14.         Object answer(InvocationOnMock invocation) { 
  15.             Object[] args = invocation.getArguments(); 
  16.             Object mock = invocation.getMock(); 
  17.             return "called with arguments: " + args; 
  18.         } 
  19.     }); 
  20.     // "called with arguments: foo 
  21.     System.out.println(mock.someMethod("foo")); 

在做方法存根時,可以指定不同時機需要提供的測試數據,例如第一次調用返回 xxx,第二次調用時拋出異常等。

參數匹配器

  1. @Test 
  2. public void testArugument{ 
  3.     //使用內建anyInt()參數匹配器 
  4.     when(mockedList.get(anyInt())).thenReturn("element"); 
  5.     System.out.println(mockedList.get(999)); //打印 "element" 
  6.     //同樣可以用參數匹配器做驗證 
  7.     verify(mockedList).get(anyInt()); 
  8.  
  9.     //注意:如果使用參數匹配器,所有的參數都必須通過匹配器提供。 
  10.     verify(mock) 
  11.     .someMethod(anyInt(), anyString(), eq("third argument")); 
  12.     //上面是正確的 - eq(0也是參數匹配器),而下面的是錯誤的 
  13.     verify(mock) 
  14.     .someMethod(anyInt(), anyString(), "third argument"); 

驗證調用次數

  1. @Test 
  2. public void testVerify{ 
  3.     List<String> mockedList = new ArrayList(); 
  4.     mockedList.add("once"); 
  5.     mockedList.add("twice"); 
  6.     mockedList.add("twice"); 
  7.     mockedList.add("three times"); 
  8.     mockedList.add("three times"); 
  9.     mockedList.add("three times"); 
  10.     //下面兩個驗證是等同的 - 默認使用times(1) 
  11.     verify(mockedList).add("once"); 
  12.     verify(mockedList, times(1)).add("once"); 
  13.     verify(mockedList, times(2)).add("twice"); 
  14.     verify(mockedList, times(3)).add("three times"); 
  15.     //使用using never()來驗證. never()相當于 times(0) 
  16.     verify(mockedList, never()).add("never happened"); 
  17.     //使用 atLeast()/atMost()來驗證 
  18.     verify(mockedList, atLeastOnce()).add("three times"); 
  19.     verify(mockedList, atLeast(2)).add("five times"); 
  20.     verify(mockedList, atMost(5)).add("three times"); 

驗證調用順序

  1. @Test 
  2. public void testOrder() 
  3.     // A. 單個Mock,方法必須以特定順序調用 
  4.     List singleMock = mock(List.class); 
  5.  
  6.     //使用單個Mock 
  7.     singleMock.add("was added first"); 
  8.     singleMock.add("was added second"); 
  9.  
  10.     //為singleMock創建 inOrder 檢驗器 
  11.     InOrder inOrder = inOrder(singleMock); 
  12.  
  13.     //確保add方法第一次調用是用"was added first",然后是用"was added second" 
  14.     inOrder.verify(singleMock).add("was added first"); 
  15.     inOrder.verify(singleMock).add("was added second"); 

以上是 Mockito 框架常用的使用方式,但 Mockito 有一定的局限性, 它只能 Mock 類或者接口,對于靜態、私有及final方法的 Mock 則無能為力了。

而 PowerMock 正是彌補這塊的缺陷,它的實現原理如下:

  • 當某個測試方法被注解 @PrepareForTest 標注后,在運行測試用例時會創建一個新的 MockClassLoader 實例并加載該測試用例使用到的類(系統類除外)。
  • PowerMock 會根據你的 mock 要求,去修改寫在注解 @PrepareForTest 里的 class 文件內容(調用非系統的靜態、Final方法),若是包含調用系統的方法則修改調用系統方法的類的 class 文件內容達到滿足需求 。

但值得高興的是在 Mockito2.7.2 及更高版本添加了對 final 類及方法支持[1] 。

同樣, Mockito3.4.0 及更高版本支持對靜態方法的 Mock[2],雖然是處于孵化階段,但對于我們做單元測試而言是已經足夠了。

決勝之機

大多數項目使用了 Spring 或 Spring Boot 作為基礎框架,研發只需要關心業務邏輯即可。

在代碼例子中將使用 Junit5 的版本,因此要求 Spring boot版本必須是2.2.0版本或以上,采用 Mockito3.5.11 的版本作為 Mock 框架,減少項目對 PowerMock 的依賴,另外還有一個重要原因是因為目前PowerMock不支持 Junit5,無法在引入 PowerMock 后使用Junit5 的相關功能及API,本文項目代碼地址:https://github.com/GoQeng/spring-mockito3-demo。

maven 配置

  1. <properties> 
  2.     <java.version>1.8</java.version> 
  3.     <mockito.version>3.5.11</mockito.version> 
  4.     <byte-buddy.version>1.10.15</byte-buddy.version> 
  5.     <redisson-spring.version>3.13.4</redisson-spring.version> 
  6.     <mysql.version>5.1.48</mysql.version> 
  7.     <jacoco.version>0.8.6</jacoco.version> 
  8.     <junit-jupiter.version>5.6.2</junit-jupiter.version> 
  9.     <junit-platform.version>1.1.1</junit-platform.version> 
  10.     <mybatis-spring.version>2.1.3</mybatis-spring.version> 
  11.     <maven-compiler.version>3.8.1</maven-compiler.version> 
  12.     <maven-surefire.version>2.12.4</maven-surefire.version> 
  13.     <h2.version>1.4.197</h2.version> 
  14. </properties> 
  15.  
  16. <dependencies> 
  17.     <!-- spring boot相關依賴 --> 
  18.     <dependency> 
  19.         <groupId>org.springframework.boot</groupId> 
  20.         <artifactId>spring-boot-starter-web</artifactId> 
  21.     </dependency> 
  22.  
  23.     <dependency> 
  24.         <groupId>org.springframework.boot</groupId> 
  25.         <artifactId>spring-boot-starter-test</artifactId> 
  26.         <scope>test</scope> 
  27.         <exclusions> 
  28.             <exclusion> 
  29.                 <groupId>org.mockito</groupId> 
  30.                 <artifactId>mockito-core</artifactId> 
  31.             </exclusion> 
  32.             <exclusion> 
  33.                 <groupId>org.junit.vintage</groupId> 
  34.                 <artifactId>junit-vintage-engine</artifactId> 
  35.             </exclusion> 
  36.         </exclusions> 
  37.     </dependency> 
  38.  
  39.     <!-- Mockito --> 
  40.     <dependency> 
  41.         <groupId>org.mockito</groupId> 
  42.         <artifactId>mockito-core</artifactId> 
  43.         <version>${mockito.version}</version> 
  44.         <scope>compile</scope> 
  45.         <exclusions> 
  46.             <exclusion> 
  47.                 <groupId>net.bytebuddy</groupId> 
  48.                 <artifactId>byte-buddy</artifactId> 
  49.             </exclusion> 
  50.             <exclusion> 
  51.                 <groupId>net.bytebuddy</groupId> 
  52.                 <artifactId>byte-buddy-agent</artifactId> 
  53.             </exclusion> 
  54.         </exclusions> 
  55.     </dependency> 
  56.     <!-- 由于mockito-core自帶的byte-buddy版本低,無法使用mock靜態方法 --> 
  57.     <dependency> 
  58.         <groupId>net.bytebuddy</groupId> 
  59.         <artifactId>byte-buddy</artifactId> 
  60.         <version>${byte-buddy.version}</version> 
  61.     </dependency> 
  62.  
  63.     <dependency> 
  64.         <groupId>net.bytebuddy</groupId> 
  65.         <artifactId>byte-buddy-agent</artifactId> 
  66.         <version>${byte-buddy.version}</version> 
  67.         <scope>test</scope> 
  68.     </dependency> 
  69.  
  70.     <dependency> 
  71.         <groupId>org.mockito</groupId> 
  72.         <artifactId>mockito-inline</artifactId> 
  73.         <version>${mockito.version}</version> 
  74.         <scope>test</scope> 
  75.     </dependency> 
  76.  
  77.     <!-- mybatis --> 
  78.     <dependency> 
  79.         <groupId>org.mybatis.spring.boot</groupId> 
  80.         <artifactId>mybatis-spring-boot-starter</artifactId> 
  81.         <version>${mybatis-spring.version}</version> 
  82.     </dependency> 
  83.  
  84.     <!-- redisson --> 
  85.     <dependency> 
  86.         <groupId>org.redisson</groupId> 
  87.         <artifactId>redisson-spring-boot-starter</artifactId> 
  88.         <version>${redisson-spring.version}</version> 
  89.         <exclusions> 
  90.             <exclusion> 
  91.                 <groupId>junit</groupId> 
  92.                 <artifactId>junit</artifactId> 
  93.             </exclusion> 
  94.         </exclusions> 
  95.         <scope>compile</scope> 
  96.     </dependency> 
  97.  
  98.     <!-- mysql --> 
  99.     <dependency> 
  100.         <groupId>mysql</groupId> 
  101.         <artifactId>mysql-connector-java</artifactId> 
  102.         <version>${mysql.version}</version> 
  103.     </dependency> 
  104.  
  105.     <!-- 代碼覆蓋率報表--> 
  106.     <dependency> 
  107.         <groupId>org.jacoco</groupId> 
  108.         <artifactId>jacoco-maven-plugin</artifactId> 
  109.         <version>${jacoco.version}</version> 
  110.     </dependency> 
  111.  
  112.     <!-- junit5 --> 
  113.     <dependency> 
  114.         <groupId>org.junit.jupiter</groupId> 
  115.         <artifactId>junit-jupiter</artifactId> 
  116.         <version>${junit-jupiter.version}</version> 
  117.         <scope>test</scope> 
  118.     </dependency> 
  119.  
  120.     <dependency> 
  121.         <groupId>org.junit.platform</groupId> 
  122.         <artifactId>junit-platform-runner</artifactId> 
  123.         <version>${junit-platform.version}</version> 
  124.         <exclusions> 
  125.             <exclusion> 
  126.                 <groupId>junit</groupId> 
  127.                 <artifactId>junit</artifactId> 
  128.             </exclusion> 
  129.         </exclusions> 
  130.     </dependency> 
  131.  
  132.     <!-- H2數據庫--> 
  133.     <dependency> 
  134.         <groupId>com.h2database</groupId> 
  135.         <artifactId>h2</artifactId> 
  136.         <version>${h2.version}</version> 
  137.         <scope>test</scope> 
  138.         <exclusions> 
  139.             <exclusion> 
  140.                 <groupId>junit</groupId> 
  141.                 <artifactId>junit</artifactId> 
  142.             </exclusion> 
  143.         </exclusions> 
  144.     </dependency> 
  145. </dependencies> 
  146.  
  147. <build> 
  148.     <plugins> 
  149.         <plugin> 
  150.             <groupId>org.apache.maven.plugins</groupId> 
  151.             <artifactId>maven-surefire-plugin</artifactId> 
  152.             <version>${maven-surefire.version}</version> 
  153.             <executions> 
  154.                 <!--指定在mvn的test階段執行此插件 --> 
  155.                 <execution> 
  156.                     <id>test</id> 
  157.                     <goals> 
  158.                         <goal>test</goal> 
  159.                     </goals> 
  160.                 </execution> 
  161.             </executions> 
  162.             <configuration> 
  163.                 <forkMode>once</forkMode> 
  164.                 <skip>false</skip> 
  165.                 <includes> 
  166.                     <include>**/SuiteTest.java</include> 
  167.                 </includes> 
  168.             </configuration> 
  169.         </plugin> 
  170.         <plugin> 
  171.             <groupId>org.apache.maven.plugins</groupId> 
  172.             <artifactId>maven-compiler-plugin</artifactId> 
  173.             <version>${maven-compiler.version}</version> 
  174.             <configuration> 
  175.                 <source>8</source> 
  176.                 <target>8</target> 
  177.             </configuration> 
  178.         </plugin> 
  179.         <plugin> 
  180.             <groupId>org.jacoco</groupId> 
  181.             <artifactId>jacoco-maven-plugin</artifactId> 
  182.             <version>${jacoco.version}</version> 
  183.             <executions> 
  184.                 <execution> 
  185.                     <goals> 
  186.                         <goal>prepare-agent</goal> 
  187.                     </goals> 
  188.                 </execution> 
  189.                 <!-- attached to Maven test phase --> 
  190.                 <execution> 
  191.                     <id>report</id> 
  192.                     <phase>test</phase> 
  193.                     <goals> 
  194.                         <goal>report</goal> 
  195.                     </goals> 
  196.                 </execution> 
  197.             </executions> 
  198.         </plugin> 
  199.     </plugins> 
  200. </build> 
  201. <reporting> 
  202.     <plugins> 
  203.         <plugin> 
  204.             <groupId>org.jacoco</groupId> 
  205.             <artifactId>jacoco-maven-plugin</artifactId> 
  206.             <reportSets> 
  207.                 <reportSet> 
  208.                     <reports> 
  209.                         <!-- select non-aggregate reports --> 
  210.                         <report>report</report> 
  211.                     </reports> 
  212.                 </reportSet> 
  213.             </reportSets> 
  214.         </plugin> 
  215.     </plugins> 
  216. </reporting> 

 

 

 

 

 

 

maven 運行測試用例是通過調用 maven 的 surefire 插件并 fork 一個子進程來執行用例的。

forkMode 屬性指明是為每個測試創建一個進程還是所有測試共享同一個進程完成,forkMode 設置值有 never、once、always 、pertest 。

  • pretest:每一個測試創建一個新進程,為每個測試創建新的JVM進程是單獨測試的最徹底方式,但也是最慢的,不適合持續回歸。
  • once:在一個進程中進行所有測試。once 為默認設置,在持續回歸時建議使用默認設置。
  • always:在一個進程中并行的運行腳本,Junit4.7 以上版本才可以使用,surefire 的版本要在 2.6 以上提供這個功能,其中 threadCount 執行時,指定可分配的線程數量,只和參數 parallel 配合使用有效,默認為 5。
  • never:從不創建新進程進行測試。

環境準備

在項目中 test 目錄下建立測試入口類 TestApplication.java,將外部依賴 Redis 單獨配置到 DependencyConfig.java 中,同時需要在 TestApplication.class 中排除對 Redis 或 Mongodb 的自動注入配置等。

注意:將外部依賴配置到DependencyConfig并不是必要的,此步驟的目的是為了避免每個單元測試類運行時都會重啟 Spring 上下文,可采用 @MockBean 的方式在代碼中引入外部依賴資源替代此方法。

  1. @Configuration 
  2. public class DependencyConfig { 
  3.  
  4.     @Bean 
  5.     public RedissonClient getRedisClient() { 
  6.         return Mockito.mock(RedissonClient.class); 
  7.     } 
  8.  
  9.     @Bean 
  10.     public RestTemplate restTemplate() { 
  11.         return Mockito.mock(RestTemplate.class); 
  12.     } 

接著在測試入口類中通過 @ComponentScan 對主入口啟動類 Application.class 及 RestClientConfig.class 進行排除。

  1. @SpringBootApplication 
  2. @ComponentScan(excludeFilters = @ComponentScan.Filter( 
  3.         type = FilterType.ASSIGNABLE_TYPE, 
  4.         classes = {Application.class, RestClientConfig.class})) 
  5. @MapperScan("com.example.mockito.demo.mapper"
  6. public class TestApplication { 
  7.  

為了不單獨寫重復的代碼,我們一般會把單獨的代碼抽取出來作為一個公共基類,其中 @ExtendWith(SpringExtension.class) 注解目的是告訴 Spring boot 將使用 Junit5 作為運行平臺,如果想買中使用 Junit4 的話,則需要使用 @RunWith(SpringRunner.class) 注解告知用 SpringRunner 運行啟動。

  1. @SpringBootTest(classes = TestApplication.class)@ExtendWith(SpringExtension.class) 
  2. public abstract class SpringBaseTest {} 

準備好配置環境后,我們便可以開始對項目的 Mapper、Service、Web 層進行測試了。

Mapper層測試

對 Mapper 層的測試主要是驗證 SQL 語句及 Mybatis 傳參等準確性。

  1. server: 
  2.   port: 8080 
  3. spring: 
  4.   test: 
  5.     context: 
  6.       cache: 
  7.         max-size: 42 
  8.   main: 
  9.     allow-bean-definition-overriding: true 
  10.   datasource: 
  11.     url: jdbc:h2:mem:test;MODE=MYSQL;DB_CLOSE_DELAY=-1;INIT=runscript from 'classpath:init.sql' 
  12.     username: sa 
  13.     password
  14.     driverClassName: org.h2.Driver 
  15.     hikari: 
  16.       minimum-idle: 5 
  17.       maximum-pool-size: 15 
  18.       auto-committrue 
  19.       idle-timeout: 30000 
  20.       pool-name: DatebookHikariCP 
  21.       max-lifetime: 1800000 
  22.       connection-timeout: 10000 
  23.       connection-test-query: SELECT 1 
  24.  
  25. mybatis: 
  26.   type-aliases-package: com.example.mockito.demo.domain 
  27.   mapper-locations: 
  28.     - classpath:mapper/*.xml 

對 Mapper 層的測試并沒有采取 Mock 的方式,而是采用 H2 內存數據庫的方式模擬真實數據庫,同時也避免由于測試數據給真實數據庫帶來的影響。

  1. jdbc:h2:mem:test;MODE=MYSQL;DB_CLOSE_DELAY=-1;INIT=runscript from 'classpath:init.sql' 

配置 H2 數據庫信息,同時 INIT 指定在創建連接時會執行類路徑下的 init.sql 即建表 SQL 。

  1. public class DemoMapperTest extends SpringBaseTest { 
  2.  
  3.     @Resource 
  4.     private DemoMapper demoMapper; 
  5.  
  6.     @Test 
  7.     public void testInsert() { 
  8.         Demo demo = new Demo(); 
  9.         demo.setName("test"); 
  10.         demoMapper.insert(demo); 
  11.  
  12.         Integer id = demo.getId(); 
  13.         Demo model = demoMapper.getDetail(id); 
  14.         Assert.assertNotNull(model); 
  15.         Assert.assertEquals(demo.getName(), model.getName()); 
  16.     } 
  17.  
  18.     @Test 
  19.     public void testGetList() { 
  20.         Demo demo = new Demo(); 
  21.         demo.setName("test"); 
  22.         demoMapper.insert(demo); 
  23.  
  24.         List<Demo> demoList = demoMapper.getList(); 
  25.         Assert.assertNotNull(demoList); 
  26.         Assert.assertEquals(1, demoList.size()); 
  27.     } 

Service層測試

一般項目的業務邏輯寫在 service 層,需要寫更多的測試用例驗證業務代碼邏輯性及準確性,盡可能的覆蓋到業務代碼的分支邏輯。

  1. public class DemoServiceTest extends SpringBaseTest { 
  2.  
  3.   @Resource 
  4.   private DemoService demoService; 
  5.   @Resource 
  6.   private RedissonClient redissonClient; 
  7.  
  8.   @Resource 
  9.   private RestTemplate restTemplate; 
  10.  
  11.   @BeforeEach 
  12.   public void setUp() { 
  13.       MockitoAnnotations.openMocks(this); 
  14.   } 
  15.  
  16.   @Test 
  17.   public void testGetList() { 
  18.       //測試第一個分支邏輯 
  19.       RAtomicLong rAtomicLong = Mockito.mock(RAtomicLong.class); 
  20.       Mockito.when(redissonClient.getAtomicLong(ArgumentMatchers.anyString())).thenReturn(rAtomicLong); 
  21.       long count = 4L; 
  22.       Mockito.when(rAtomicLong.incrementAndGet()).thenReturn(count); 
  23.       List<Demo> demoList = demoService.getList(); 
  24.       Assert.assertTrue(demoList != null && demoList.size() == 1); 
  25.       Demo demo = demoList.get(0); 
  26.       Assert.assertNotNull(demo); 
  27.       Assert.assertEquals(Integer.valueOf(4), demo.getId()); 
  28.       Assert.assertEquals("testCount4", demo.getName()); 
  29.  
  30.       //測試第二個分支邏輯 
  31.       Mockito.when(redissonClient.getAtomicLong(ArgumentMatchers.anyString())).thenReturn(rAtomicLong); 
  32.       count = 1L; 
  33.       Mockito.when(rAtomicLong.incrementAndGet()).thenReturn(count); 
  34.  
  35.       MockedStatic<AESUtil> aesUtilMockedStatic = Mockito.mockStatic(AESUtil.class); 
  36.       aesUtilMockedStatic.when(() -> AESUtil.encrypt(ArgumentMatchers.eq("test"), ArgumentMatchers.eq("1234567890123456"))) 
  37.               .thenReturn("demo"); 
  38.  
  39.       demoList = demoService.getList(); 
  40.       Assert.assertTrue(demoList != null && demoList.size() == 1); 
  41.       Demo encryptDemo = demoList.get(0); 
  42.       Assert.assertNotNull(encryptDemo); 
  43.       Assert.assertEquals(Integer.valueOf(1), encryptDemo.getId()); 
  44.       Assert.assertEquals("testEncrypt", encryptDemo.getName()); 
  45.  
  46.       //測試第三個分支邏輯 
  47.       Mockito.when(redissonClient.getAtomicLong(ArgumentMatchers.anyString())).thenReturn(rAtomicLong); 
  48.       count = 1L; 
  49.       Mockito.when(rAtomicLong.incrementAndGet()).thenReturn(count); 
  50.  
  51.       //執行真實方法 
  52.       aesUtilMockedStatic.when(() -> AESUtil.encrypt(ArgumentMatchers.eq("test"), ArgumentMatchers.eq("1234567890123456"))) 
  53.               .thenCallRealMethod(); 
  54.  
  55.       String mobileUrl = "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel="
  56.       MobileInfoDTO mobileInfoDTO = new MobileInfoDTO(); 
  57.       mobileInfoDTO.setName("testMobile"); 
  58.       mobileInfoDTO.setLocation("testLocation"); 
  59.       Mockito.when(restTemplate.getForObject(mobileUrl, MobileInfoDTO.class)).thenReturn(mobileInfoDTO); 
  60.       demoList = demoService.getList(); 
  61.       Assert.assertNotNull(demoList); 
  62.       Assert.assertEquals(1, demoList.size()); 
  63.       Demo demo1 = demoList.get(0); 
  64.       Assert.assertNotNull(demo1); 
  65.       Assert.assertEquals(mobileInfoDTO.getName(), demo1.getName()); 
  66.     } 

WEB層測試

  1. public class DemoControllerTest extends SpringBaseTest { 
  2.  
  3.   private MockMvc mockMvc; 
  4.  
  5.   @Mock 
  6.   private DemoService demoService; 
  7.  
  8.   //把demoService注入到demoController中 
  9.   @InjectMocks 
  10.   private DemoController demoController; 
  11.  
  12.   @BeforeEach 
  13.   public void setUp() { 
  14.       MockitoAnnotations.openMocks(this); 
  15.       //手動構建,不使用@AutoConfigureMockMvc注解避免重復啟動spring上下文 
  16.       this.mockMvc = MockMvcBuilders.standaloneSetup(demoController).build(); 
  17.   } 
  18.  
  19.   @Test 
  20.   public void addDemoTest() throws Exception { 
  21.       String name = "mock demo add"
  22.       mockMvc.perform(MockMvcRequestBuilders.post("/demo"
  23.               .param("name"name)) 
  24.               .andExpect(MockMvcResultMatchers.status().isOk()) 
  25.               .andExpect(MockMvcResultMatchers.content().string(containsString("OK"))) 
  26.               .andDo(MockMvcResultHandlers.print()); 
  27.       //驗證調用次數,發生過一次 
  28.       Mockito.verify(demoService, Mockito.times(1)).addDemo(ArgumentMatchers.any()); 
  29.   } 
  30.  
  31.   @Test 
  32.   public void getDemoListTest() throws Exception { 
  33.       mockMvc.perform(MockMvcRequestBuilders.get("/demos")) 
  34.               .andExpect(MockMvcResultMatchers.status().isOk()) 
  35.               .andExpect(MockMvcResultMatchers.content().string("[]")) 
  36.               .andDo(MockMvcResultHandlers.print()); 
  37.       //驗證調用次數,發生過一次 
  38.       Mockito.verify(demoService, Mockito.times(1)).getList(); 
  39.   } 
  40.  
  41.   @Test 
  42.   public void getDemoDetailTest() throws Exception { 
  43.       String name = "mock demo getDetail"
  44.       mockMvc.perform(MockMvcRequestBuilders.get("/demo/{id}", 1)) 
  45.               .andExpect(MockMvcResultMatchers.status().isOk()) 
  46.               .andExpect(MockMvcResultMatchers.content().string(containsString(""))) 
  47.               .andDo(MockMvcResultHandlers.print()); 
  48.       //驗證調用次數,發生過一次 
  49.       Mockito.verify(demoService, Mockito.times(1)).getDetail(ArgumentMatchers.anyInt()); 
  50.   } 

套件測試

當寫完 mapper、service、web 層的單元測試后,我們便可以把這些單元測試類都打包到套件中,并在 maven surefire 插件指定需要執行的套件類,我們可以使用 @SelectPackages 或 @SelectClass 注解把要測試的類包含進來。

  1. @RunWith(JUnitPlatform.class) 
  2. @SelectPackages("com.example.mockito.demo"
  3. public class SuiteTest { 

把測試套件配置到surefire插件中。

  1. <plugin> 
  2.     <groupId>org.apache.maven.plugins</groupId> 
  3.     <artifactId>maven-surefire-plugin</artifactId> 
  4.     <version>${maven-surefire.version}</version> 
  5.     <executions> 
  6.         <!--指定在mvn的test階段執行此插件 --> 
  7.         <execution> 
  8.             <id>test</id> 
  9.             <goals> 
  10.                 <goal>test</goal> 
  11.             </goals> 
  12.         </execution> 
  13.     </executions> 
  14.     <configuration> 
  15.         <forkMode>once</forkMode> 
  16.         <skip>false</skip> 
  17.         <includes> 
  18.             <include>**/SuiteTest.java</include> 
  19.         </includes> 
  20.     </configuration> 
  21. </plugin> 

 

 

 

 

生成覆蓋率報告

  1. <plugin> 
  2.     <groupId>org.jacoco</groupId> 
  3.     <artifactId>jacoco-maven-plugin</artifactId> 
  4.     <version>${jacoco.version}</version> 
  5.     <executions> 
  6.         <execution> 
  7.             <goals> 
  8.                 <goal>prepare-agent</goal> 
  9.             </goals> 
  10.         </execution> 
  11.         <!-- 綁定到test階段 --> 
  12.         <execution> 
  13.             <id>report</id> 
  14.             <phase>test</phase> 
  15.             <goals> 
  16.                 <goal>report</goal> 
  17.             </goals> 
  18.         </execution> 
  19.     </executions> 
  20. </plugin> 

項目中使用 jacoco 作為代碼覆蓋率工具,在命令行中運行 mvn clean test 后會執行所有單元測試用例,隨后會在 target 目錄下生成site 文件夾,文件夾包含 jacoco 插件生成的測試報告。

報告中主要包含本次測試中涉及到的類、方法、分支覆蓋率,其中紅色的表示未被覆蓋到,綠色表示全覆蓋,黃色的則表示部分覆蓋到,可點擊某個包或某個類查看具體哪些行未被覆蓋等。

注意:

  • test 測試目錄與 main 開發目錄的資源是相互隔離的。
  • 使用 @MockBean 會導致啟動 Spring 上下文多次,影響測試效率。
  • 使用 @AutoConfigureMockMvc 注解導致 Spring 上下文多次重啟,建議使用MockMvcBuilders.standaloneSetup(demoController).build() 構建。

言而總之

雖然編寫單元測試會帶來一定的工作量,但通過使用 Mockito 不僅可以保留測試用例,還可以快速驗證改動后的代碼邏輯,對復雜或依賴中間件多的項目而言,使用 Mockito 的優勢會更加明顯。

除此之外,更加重要的是我們自己可以創建條件來模擬各種情景下代碼的邏輯準確性,保證代碼的質量,提高代碼維護性、提前發現潛在的問題,不再因為賬號問題等導致測不到某些邏輯代碼,使得項目上線也心里有底。

引用

[1]Mockito2.7.2 及更高版本添加了對 final 類及方法支持: https://www.baeldung.com/mockito-final

 

[2]Mockito3.4.0 及更高版本支持對靜態方法的 Mock: https://tech.cognifide.com/blog/2020/mocking-static-methods-made-possible-in-mockito-3.4.0

 

責任編輯:武曉燕 來源: 碼農私房話
相關推薦

2020-09-30 08:08:15

單元測試應用

2022-08-26 08:53:46

單元測試校驗框架

2017-01-14 23:42:49

單元測試框架軟件測試

2020-09-11 16:00:40

Bash單元測試

2021-10-12 19:16:26

Jest單元測試

2011-04-18 13:20:40

單元測試軟件測試

2024-01-09 08:08:12

Go單元測試系統

2024-08-21 08:22:33

2021-03-28 23:03:50

Python程序員編碼

2022-03-15 11:55:24

前端單元測試

2025-08-28 01:00:00

Go單元測試

2017-01-16 12:12:29

單元測試JUnit

2017-01-14 23:26:17

單元測試JUnit測試

2020-07-07 07:33:12

Java單元集成

2020-08-18 08:10:02

單元測試Java

2017-03-23 16:02:10

Mock技術單元測試

2021-05-05 11:38:40

TestNGPowerMock單元測試

2021-03-11 12:33:50

JavaPowerMock技巧

2023-07-28 10:27:48

Java單元測試

2023-07-26 08:58:45

Golang單元測試
點贊
收藏

51CTO技術棧公眾號

中文字幕亚洲无线码a| 欧美调教femdomvk| 国产高清一区视频| 香蕉影院在线观看| 国产精品99久久久久久动医院| 欧美一级一区二区| 99福利在线观看| 黄色网址在线免费| 91日韩精品一区| 成人激情春色网| 日本三级小视频| 久久久久久久久久久久久久| 日韩精品在线看| 原创真实夫妻啪啪av| 成人性生活视频| 亚洲精品国产一区二区精华液| 欧美久久久久久| 狠狠躁夜夜躁av无码中文幕| 青娱乐精品视频| 国内精品400部情侣激情| 老司机精品免费视频| 91蝌蚪精品视频| 欧美精品在线观看播放| 国产av天堂无码一区二区三区| 一广人看www在线观看免费视频| 成人中文字幕电影| 91人成网站www| 亚洲色成人www永久网站| 亚洲免费激情| 欧美黑人一级爽快片淫片高清| 亚洲天堂av中文字幕| 日本福利一区| 亚洲成人精品av| 少妇愉情理伦片bd| 高清久久一区| 欧美日韩国产欧美日美国产精品| 成人免费在线小视频| 特级毛片在线| 亚洲女人的天堂| 伊人av成人| 99re在线视频| 国产欧美日韩一区二区三区在线观看| 久久久久久久久久久久久久一区| 囯产精品久久久久久| 国产精品一区二区在线看| 国产在线高清精品| 中国女人一级一次看片| 日本亚洲三级在线| 国产91九色视频| 天堂网视频在线| 久久久久久久尹人综合网亚洲 | 3d成人动漫在线| 久久久久九九视频| 欧美日韩精品久久久免费观看| 少妇性bbb搡bbb爽爽爽欧美| www.久久精品| 欧美精品一区二区三区久久| 国产一级在线| 国产精品久久网站| 亚洲最新免费视频| 国内外激情在线| 亚洲综合色婷婷| 亚洲色成人www永久在线观看| 牛牛在线精品视频| 亚洲成年人网站在线观看| 国产美女在线一区| 九色porny自拍视频在线播放| 岛国精品视频在线播放| 已婚少妇美妙人妻系列| 日韩制服一区| 5858s免费视频成人| 中文字幕第六页| 福利欧美精品在线| 国产婷婷97碰碰久久人人蜜臀| 日韩精品无码一区二区三区久久久 | 欧美亚韩一区| 欧美在线观看网站| 少妇无套内谢久久久久| 久久66热偷产精品| 99re视频在线| 欧美高清电影在线| 中文字幕中文字幕一区二区| 男人天堂a在线| 黄色亚洲网站| 91精品国产欧美日韩| 欧美xxxxx精品| 国内黄色精品| 欧美美女18p| jizz国产在线观看| 精品一区二区三区免费毛片爱| 国产高清在线一区| yes4444视频在线观看| 樱桃视频在线观看一区| 日韩欧美亚洲天堂| 日本免费成人| 亚洲精品成人网| 日本成人免费在线观看| 国产一区日韩欧美| 国产精品福利在线| 国内毛片毛片毛片毛片| 久久久久高清精品| 无码毛片aaa在线| 日韩精品专区| 日韩欧美精品在线| 国产精品酒店视频| 日韩天天综合| 91亚色免费| 99青草视频在线播放视| 精品人伦一区二区三区蜜桃免费| 欧美大片久久久| 美女网站一区| 欧美精品久久久久久久| 国产一区二区三区成人| 久久毛片高清国产| 欧美日韩福利在线| 久久久久久久久久久久电影| 亚洲午夜久久久影院| 男女啊啊啊视频| 国产不卡在线视频| 中文字幕精品一区日韩| 久久夜夜操妹子| 日韩精品在线视频美女| 国产亚洲精品成人| 国产一区二区三区美女| 水蜜桃一区二区| 免费亚洲电影| 日韩av网址在线| 国产一卡二卡在线播放| 国产精品乡下勾搭老头1| 日韩欧美亚洲区| 亚洲播播91| 亚洲欧洲一区二区三区在线观看 | 无码av免费一区二区三区试看| 一二三av在线| 99久久婷婷这里只有精品 | 久久视频在线看| 日批视频免费观看| 久久久国产精品麻豆| www.浪潮av.com| 国产精品qvod| 高清欧美一区二区三区| 亚洲精品久久久狠狠狠爱| 亚洲色图欧美在线| 日韩av片免费观看| 偷偷www综合久久久久久久| 国产精品吴梦梦| 天堂аⅴ在线地址8| 欧美日韩久久久一区| 特黄一区二区三区| 麻豆freexxxx性91精品| 亚洲一区二区精品在线观看| 高清欧美日韩| 久久精品国产清自在天天线| 国产精品爽爽久久久久久| 中文字幕一区在线| aaa一级黄色片| 欧美人成网站| 久久久久综合一区二区三区| 成人美女视频| 中文日韩在线视频| 91亚洲精品国偷拍自产在线观看 | 国产午夜精品一区二区三区| 99久久久久久久久| 欧美韩日一区二区三区四区| 无尽裸体动漫2d在线观看| 国产精品二区不卡| 成人av男人的天堂| 黄色在线网站噜噜噜| 亚洲欧美三级伦理| 免费看av在线| 一区二区三区在线视频观看58 | 88久久精品| 91精品国产沙发| 国产精品视频一区二区久久| 欧美日韩成人一区二区| 久久久久久久久久网站| 99亚偷拍自图区亚洲| 美女一区二区三区视频| 91精品国产视频| 久久99影院| 欧美videos粗暴| 欧美高清电影在线看| 日本a一级在线免费播放| 欧美丝袜第三区| 青青草手机视频在线观看| av电影在线观看完整版一区二区| 亚洲黄色av网址| 欧美日韩精品一本二本三本 | 91在线视频| 精品美女一区二区| 波多野结衣电车痴汉| 亚洲女子a中天字幕| 特级西西人体4444xxxx| 理论片日本一区| 久久久久免费看黄a片app| 欧美综合在线视频观看| 国产精品加勒比| 玖玖精品在线| 91chinesevideo永久地址| 欧美成人高清在线| 日韩精品在线免费播放| aaa级黄色片| 欧美性色欧美a在线播放| 国产极品美女高潮无套嗷嗷叫酒店| 国产人成亚洲第一网站在线播放| 无码国产精品一区二区免费式直播 | 国产欧美日韩另类视频免费观看 | 日韩中文字幕视频网| 日本精品视频在线观看| 国产乱妇乱子在线播视频播放网站| 一区二区三区黄色| 五月婷婷六月丁香| 欧美一区二区视频在线观看| 波多野结衣高清在线| 精品国产福利在线| 永久看片925tv| 国产精品免费人成网站| 菠萝菠萝蜜网站| 成人一道本在线| 国产精品二区视频| 美国十次了思思久久精品导航| 无码aⅴ精品一区二区三区浪潮| 韩日精品视频| 成人国产在线看| 99久久夜色精品国产亚洲1000部| 日本一区二区三区视频在线播放| 任你躁在线精品免费| 成人9ⅰ免费影视网站| 国产午夜精品一区在线观看| 国产一区二区在线免费视频| 国产成人精品123区免费视频| 4k岛国日韩精品**专区| 狂野欧美性猛交xxxxx视频| 久久视频在线观看免费| 日本视频在线免费观看| 中文字幕亚洲欧美| 成年人在线视频免费观看| 亚洲天堂男人天堂女人天堂| 日韩欧美电影在线观看| 日韩av一区在线观看| 天堂在线观看免费视频| 亚洲国产精品久久久久秋霞不卡 | 男人添女荫道口图片| 亚洲性感美女99在线| 天天做天天躁天天躁| 国产精品v亚洲精品v日韩精品 | 欧美一区二区三区男人的天堂| 一级全黄裸体免费视频| 欧美日韩精品福利| 国产又大又长又粗| 欧美久久久影院| av网站免费播放| 欧美哺乳videos| 狠狠综合久久av一区二区| 精品不卡在线视频| 污污视频在线免费看| 亚洲老司机av| eeuss影院www在线播放| 波霸ol色综合久久| 91中文在线| 亚洲18私人小影院| 欧美黑人疯狂性受xxxxx野外| 国产福利视频一区二区| 精品美女一区| 成人18视频| 九九久久电影| 亚洲永久激情精品| 亚洲女同另类| 久操网在线观看| 视频一区二区欧美| 香港日本韩国三级网站| 国产精品综合一区二区三区| 国产精品一区二区在线免费观看| caoporn国产精品| 国产成人av一区二区三区不卡| 欧美国产1区2区| 欧美日韩成人免费观看| 欧美视频13p| 国产精品久久久久久久一区二区 | 麻豆av电影在线观看| 视频在线观看99| 成人性生交大片免费看在线播放| 欧美最近摘花xxxx摘花| 国产区一区二| 久久影视中文粉嫩av| 91精品国产乱码久久久久久| 成人毛片一区二区| 美女久久久精品| 人妻体内射精一区二区三区| 中文字幕二三区不卡| 日韩激情一区二区三区| 欧美日韩国产一二三| 五月婷婷丁香六月| 另类色图亚洲色图| 欧洲亚洲两性| 粉嫩高清一区二区三区精品视频| 国产不卡av一区二区| 青青青青在线视频| 久热成人在线视频| 日本xxxx裸体xxxx| 亚洲日本护士毛茸茸| 99re这里只有精品在线| 日韩精品一区二区三区四区| av电影在线观看网址| 亚洲2020天天堂在线观看| 亚洲三级在线| 日本福利一区二区三区| 狠狠综合久久| 国产aⅴ爽av久久久久| 久久美女艺术照精彩视频福利播放| 欧美日韩在线国产| 欧美日韩一区不卡| 欧美69xxxxx| 国内成人精品一区| 日韩欧美高清一区二区三区| 亚洲午夜在线观看| 丝袜诱惑制服诱惑色一区在线观看 | 国产精品亚洲一区二区在线观看| 欧美亚州在线观看| 国产精品尤物| 国产原创剧情av| 一区二区三区免费在线观看| 在线免费看毛片| 亚洲天堂影视av| 久久精品女人天堂av免费观看| 国产三级精品在线不卡| 韩国亚洲精品| 波多野结衣办公室双飞| 一区二区三区精品在线| 国产特级黄色片| 久久久精品免费视频| 国产亚洲欧美日韩精品一区二区三区| 欧美日韩一区综合| 久久精品中文| 国产成人无码精品久久二区三| 婷婷综合在线观看| 天堂中文字幕av| 26uuu久久噜噜噜噜| 日日天天久久| 日韩精品视频一区二区在线观看| 99久久精品免费看| 午夜毛片在线观看| 亚洲精品午夜精品| 欧美在线观看黄| 成人福利片网站| 成人中文字幕+乱码+中文字幕| 久久视频国产| 精品综合久久久久| 国产精品美女久久久久aⅴ| 天堂av免费在线观看| 在线看日韩欧美| 日本欧美在线| 99re8这里只有精品| 国产精品综合视频| 久久精品视频9| 日韩av中文字幕在线播放| 在线一区av| 日韩视频专区| 激情综合网av| 精品无码一区二区三区电影桃花| 亚洲精品一区二区三区99| 九色porny丨国产首页在线| 欧美日韩在线精品| 久久成人麻豆午夜电影| 精品99久久久久成人网站免费 | 亚瑟在线精品视频| 天堂中文在线资| 欧美一级大胆视频| 成人在线免费视频观看| 欧美日韩理论片| 亚洲成人动漫一区| 国产女人在线视频| 91美女片黄在线观看游戏| 国产精品a久久久久| 久久亚洲AV成人无码国产野外 | 日本特黄a级片| 亚洲综合视频在线观看| 日韩欧美电影在线观看| 成人国产精品久久久| 亚洲图片在线| 免费看黄色av| 日韩精品中文字幕在线一区| 亚洲国产欧美日本视频| 熟女视频一区二区三区| av中文字幕一区| 一级做a爱片久久毛片| 久久久久成人网| 成人影视亚洲图片在线| 日韩黄色一区二区| 欧美亚洲禁片免费| 欧美高清另类hdvideosexjaⅴ| 欧美性xxxx69| 成人久久视频在线观看| 国产精品成人无码| 国内外成人免费激情在线视频网站 | 国产免费xxx| 久久久影视传媒| 午夜精品久久久久久久99热黄桃| 清纯唯美日韩制服另类| 伊人久久大香线蕉精品组织观看|