PowerMock(一):PowerMock的基本使用

文章目錄

    為啥要使用PowerMock
    PowerMock的使用
        環(huán)境
        引入依賴
        注解說明
        mock普通方法
        mock拋出異常
        mock新建對(duì)象
        mock無返回值的方法
        mock被final修飾的方法
        參數(shù)模糊匹配
        mock靜態(tài)方法
        mock私有方法
        總結(jié)
        參考

為啥要使用PowerMock

現(xiàn)在流行的測試驅(qū)動(dòng)開發(fā)TDD(Test-Driven Development) ,是敏捷開發(fā)中一項(xiàng)核心實(shí)踐和技術(shù)。也是一種設(shè)計(jì)方法論。其中最重要的一環(huán)就是使用單元測試。
單元測試是保證代碼質(zhì)量的一個(gè)重要手段,通過單元測試我們可以快速的測試代碼的各個(gè)分支,各種場景,代碼重構(gòu)時(shí)只需要重新跑下單元測試就是能知道代碼潛在的問題。
單元測試是通過Mock的方式調(diào)用被測試的方法,其有如下幾個(gè)優(yōu)點(diǎn):

    Mock可以解除測試對(duì)象對(duì)外部服務(wù)的依賴(比如數(shù)據(jù)庫,第三方接口等),使得測試用例可以獨(dú)立運(yùn)行。不管是單體應(yīng)用還是微服務(wù),這點(diǎn)都特別重要。
    Mock的第二個(gè)好處就是替換外部服務(wù)調(diào)用,提升測試用例的運(yùn)行速度。因?yàn)槿魏瓮獠糠?wù)調(diào)用至少是跨進(jìn)程級(jí)別的消耗,甚至是跨系統(tǒng)、跨網(wǎng)絡(luò)的消耗,而Mock可以把消耗降低到進(jìn)程內(nèi)。
    Mock的第三個(gè)好處就是提升測試效率,提高單位時(shí)間內(nèi)測試的接口數(shù)量。
    Mock的框架有很多中比如EasyMock等,這里選用PowerMock是因?yàn)镻owerMock可以用來Mock 私有方法,靜態(tài)方法以及final方法。EasyMock等則不能。

PowerMock的使用
環(huán)境
軟件    版本
junit    4.13
powermock    2.0.7
引入依賴

  <properties>
        <powermock.version>2.0.7</powermock.version>
    </properties>
     <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>
        <!--powermock開始-->
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
        <!--powermock結(jié)束-->


這里引入了是三個(gè)依賴,junit依賴如果項(xiàng)目中已有的話,則不需要重復(fù)引入,需要注意的是JUnit 4.4及以上版本的JUnit需要引入2.0.x 版本以上的 powermock 。如果項(xiàng)目中有mockito依賴還需要注意mockito的版本與powermock版本對(duì)應(yīng)關(guān)系,對(duì)應(yīng)如下圖:
詳細(xì)請(qǐng)參考Using PowerMock with Mockito,如果引入的版本不匹配則可能會(huì)報(bào)如下錯(cuò)誤:

java.lang.TypeNotPresentException: Type org.powermock.modules.junit4.PowerMockRunner not present



依賴引入之后就可以編寫單元測試代碼了。
注解說明

現(xiàn)有一個(gè)待測試的類UserServiceImpl,該類中注入了一個(gè)UserMapper的類實(shí)例。

@Service
public class UserServiceImpl {
     @Autowried
    private UserMapper userMapper;
    ........省略部分方法
}


那么如何對(duì)上面的類通過powermock的方式進(jìn)行單元測試呢?首先是定義一個(gè)測試類,定義如下:

@RunWith(PowerMockRunner.class)
@PrepareForTest({UserServiceImpl.class, DateUtil.class, UserMapper.class})
public class UserServiceImplTest {
      @Mock
    private UserMapper userMapper;
    @InjectMocks
    private UserServiceImpl userServiceImpl = new UserServiceImpl();
}



@RunWith(PowerMockRunner.class) 注解表明使用PowerMockRunner運(yùn)行測試用例,這個(gè)必須添加,不然無法使用PowerMock。
@PrepareForTest({UserServiceImpl.class, DateUtil.class, UserMapper.class}) @PrepareForTest 注解是用來添加所有需要測試的類,這里列舉了三個(gè)需要測試的類。
@Mock注解修飾會(huì)mock出來一個(gè)對(duì)象,這里mock出來的是UserMapper類實(shí)例。
@InjectMocks 注解會(huì)主動(dòng)將已存在的mock對(duì)象注入到bean中,按名稱注入,這個(gè)注解修飾在我們需要測試的類上。必須要手動(dòng)new一個(gè)實(shí)例,不然單元測試會(huì)有問題。
這幾個(gè)注解是一個(gè)測試類必須要的。說完了測試類的定義,接下來就讓我們來看看各種方法是如何mock的。
mock普通方法

    待測試的方法(UserMapper中)

 boolean saveUser(User user) {
        int i = userMapper.addUser(user);
        return i == 1 ? true : false;
    }



這里的方法int i = userMapper.addUser(user); 有入?yún)?,有出參,沒有關(guān)鍵字修飾,是一個(gè)普通的方法,mock的方式也很簡單,就是PowerMockito.when(userMapper.addUser(user)).thenReturn(1); 在when方法中調(diào)用你需要mock的方法,thenReturn方法寫入你期待返回的值。從字面意思理解就是當(dāng)調(diào)用xxx方法時(shí),返回xxx值。 詳細(xì)的示例如下:
2. 測試方法

 User user = new User();
        user.setId(1);
        user.setUserName("test");
        user.setPassword("admin123");
        PowerMockito.when(userMapper.addUser(user)).thenReturn(1);
        boolean result = userServiceImpl.saveUser(user);
        Assert.assertEquals(true, result);



mock拋出異常

單元測試中我們有時(shí)候需要mock異常的拋出,其mock的方式也很簡單就是在thenThrow(new Exception())寫入你期待拋出的異常。如果被mock的方法拋出的是受檢異常(checked exception)的話,那么thenThrow拋出new Exception()或者其子類。
如果被mock的方法拋出的是非受檢異常(unchecked exception),那么thenThrow拋出new RuntimeException或其子類。使用的示范如下:

    待測試的方法(UserMapper中)

 int delUser(int id) throws Exception {
        if (id == -1) {
            throw new Exception("傳入的id值不對(duì)");
        } else {
            return 1;
        }
    }



    測試方法

   PowerMockito.when(userMapper.delUser(-1)).thenThrow(new Exception());


這里delUser方法拋出的是受檢異常Exception,所以在thenThrow中需要new一個(gè)Exception對(duì)象。
mock新建對(duì)象

如果我們要對(duì)一個(gè)實(shí)體對(duì)象Bean進(jìn)行Mock,只需要這樣寫PowerMockito.whenNew(User.class).withAnyArguments().thenReturn(user)
這個(gè)代碼的意思是創(chuàng)建一個(gè)User實(shí)例對(duì)象,不管傳入啥參數(shù)都返回定義的實(shí)例user,用于替換被測試方法中相應(yīng)的User對(duì)象。使用示范如下:

    待測試的方法(UserMapper中)

 public int countUser() {
        User user = new User();
        int count = 0;
        if (user.getId() > 0) {
            count += 1;
        }
        return count;
    }



    測試方法

 // 6.mock新建對(duì)象
        User user = new User();
        user.setId(11);
        PowerMockito.whenNew(User.class).withAnyArguments().thenReturn(user);
        int result = userServiceImpl.countUser();
        Assert.assertEquals(1, result);



這里mock了一個(gè)User對(duì)象。id是11,當(dāng)調(diào)用countUser方法時(shí)可以拿到之前mock的User對(duì)象,所以返回的結(jié)果是1。
mock無返回值的方法

對(duì)于返回值是通過void修飾的方法,他的mock方式與普通方法的mock方式不同。有兩種方式mock。
方式一:

  PowerMockito.doNothing().when(userMapper, "updateUser", new User());



在when方法中傳入userMapper類實(shí)例,需要調(diào)用的方法名,以及需要傳入的參數(shù)。
方式二:

  PowerMockito.doNothing().when(userMapper).updateUser(user);


在when方法中只傳入userMapper類實(shí)例,然后通過函數(shù)式調(diào)用的方式調(diào)用待測試的方法。
使用示范如下:

    待測試的方法(UserServiceImpl中)

 public void updateUser(User user) {
        userMapper.updateUser(user);
    }

    測試方法

  User user = new User();
        // 4.mock返回值為void的方法
        //方法一
        PowerMockito.doNothing().when(userMapper, "updateUser", new User());
        //方法二
        PowerMockito.doNothing().when(userMapper).updateUser(user);
        userServiceImpl.updateUser(user);



mock被final修飾的方法

現(xiàn)在有一個(gè)方法被final關(guān)鍵字修飾,那么該如何要mock這個(gè)方法,首先需要mock出一個(gè)類實(shí)例。如下所示:

    UserMapper mock = PowerMockito.mock(UserMapper.class);

這里需要特別注意的是被mock的類必須要在@PrepareForTest注解中指定,如本例中的@PrepareForTest({UserMapper.class})。不然就會(huì)報(bào)如下錯(cuò)誤:

org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.

使用示范如下:

    待測試的方法(UserMapper中)

    final String getUserName() {
        return "admin";
    }


    測試方法

  UserMapper mock = PowerMockito.mock(UserMapper.class);
  when(mock.getUserName()).thenReturn("123");


參數(shù)模糊匹配

前面的測試方法中,參數(shù)我們都是指定的,在一些場景下,對(duì)于一些比較復(fù)雜的參數(shù),我們不好構(gòu)造,這時(shí)候參數(shù)模糊匹配就派上用場了。如下所示,現(xiàn)有方法selectUser,他有三個(gè)參數(shù),參數(shù)類型個(gè)不相同。

User selectUser(Integer id, String userName, String password)



當(dāng)對(duì)這個(gè)方法進(jìn)行mock時(shí),可以不用傳入具體的參數(shù)值。就行這樣進(jìn)行mock。

PowerMockito.when(userMapper.selectUser(ArgumentMatchers.anyInt(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())).thenReturn(user);



其中ArgumentMatchers.anyInt()是指任意的int類型的值,ArgumentMatchers.anyString()是指任意String類型的值。需要特別注意的是一個(gè)方法中只要有一個(gè)參數(shù)使用了模糊匹配,其余的參數(shù)也都需要使用模糊匹配。
mock靜態(tài)方法

對(duì)靜態(tài)方法的mock也比較簡單,與普通方法的mock相比只是多了一行代碼。就是首先需要對(duì)靜態(tài)方法的所在的類進(jìn)行mock。

 PowerMockito.mockStatic(DateUtil.class);


同時(shí)被mock的類必須要在@PrepareForTest注解中指定,像本例中的DateUtil類。@PrepareForTest({ DateUtil.class}),其他的與普通方法的mock一樣,再此就不在贅述了。
mock私有方法

當(dāng)我們需要測試的方法中調(diào)用了一個(gè)比較復(fù)雜的私有方法時(shí),我們該如何mock呢?針對(duì)這種情況PowerMock也可以輕松應(yīng)對(duì)。首先調(diào)用spy方法創(chuàng)建出一個(gè)新的UserServiceImpl類實(shí)例。然后通過這個(gè)實(shí)例來mock這個(gè)私有方法。如下所示:

     UserServiceImpl spy = PowerMockito.spy(userServiceImpl);
     PowerMockito.when(spy, "verifyId", 0).thenReturn(true);

   

使用示范:

    待測試的方法

  boolean delUser(int id) throws Exception {
        int i = userMapper.delUser(id);
        return verifyId(i);
    }

 

    測試方法

   public void testVerifyId() throws Exception {
        // 5.mock私有方法
        UserServiceImpl spy = PowerMockito.spy(userServiceImpl);
        PowerMockito.when(spy, "verifyId", 0).thenReturn(true);
        PowerMockito.when(userMapper.delUser(0)).thenReturn(1);
        Assert.assertEquals(userServiceImpl.delUser(0), true);
    }


總結(jié)

本文詳細(xì)介紹了PowerMock的常見使用,PowerMock是一個(gè)應(yīng)用比較廣泛的單元測試框架,運(yùn)用在單元測試中可以很好的提供測試效率。PowerMock可以mock 普通方法,私有方法,靜態(tài)方法,final修飾的方法。
參考

無所不能的PowerMock,mock私有方法,靜態(tài)方法,測試私有方法,final類
power mock 入門介紹及使用示例





作者:碼農(nóng)飛哥
微信公眾號(hào):碼農(nóng)飛哥