Mockito測試(二)

最開始項目中是沒有單元測試的,基本都是本身經過各類方式來實現測試的。好比修改代碼,測完再改回來;再好比直接模擬用戶操做,直接當黑盒測試,而後本身去看相應的邏輯有沒有,狀態有沒有改變。html

這些方式有幾個缺點:框架

  • 測試不完整,挖有一些隱藏的坑ide

  • 改代碼測試,在該回來的時候可能引入新bug單元測試

  • 手工測試比較耗時測試

  • 下次改需求時,須要再次手工測試this

這個裏面屢次手工測試比較難受,太浪費時間了。之前因爲一個邏輯牽扯比較多,構造對象比較複雜,僅僅用JUnit寫測試的工做量仍是太大,因此單元測試一直沒有進行下去。後來引入的mockito框架來用於新代碼的測試,powermock用於之前的代碼測試。下面將介紹一下mockito和powermock框架,就明白爲何要用這兩個框架了。spa

Mockito

mockito是用的比較廣的mock框架。mock技術的目的和做用是模擬一些在應用中不容易構造或者比較複雜的對象,從而把測試與測試邊界之外的對象隔離開。爲了說明使用方法,先引入一下基本對象code

public class User {
    private int userId;
    private ComplexObject complexObject;
    
    public int getUserId() {
        return userId;
    }
    //... construction getter
}
public class Service {
    public boolean checkUser(User user) {
        if(user.getUserId() < 100){
            return true;
        }
        return false;
    }
}

默認的static importhtm

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

Mock

若是要測試Service#checkUser方法,咱們就要構造User對象。假設ComplexObject構造很複雜,若是不用mock,測試將步履維艱。下面來看看mockito是如何構造一個假的User並進行測試的吧。對象

@Test
public void testCheckUser() throws Exception {
    Service service = new Service();
    User user = mock(User.class);
    when(user.getUserId()).thenReturn(2);
    boolean checkResult = service.checkUser(user);
    assertTrue(checkResult);
}

上面能夠看到只用mock方法就能夠了,而後設置一下getUserId方法的返回就好了。when的語法理解很容易,就不解釋了。上面的when語句也能夠換成

doReturn(2).when(user).getUserId();

在這個例子中,這兩種when的寫法都是可行的。一共有如下幾種方式來模擬一個方法。

  • doReturn

  • doCallRealMethod

  • doNothing

  • doThrow

  • doAnswer

固然也有thenXXX這種形式。

Spy

spy和mock很像,都是模擬一個對象。可是mock是把全部方法都接管了,spy是默認調用對象的方法。若是先mock出一個對象,而後對每個方法調用doCallRealMethod,這就至關於spy出一個對象。
因此spy和mock只是初始模擬對象的默認設置不同而已,其餘行爲都是同樣的。

Annotation

能夠直接用註解來實現mock:

@Mock
User user;

@Before
public void initMocks() {
    MockitoAnnotations.initMocks(this);
}

@Test
public void testCheckUser() throws Exception {
    Service service = new Service();
    doReturn(2).when(user).getUserId();
    boolean checkResult = service.checkUser(user);
    assertTrue(checkResult);
}

這個須要調用initMocks(this)來注入,這裏是經過@Before,也能夠經過@RunWith來調用initMocks方法。

也能夠用Spy註解:

@Spy User user;

還有一個註解比較有用@InjectMocks,這個能夠把對象注入到其餘對象中去的。
下面稍微添加一下代碼:

public class ComplexObject {
    @Override
    public String toString() {
        return "Complex lhcpig";
    }
    //...
}
public class Service {
    public String handleUser(User user){
        return user.getComplexObject() + "";
    }
    //...
}
public class TestService {

    @InjectMocks
    User user;
    @Spy
    ComplexObject complexObject;

    @Before
    public void initMocks() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testHandleUser() throws Exception {
        Service service = new Service();
        String s = service.handleUser(user);
        assertThat(s, is("Complex lhcpig"));
    }
    //...
}
  • 注1:這裏和以前的那個test有衝突,由於User的註解不同,因此第一個test會報NotAMockException或者MissingMethodInvocationException異常。

  • 注2:這裏用Spy,能夠不用額外代碼,就CallRealMethod。

Verify

這個是用來判斷方法是否被調用,調用是否超時,調用了多少次等測試。

@Test
public void testCheckUser() throws Exception {
    Service service = new Service();
    when(user.getUserId()).thenReturn(2);
    boolean checkResult = service.checkUser(user);
    assertTrue(checkResult);
    verify(user).getUserId();
    verify(user, timeout(100)).getUserId();
    user.getUserId();
    verify(user, times(2)).getUserId();
}

若是方法有參數,也能夠驗證參數。
這裏只是簡介,若是想詳細瞭解Mockito,建議仍是看官網文檔

PowerMock

Mockito不支持final方法,私有方法,靜態方法,而PowerMock支持。因此這裏也要介紹一下。可是仍是不建議項目中使用,若是須要使用PowerMock才能測試,說明代碼的可測試性很差,須要改進代碼。通常都是歷史遺留代碼或者第三方庫相關測試的時候才須要使用。

下面是使用方式

@RunWith(PowerMockRunner.class)
@PrepareForTest( { YourClassWithEgStaticMethod.class })
public class YourTestCase {
...
}

給個例子,你們就理解了

@RunWith(PowerMockRunner.class)
@PrepareForTest( { Service.class })
public class TestService {

    @Before
    public void initMocks() {
        mockStatic(Service.class);
    }

    @Test
    public void testTestStaticFinal() throws Exception {
        PowerMockito.when(Service.testStaticFinal()).thenReturn("mock1");
        assertEquals("mock1", Service.testStaticFinal());
    }

    @Test
    public void testPrivate() throws Exception {
        Service t = mock(Service.class);
        PowerMockito.when(t, "testPrivate").thenReturn("xxx");
        doCallRealMethod().when(t).testPrivateForPublic();
        assertEquals("xxx", t.testPrivateForPublic());
    }

    @Test
    public void testTestPrivateWithArg() throws Exception {
        Service t = spy(new Service());
        String arg = "dd";
        PowerMockito.when(t, "testPrivateWithArg", arg).thenReturn("lhc");
        assertEquals("lhc", t.getTestPrivateWithArg(arg));
    }
}
public class Service {

    public static final String testStaticFinal() {
        System.out.println("testStaticFinal");
        return "static final";
    }

    private String testPrivate() {
        System.out.println("testPrivate");
        return "private";
    }

    public String testPrivateForPublic() {
        System.out.println("testPrivateForPublic");
        return testPrivate();
    }

    private String testPrivateWithArg(String arg) {
        System.out.println("testPrivateWithArg");
        return arg + "x";
    }
}

私有方法用PowerMock測試後,若是要修更名字就會很麻煩,重構起來也可能會影響測試用例。所PowerMock的正確使用方式是儘可能不使用。

由於要反射調用私有方法,因此寫法沒有mockito那麼優雅。我這裏使用的是基於Mockito的PowerMock,因此能夠混合使用,好比上面用到的spy,when等。固然PowerMock還有基於其餘mock框架(EasyMock)的擴展,這裏就再也不進一步介紹了。

想讓測試更加高效,測試框架仍是其次,寫出可測試性的代碼纔是最重要的。

相關文章
相關標籤/搜索