自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用 PowerMock 寫單元測試,被坑慘了!

開發(fā) 前端
PowerMock 寫單測對(duì)開發(fā)人員來說確實(shí)很方便,但是如果工程中的代碼量比較大,團(tuán)隊(duì)又要求單測覆蓋率高,那單測類的數(shù)量確實(shí)會(huì)很多,最終結(jié)果就是單測耗時(shí)時(shí)間很長。這種情況并不適合使用 PowerMock 框架。

大家好,我是君哥。

最近在工作中遇到一個(gè)不太好解決的問題,我負(fù)責(zé)的系統(tǒng)單元測試跑的非常慢,有時(shí)候甚至超過 2 個(gè)半小時(shí)。

公司要求上線前流水線里面的單測必須全部跑成功。跑流水線的時(shí)候如果有單測跑失敗,需要修改后重新跑,又得跑 2 個(gè)多小時(shí)。極端情況下得反反復(fù)復(fù)來幾次,真的讓人感到煎熬。有時(shí)候發(fā)現(xiàn)測試用例跑失敗的原因竟然是 OOM。

今天就來聊一聊造成單測跑的慢的罪魁禍?zhǔn)?,PowerMock。

1.PowerMock 基礎(chǔ)

要說 PowerMock 怎么樣,那是真的非常好用。下面列給出幾個(gè)示例,先上一段業(yè)務(wù)代碼,然后我們通過 3 個(gè)測試用例把這段代碼單測覆蓋率寫到 100%。

1  public class FileParser {
2  
3      private Logger logger = LoggerFactory.getLogger(getClass());
4  
5      @Resource
6      private UserRepository userRepository;
7  
8      public void parseFile(String fileName) {
9          File file = new File(fileName);
10          if (!file.exists()){
11              return;
12          }
13          try {
14              BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
15              String line = null;
16              while ((line = bufferedReader.readLine())!= null){
17                  User user = userRepository.getUser(line);
18                  logger.info("user with name{}:{}", line, user);
19              }
20         }catch (IOException e){
21             throw new RuntimeException(e);
22         }
23     }
24 }

這段代碼涉及到讀文件、依賴注入、異常處理,我們寫單測也從這三個(gè)方面來完成。

1.1 文件不存在

我們先來模擬一下文件不存在,這個(gè)用例覆蓋到上面文件不存在的判斷。測試用例如下 :

@Test
public void testParseFile_not_exists() throws Exception {
 File file = PowerMockito.mock(File.class);
 PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(file);
 when(file.exists()).thenReturn(false);
 fileParser.parseFile("123");
 Mockito.verify(userRepository, Mockito.times(0)).getUser(anyString());
}

這里使用 PowerMock 方便地模擬了第 11 行代碼文件不存在,用例成功。

1.2 循環(huán)跳出

這段用例要模擬按行讀文件、dao 層查詢用戶、跳出循環(huán)這三個(gè)代碼,測試用例代碼如下:

@Test
public void testParseFile_exists() throws Exception {
 File file = PowerMockito.mock(File.class);
 PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(file);
 when(file.exists()).thenReturn(true);

 FileInputStream fileInputStream = PowerMockito.mock(FileInputStream.class);
 PowerMockito.whenNew(FileInputStream.class).withAnyArguments().thenReturn(fileInputStream);

 InputStreamReader inputStreamReader = PowerMockito.mock(InputStreamReader.class);
 PowerMockito.whenNew(InputStreamReader.class).withAnyArguments().thenReturn(inputStreamReader);

 BufferedReader bufferedReader = PowerMockito.mock(BufferedReader.class);
 PowerMockito.whenNew(BufferedReader.class).withAnyArguments().thenReturn(bufferedReader);

 //模擬循環(huán)和跳出
 when(bufferedReader.readLine()).thenReturn("testUser").thenReturn("user").thenReturn(null);
 User user = PowerMockito.mock(User.class);
 when(userRepository.getUser(anyString())).thenReturn(user);

 fileParser.parseFile("123");

 Mockito.verify(userRepository, Mockito.times(1)).getUser(anyString());
}

這段用例跑完后,已經(jīng)覆蓋到源代碼的第 17行和 19 行。

1.3 模擬異常

源代碼中有一個(gè)異常處理,用例要達(dá)到 100% 覆蓋,必須把這個(gè)異常用測試用例模擬出來。下面看一下測試用例:

@Test(expected = RuntimeException.class)
public void testParseFile_exception() throws Exception {
    File file = PowerMockito.mock(File.class);
    PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(file);
    when(file.exists()).thenReturn(true);

    FileInputStream fileInputStream = PowerMockito.mock(FileInputStream.class);
    PowerMockito.whenNew(FileInputStream.class).withAnyArguments().thenReturn(fileInputStream);

    InputStreamReader inputStreamReader = PowerMockito.mock(InputStreamReader.class);
    PowerMockito.whenNew(InputStreamReader.class).withAnyArguments().thenReturn(inputStreamReader);

    BufferedReader bufferedReader = PowerMockito.mock(BufferedReader.class);
    PowerMockito.whenNew(BufferedReader.class).withAnyArguments().thenReturn(bufferedReader);

    //模擬拋出異常
    when(bufferedReader.readLine()).thenThrow(new IOException());

    fileParser.parseFile("123");
}

至此,單測覆蓋率達(dá)到 100%。

2.PowerMock 進(jìn)階

下面再來使用幾個(gè) PowerMock 的功能。再來一段示例代碼:

1   public void parseFileWithScanner(String fileName) {
2    File file = new File(fileName);
3    if (!file.exists()){
4     return;
5    }
6    try {
7     Scanner scanner = new Scanner(file);
8     String line = null;
9     while (scanner.hasNextLine()){
10     line = scanner.nextLine();
11     if (StringUtils.equals(line, "testUser")){
12      User user = userRepository.getUser(line);
13      logger.info("user with name{}:{}", line, user);
14     }
15    }
16   }catch (IOException e){
17    throw new RuntimeException(e);
18   }
19  }

這次我們也要增加 2 個(gè)用例的 mock,一個(gè)是 Scanner 這個(gè) final 類,第二個(gè)是 StringUtils 這個(gè)靜態(tài)類。

2.1 final 類

雖然是一個(gè) final 類,但使用了 PowerMock 框架,我們就像普通類一樣就可以用例。

@Test
public void testParseFile_scanner() throws Exception {
 File file = PowerMockito.mock(File.class);
 PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(file);
 when(file.exists()).thenReturn(true);

 Scanner scanner = PowerMockito.mock(Scanner.class);
 PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner);

 //模擬循環(huán)
 when(scanner.hasNextLine()).thenReturn(true).thenReturn(true).thenReturn(false);
 when(scanner.nextLine()).thenReturn("testUser").thenReturn("user");

 User user = PowerMockito.mock(User.class);
 when(userRepository.getUser(anyString())).thenReturn(user);

 fileParser.parseFileWithScanner("123");

 Mockito.verify(userRepository, Mockito.times(1)).getUser(anyString());
}

除了 final 類,抽象類、接口都可以 mock,確實(shí)很方便。

2.2 靜態(tài)類

PowerMock 可以方便地模擬靜態(tài)類,下面這個(gè)測試用例對(duì) StringUtils 這個(gè)靜態(tài)類進(jìn)行了 mock,每次 equals 方法都是返回 false。

@Test
public void testParseFile_StringUtils() throws Exception {
 File file = PowerMockito.mock(File.class);
 PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(file);
 when(file.exists()).thenReturn(true);

 Scanner scanner = PowerMockito.mock(Scanner.class);
 PowerMockito.whenNew(Scanner.class).withAnyArguments().thenReturn(scanner);

 //模擬循環(huán)
 when(scanner.hasNextLine()).thenReturn(true).thenReturn(true).thenReturn(false);
 when(scanner.nextLine()).thenReturn("testUser").thenReturn("user");
 when(StringUtils.equals(anyString(), anyString())).thenReturn(false).thenReturn(false);
 User user = PowerMockito.mock(User.class);
 when(userRepository.getUser(anyString())).thenReturn(user);

 fileParser.parseFileWithScanner("123");

 Mockito.verify(userRepository, Mockito.times(0)).getUser(anyString());
}

因?yàn)?equals 方法一直返回 false,所以 getUser 方法沒有執(zhí)行到,測試用例中 verify getUser 方法被調(diào)用 0 次。需要注意的是,模擬靜態(tài)類需要在類定義上面加上一個(gè)注解,然后對(duì)靜態(tài)類要做一次 mockStatic??聪旅娴?@Before 注解。

@RunWith(PowerMockRunner.class)
@PrepareForTest({FileParser.class, StringUtils.class})
public class FileParserTest {

@Before
public void before(){
 PowerMockito.mockStatic(StringUtils.class);
}

3.原因分析

PowerMock 因?yàn)槭褂昧?@PrepareForTest、@PowerMockIgnore、@SuppressStaticInitialzationFor 這三個(gè)注解,這三個(gè)注解的參數(shù)值不一樣,會(huì)導(dǎo)致每個(gè)單測類執(zhí)行的時(shí)候不能復(fù)用公有類加載器,而是需要?jiǎng)?chuàng)建一個(gè)自己獨(dú)有的類加載器。這導(dǎo)致類加載過程十分耗時(shí)。

在單測類數(shù)量比較少的情況下,單測耗時(shí)問題是不會(huì)出現(xiàn)的,但是如果一個(gè)工程中的單測類數(shù)據(jù)猛增,比如我們的單測類在 600+,問題就暴露出來的。最難的是不太好做優(yōu)化,因?yàn)槿绻サ?PowerMock 框架,要改造的東西太多了。

4.最后

PowerMock 寫單測對(duì)開發(fā)人員來說確實(shí)很方便,但是如果工程中的代碼量比較大,團(tuán)隊(duì)又要求單測覆蓋率高,那單測類的數(shù)量確實(shí)會(huì)很多,最終結(jié)果就是單測耗時(shí)時(shí)間很長。這種情況并不適合使用 PowerMock 框架。

圖片圖片

同時(shí)我們也要看到,PowerMock 最近一次核心代碼更新已經(jīng)是 4 年前了,單測類數(shù)據(jù)量多導(dǎo)致的內(nèi)存問題、耗時(shí)問題并沒有解決。所以選型的時(shí)候一定要慎重。

責(zé)任編輯:武曉燕 來源: 君哥聊技術(shù)
相關(guān)推薦

2021-05-05 11:38:40

TestNGPowerMock單元測試

2021-03-11 12:33:50

JavaPowerMock技巧

2020-03-20 08:00:32

代碼程序員追求

2017-01-14 23:26:17

單元測試JUnit測試

2017-01-16 12:12:29

單元測試JUnit

2020-09-11 16:00:40

Bash單元測試

2021-10-12 19:16:26

Jest單元測試

2017-01-14 23:42:49

單元測試框架軟件測試

2021-07-16 07:57:35

SpringBootOpenFeign微服務(wù)

2017-03-23 16:02:10

Mock技術(shù)單元測試

2023-07-26 08:58:45

Golang單元測試

2012-05-17 09:09:05

Titanium單元測試

2013-06-04 09:49:04

Spring單元測試軟件測試

2025-04-22 03:00:00

模型SpringAI

2024-10-16 16:09:32

2021-03-28 23:03:50

Python程序員編碼

2010-03-04 15:40:14

Python單元測試

2020-08-18 08:10:02

單元測試Java

2019-01-29 09:00:44

PyHamcrest單元測試框架

2021-06-15 08:08:47

Java單元測試
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)