目录
一、官网
二、Demo示例
1. 目录结构
2. 被测试的类
3. 测试类
三、Mockito方法说明
1. mock对象创建
2. Mockito框架中的常见方法说明
2.1 常见的打桩方法
2.2 常见的验证行为
2.3 其他方法
3. Mockito的局限性
Mockito:
https://github.com/mockito/mockito
Mockito (Mockito 4.4.0 API)
为什么要用mock:解决测试类对其他类的依赖。在实际的测试过程中,有些需要被测试的方法对其他类对象或变量有依赖,如果不初始化的话,很容易出现NP导致无法顺利的继续测试,这个时候就需要mock这些对象来解决了。
参考Android Developer中提到的示例来说明Mockito用法。
// gradle引入
dependencies {
// test目录
testImplementation 'org.mockito:mockito-core:4.4.0'
// androidTest目录
//androidTestImplementation "org.mockito:mockito-android:4.4.0"
}
// 被测试类为 SharedPreferencesHelper
package com.fanff.unittestdemo.mockdemo;
import android.content.SharedPreferences;
import java.util.Calendar;
/**
* 参考Google官网示例:https://github.com/android/testing-samples/tree/master/unit/BasicSample
*/
public class SharedPreferencesHelper {
// Keys for saving values in SharedPreferences.
static final String KEY_NAME = "key_name";
static final String KEY_DOB = "key_dob_millis";
static final String KEY_EMAIL = "key_email";
private final SharedPreferences mSharedPreferences;
public SharedPreferencesHelper(SharedPreferences sharedPreferences) {
mSharedPreferences = sharedPreferences;
}
public boolean savePersonalInfo(SharedPreferenceEntry sharedPreferenceEntry){
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(KEY_NAME, sharedPreferenceEntry.getName());
editor.putLong(KEY_DOB, sharedPreferenceEntry.getDateOfBirth().getTimeInMillis());
editor.putString(KEY_EMAIL, sharedPreferenceEntry.getEmail());
// Commit changes to SharedPreferences.
return editor.commit();
}
public SharedPreferenceEntry getPersonalInfo() {
// Get data from the SharedPreferences.
String name = mSharedPreferences.getString(KEY_NAME, "");
Long dobMillis =
mSharedPreferences.getLong(KEY_DOB, Calendar.getInstance().getTimeInMillis());
Calendar dateOfBirth = Calendar.getInstance();
dateOfBirth.setTimeInMillis(dobMillis);
String email = mSharedPreferences.getString(KEY_EMAIL, "");
// Create and fill a SharedPreferenceEntry model object.
return new SharedPreferenceEntry(name, dateOfBirth, email);
}
}
package com.fanff.unittestdemo.mockdemo;
import java.util.Calendar;
public class SharedPreferenceEntry {
// Name of the user.
private final String mName;
// Date of Birth of the user.
private final Calendar mDateOfBirth;
// Email address of the user.
private final String mEmail;
public SharedPreferenceEntry(String name, Calendar dateOfBirth, String email) {
mName = name;
mDateOfBirth = dateOfBirth;
mEmail = email;
}
public String getName() {
return mName;
}
public Calendar getDateOfBirth() {
return mDateOfBirth;
}
public String getEmail() {
return mEmail;
}
}
Q:只想测试SharedPreferencesHelper#savePersonalInfo()是否有调用Editor#commit()
A:但是savePersonalInfo()这个方法中需要依赖的对象有SharedPreferenceEntry、Editor,想要绕开这些对象的创建就需要用到mock了。
public boolean savePersonalInfo(SharedPreferenceEntry sharedPreferenceEntry){
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(KEY_NAME, sharedPreferenceEntry.getName());
editor.putLong(KEY_DOB, sharedPreferenceEntry.getDateOfBirth().getTimeInMillis());
editor.putString(KEY_EMAIL, sharedPreferenceEntry.getEmail());
// Commit changes to SharedPreferences.
return editor.commit();
}
// 单元测试代码
package com.fanff.unittestdemo.mockdemo;
import android.content.SharedPreferences;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.Calendar;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
public class SharedPreferencesHelperTest {
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void testSavePersonalInfo() {
// Mock SharedPreferences对象作为SharedPreferencesHelper构造方法的参数
SharedPreferences sharedPreferences = Mockito.mock(SharedPreferences.class);
SharedPreferencesHelper sharedPreferencesHelper =
new SharedPreferencesHelper(sharedPreferences);
// Mock SharedPreferenceEntry对象作为savePersonalInfo方法的参数
SharedPreferenceEntry sharedPreferenceEntry = Mockito.mock(SharedPreferenceEntry.class);
// Mock SharedPreferences.Editor对象作为savePersonalInfo方法里的局部变量
SharedPreferences.Editor editor = Mockito.mock(SharedPreferences.Editor.class);
// Mock savePersonalInfo()方法内部的执行流程
when(sharedPreferences.edit()).thenReturn(editor);
when(editor.putString(anyString(), anyString())).thenReturn(editor);
when(sharedPreferenceEntry.getDateOfBirth()).thenReturn(Calendar.getInstance());
when(editor.putLong(anyString(), anyLong())).thenReturn(editor);
// 测试savePersonalInfo()是否有调用commit
sharedPreferencesHelper.savePersonalInfo(sharedPreferenceEntry);
Mockito.verify(editor).commit();
}
}
当然为了简化mock对象的初始化,可以这样写:
(1) 单元测试类定义的开头,添加 @RunWith(MockitoJUnitRunner.class)
注释。此注释可告知 Mockito 测试程序运行的框架;
(2) 在对象字段声明前添加 @Mock
注释。
package com.fanff.unittestdemo.mockdemo;
import android.content.SharedPreferences;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Calendar;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class SharedPreferencesHelperTest {
// 如果使用@Mock注解的方式mock对象则需要在类前加 @RunWith(MockitoJUnitRunner.class)
@Mock
SharedPreferences sharedPreferences;
@Mock
SharedPreferenceEntry sharedPreferenceEntry;
@Test
public void testSavePersonalInfo() {
SharedPreferencesHelper sharedPreferencesHelper =
new SharedPreferencesHelper(sharedPreferences);
SharedPreferences.Editor editor = Mockito.mock(SharedPreferences.Editor.class);
when(sharedPreferences.edit()).thenReturn(editor);
when(editor.putString(anyString(), anyString())).thenReturn(editor);
when(sharedPreferenceEntry.getDateOfBirth()).thenReturn(Calendar.getInstance());
when(editor.putLong(anyString(), anyLong())).thenReturn(editor);
sharedPreferencesHelper.savePersonalInfo(sharedPreferenceEntry);
Mockito.verify(editor).commit();
}
@Test
public void testGetPersonalInfo() {
}
}
// 测试结果及覆盖
mock的对象可以通过Mockito.mock()和Mockito.spy()两种方法来创建。
Mock:
* (1) mock对象调用的所有方法都是空方法。非void方法都将返回默认值,比如返回值为int的方法将返回0,返回值为对象的方法将返回null等,而void方法将什么都不做;
* (2) 适用场景:类对外部依赖较多,只关心少数函数的具体实现。Spy:
* (1) 是正常对象的替身,跟正常对象的使用一样;
* (2) 适用场景:类对外部依赖较少,关心大部分函数的具体实现
// 被测代码: Calculator.java
package com.fanff.unittestdemo.junitdemo;
public class Calculator {
public int addExact(int x, int y) {
return x + y;
}
public int subtractExact(int x, int y) {
return x - y;
}
public int multiplyExact(int x, int y) {
return x * y;
}
// TODO: zero case
public int intDivide(int x, int y) {
return x / y;
}
}
// 测试代码:CalculatorMockTest.java
package com.fanff.unittestdemo.mockdemo;
import com.fanff.unittestdemo.junitdemo.Calculator;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
public class CalculatorMockTest {
// Mock vs Spy的区别
@Test
public void testMultiplyExact() {
Calculator calculatorMockObj = Mockito.mock(Calculator.class);
Assert.assertEquals(0, calculatorMockObj.multiplyExact(6, 8));// Pass
// Assert.assertEquals(48, calculatorMockObj.multiplyExact(6, 8));// Fail
Calculator calculatorSpyObj = Mockito.spy(Calculator.class);
Assert.assertEquals(48, calculatorSpyObj.multiplyExact(6, 8));// Pass
// Assert.assertEquals(0, calculatorSpyObj.multiplyExact(6, 8));// Fail
}
}
Q: 如果想Mockito.mock()出来的对象能像spy()出来的对象一样执行方法的具体实现,该如何做?如果Mockito.spy()出来的对象能像mock()出来的对象一样不不去关心执行方法的具体实现,该如何实现呢?
A:使用打桩方法doCallRealMethod()和doReturn()。如下示例。
/mock vs spy
@Test
public void testSubtractExact() {
Calculator calculatorMockObj = Mockito.mock(Calculator.class);
Assert.assertEquals(0, calculatorMockObj.subtractExact(6, 8));// Pass
// Assert.assertEquals(-2, calculatorMockObj.subtractExact(6, 8));// Fail
doCallRealMethod().when(calculatorMockObj).subtractExact(anyInt(), anyInt());
Assert.assertEquals(-2, calculatorMockObj.subtractExact(6, 8));// Pass
}
@Test
public void testAddExact() {
Calculator calculatorSpyObj = Mockito.spy(Calculator.class);
Assert.assertEquals(16, calculatorSpyObj.addExact(8, 8));// Pass
// Assert.assertEquals(0, calculatorSpyObj.addExact(8, 8));// Fail
doReturn(0).when(calculatorSpyObj).addExact(anyInt(), anyInt());
Assert.assertEquals(0, calculatorSpyObj.addExact(8, 8));// Pass
}
常用的API可以分为两大类: 打桩方法和验证行为。
方法名 | 描述 |
thenReturn(T value) |
设置要返回的值 |
thenThrow(Throwable... throwables) |
设置要抛出的异常 |
thenAnswer(Answer> answer) |
对结果进行拦截 |
doReturn(Object toBeReturned) |
提前设置要返回的值 |
doThrow(Throwable... toBeThrown) |
提前设置要抛出的异常 |
doAnswer(Answer answer) |
提前对结果进行拦截 |
doCallRealMethod() |
调用某一个方法的真实实现 |
doNothing() |
设置void方法什么也不做 |
验证方法 | |
方法名 | 描述 |
after(long millis) | 在给定的时间后进行验证 |
timeout(long millis) | 验证方法执行是否超时 |
atLeast(int minNumberOfInvocations) | 至少进行n次验证 |
atMost(int maxNumberOfInvocations) | 至多进行n次验证 |
description(String description) | 验证失败时输出的内容 |
times(int wantedNumberOfInvocations) | 验证调用方法的次数 |
never() | 验证交互没有发生,相当于times(0) |
only() | 验证方法只被调用一次,相当于times(1) |
参数匹配 | |
方法名 | 描述 |
---|---|
anyObject() | 匹配任何对象 |
any(Class |
与anyObject()一样 |
any() | 与anyObject()一样 |
anyBoolean() | 匹配任何boolean和非空Boolean |
anyByte() | 匹配任何byte和非空Byte |
anyCollection() | 匹配任何非空Collection |
anyDouble() | 匹配任何double和非空Double |
anyFloat() | 匹配任何float和非空Float |
anyInt() | 匹配任何int和非空Integer |
anyList() | 匹配任何非空List |
anyLong() | 匹配任何long和非空Long |
anyMap() | 匹配任何非空Map |
anyString() | 匹配任何非空String |
contains(String substring) | 参数包含给定的substring字符串 |
argThat(ArgumentMatcher |
创建自定义的参数匹配模式 |
方法名 | 方法描述 |
---|---|
reset(T … mocks) | 重置Mock |
spy(Class |
实现调用真实对象的实现 |
inOrder(Object… mocks) | 验证执行顺序 |
@InjectMocks注解 | 自动将模拟对象注入到被测试对象中 |
Android单元测试系列(1)-开篇_Chris_166的博客-CSDN博客
在 "二、单元测试工具链"中已经提过了,下篇介绍解决之道 "powermock"的用法