Moke測試學習總結: 被測試代碼: public class LoginPresenter { private UserManager mUserManager = new UserManager(); public void login(String username, String passw ...
Moke測試學習總結:
被測試代碼:
public class LoginPresenter {
private UserManager mUserManager = new UserManager();
public void login(String username, String password) {
if (username == null || username.length() == 0) return;
if (password == null || password.length() < 6) return;
mUserManager.performLogin(username, password);
}
}
單元測試
public class LoginPresenterTest {
@Test
public void testLogin() throws Exception {
LoginPresenter loginPresenter = new LoginPresenter();
loginPresenter.login("xiaochuang", "xiaochuang password");
//驗證LoginPresenter裡面的mUserManager的performLogin()方法得到了調用,同時參數分別是“xiaochuang”、“xiaochuang‘s password”
...
}
}
問題1:麽驗證 LoginPresenter 裡面的 mUserManager 的 performLogin() 方法得到了調用,以及它的參數是正確性呢?
下麵開始mock的學習
Moko概念:
Mock的概念,其實很簡單,我們前面也介紹過:所謂的mock就是創建一個類的虛假的對象,在測試環境中,用來替換掉真實的對象,以達到兩大目的:
- 驗證這個對象的某些方法的調用情況,調用了多少次,參數是什麼等等
- 指定這個對象的某些方法的行為,返回特定的值,或者是執行特定的動作
驗證開始:
試誤1:
public class LoginPresenterTest {
@Test
public void testLogin() throws Exception {
Mockito.mock(UserManager.class);
LoginPresenter loginPresenter = new LoginPresenter();
loginPresenter.login("xiaochuang", "xiaochuang password");
UserManager userManager = loginPresenter.getUserManager();
Mockito.verify(userManager).performLogin("xiaochuang", "xiaochuang password"); //<==
}
}
試誤2:
public class LoginPresenterTest {
@Test
public void testLogin() throws Exception {
UserManager mockUserManager = Mockito.mock(UserManager.class); //<==
LoginPresenter loginPresenter = new LoginPresenter();
loginPresenter.login("xiaochuang", "xiaochuang password");
Mockito.verify(mockUserManager).performLogin("xiaochuang", "xiaochuang password"); //<==
}
}
正確:
public class LoginPresenter {
private UserManager mUserManager = new UserManager();
public void login(String username, String password) {
if (username == null || username.length() == 0) return;
if (password == null || password.length() < 6) return;
mUserManager.performLogin(username, password);
}
public void setUserManager(UserManager userManager) { //<==
this.mUserManager = userManager;
}
}
@Test
public void testLogin() throws Exception {
UserManager mockUserManager = Mockito.mock(UserManager.class);
LoginPresenter loginPresenter = new LoginPresenter();
loginPresenter.setUserManager(mockUserManager); //<==
loginPresenter.login("xiaochuang", "xiaochuang password");
Mockito.verify(mockUserManager).performLogin("xiaochuang", "xiaochuang password");
}
第一種情況:
- 驗證方法調用
Mockito.verify(mockUserManager, Mockito.times(1)).performLogin("xiaochuang", "xiaochuang password");
Mockito.verify(mockUserManager).performLogin(Mockito.anyString(), Mockito.anyString());
類似 anyString ,還有 anyInt, anyLong, anyDouble 等等。 anyObject 表示任何對象, any(clazz) 表示任何屬於clazz的對象。在寫這篇文章的時候,我剛剛發現,還有非常有意思也非常人性化的 anyCollection,anyCollectionOf(clazz), anyList(Map, set), anyListOf(clazz) 等等。
- 指定mock對象的某些方法的行為
public void login(String username, String password) {
if (username == null || username.length() == 0) return;
//假設我們對密碼強度有一定要求,使用一個專門的validator來驗證密碼的有效性
if (mPasswordValidator.verifyPassword(password)) return; //<==
mUserManager.performLogin(null, password);
}
//先創建一個mock對象
PasswordValidator mockValidator = Mockito.mock(PasswordValidator.class);
//當調用mockValidator的verifyPassword方法,同時傳入"xiaochuang_is_handsome"時,返回true
Mockito.when(mockValidator.verifyPassword("xiaochuang_is_handsome")).thenReturn(true);
//當調用mockValidator的verifyPassword方法,同時傳入"xiaochuang_is_not_handsome"時,返回false
Mockito.when(validator.verifyPassword("xiaochuang_is_not_handsome")).thenReturn(false);
同樣的,你可以用 any 系列方法來指定"無論傳入任何參數值,都返回xxx":
//當調用mockValidator的verifyPassword方法時,返回true,無論參數是什麼
Mockito.when(validator.verifyPassword(anyString())).thenReturn(true);
2、我們想進一步測試傳給 mUserManager.performLogin 的 NetworkCallback 裡面的代碼,
Mockito.doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
//這裡可以獲得傳給performLogin的參數
Object[] arguments = invocation.getArguments();
//callback是第三個參數
NetworkCallback callback = (NetworkCallback) arguments[2];
callback.onFailure(500, "Server error");
return 500;
}
}).when(mockUserManager).performLogin(anyString(), anyString(), any(NetworkCallback.class));
//假設目標類的實現是這樣的
public class PasswordValidator {
public boolean verifyPassword(String password) {
return "xiaochuang_is_handsome".equals(password);
}
}
另外 spy的情況
@Test
public void testSpy() {
//跟創建mock類似,只不過調用的是spy方法,而不是mock方法。spy的用法
PasswordValidator spyValidator = Mockito.spy(PasswordValidator.class);
//在預設情況下,spy對象會調用這個類的真實邏輯,並返回相應的返回值,這可以對照上面的真實邏輯
spyValidator.verifyPassword("xiaochuang_is_handsome"); //true
spyValidator.verifyPassword("xiaochuang_is_not_handsome"); //false
//spy對象的方法也可以指定特定的行為
Mockito.when(spyValidator.verifyPassword(anyString())).thenReturn(true);
//同樣的,可以驗證spy對象的方法調用情況
spyValidator.verifyPassword("xiaochuang_is_handsome");
Mockito.verify(spyValidator).verifyPassword("xiaochuang_is_handsome"); //pass
}