?前言
Mockito
?是當前最流行的?單元測試?Mock
?框架。采用?Mock
?框架,我們可以?虛擬?出一個?外部依賴,降低測試?組件?之間的?耦合度,只注重代碼的?流程與結果,真正地實現(xiàn)測試目的。
正文
什么是Mock
Mock
?的中文譯為仿制的,模擬的,虛假的。對于測試框架來說,即構造出一個模擬/虛假的對象,使我們的測試能順利進行下去。
Mock
?測試就是在測試過程中,對于某些?不容易構造(如?HttpServletRequest
?必須在?Servlet
?容器中才能構造出來)或者不容易獲取?比較復雜?的對象(如?JDBC
?中的?ResultSet
對象),用一個?虛擬?的對象(Mock
?對象)來創(chuàng)建,以便測試方法。
為什么使用Mock測試
單元測試?是為了驗證我們的代碼運行正確性,我們注重的是代碼的流程以及結果的正確與否。
對比真實運行代碼,可能其中有一些?外部依賴?的構建步驟相對麻煩,如果我們還是按照真實代碼的構建規(guī)則構造出外部依賴,會大大增加單元測試的工作,代碼也會參雜太多非測試部分的內容,測試用例顯得復雜難懂。
采用?Mock
?框架,我們可以?虛擬?出一個?外部依賴,只注重代碼的?流程與結果,真正地實現(xiàn)測試目的。
Mock測試框架的好處
- 可以很簡單的虛擬出一個復雜對象(比如虛擬出一個接口的實現(xiàn)類);
- 可以配置?
mock
?對象的行為; - 可以使測試用例只注重測試流程與結果;
- 減少外部類、系統(tǒng)和依賴給單元測試帶來的耦合。
Mockito的流程
如圖所示,使用?Mockito
?的大致流程如下:
- 創(chuàng)建?外部依賴?的?
Mock
?對象, 然后將此?Mock
?對象注入到?測試類?中; - 執(zhí)行?測試代碼;
- 校驗?測試代碼?是否執(zhí)行正確。
Mockito的使用
在?Module
?的?build.gradle
?中添加如下內容:
dependencies {
//Mockito for unit tests
testImplementation "org.mockito:mockito-core:2.+"
//Mockito for Android tests
androidTestImplementation 'org.mockito:mockito-android:2.+'
}
這里稍微解釋下:
-
mockito-core
: 用于?本地單元測試,其測試代碼路徑位于?module-name/src/test/java/
-
mockito-android
: 用于?設備測試,即需要運行?android
?設備進行測試,其測試代碼路徑位于?module-name/src/androidTest/java/
mockito-core最新版本可以在 Maven 中查詢:mockito-core。 mockito-android最新版本可以在 Maven 中查詢:mockito-android
Mockito的使用示例
普通單元測試使用?mockito(mockito-core)
,路徑:module-name/src/test/java/
這里摘用官網的?Demo
:
檢驗調對象相關行為是否被調用
import static org.mockito.Mockito.*;
// Mock creation
List mockedList = mock(List.class);
// Use mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one"); //調用了add("one")行為
mockedList.clear(); //調用了clear()行為
// Selective, explicit, highly readable verification
verify(mockedList).add("one"); // 檢驗add("one")是否已被調用
verify(mockedList).clear(); // 檢驗clear()是否已被調用
這里?mock
?了一個?List
(這里只是為了用作?Demo
?示例,通常對于?List
?這種簡單的類對象創(chuàng)建而言,直接?new
?一個真實的對象即可,無需進行?mock
),verify()
?會檢驗對象是否在前面已經執(zhí)行了相關行為,這里?mockedList
?在?verify
?之前已經執(zhí)行了?add("one")
?和?clear()
?行為,所以verify()
?會通過。
配置/方法行為
// you can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);
// stubbing appears before the actual execution
when(mockedList.get(0)).thenReturn("first");
// the following prints "first"
System.out.println(mockedList.get(0));
// the following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
這里對幾個比較重要的點進行解析:
when(mockedList.get(0)).thenReturn("first")
這句話?Mockito
?會解析為:當對象?mockedList
?調用?get()
方法,并且參數(shù)為?0
?時,返回結果為"first"
,這相當于定制了我們?mock
?對象的行為結果(mock LinkedList
?對象為?mockedList
,指定其行為?get(0)
,則返回結果為?"first"
)。
mockedList.get(999)
由于?mockedList
?沒有指定?get(999)
?的行為,所以其結果為?null
。因為?Mockito
?的底層原理是使用?cglib
?動態(tài)生成一個?代理類對象,因此,mock
?出來的對象其實質就是一個?代理,該代理在?沒有配置/指定行為?的情況下,默認返回?空值。
上面的?Demo
?使用的是?靜態(tài)方法?mock()
?模擬出一個實例,我們還可以通過注解?@Mock
?也模擬出一個實例:
@Mock
private Intent mIntent;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void mockAndroid(){
Intent intent = mockIntent();
assertThat(intent.getAction()).isEqualTo("com.yn.test.mockito");
assertThat(intent.getStringExtra("Name")).isEqualTo("Whyn");
}
private Intent mockIntent(){
when(mIntent.getAction()).thenReturn("com.yn.test.mockito");
when(mIntent.getStringExtra("Name")).thenReturn("Whyn");
return mIntent;
}
對于標記有?@Mock
,?@Spy
,?@InjectMocks
?等注解的成員變量的?初始化?到目前為止有?2
?種方法:
- 對?
JUnit
?測試類添加?@RunWith(MockitoJUnitRunner.class)
- 在標示有?
@Before
?方法內調用初始化方法:MockitoAnnotations.initMocks(Object)
上面的測試用例,對于?@Mock
?等注解的成員變量的初始化又多了一種方式?MockitoRule
。規(guī)則?MockitoRule
?會自動幫我們調用?MockitoAnnotations.initMocks(this)
?去?實例化?出?注解?的成員變量,我們就無需手動進行初始化了。
Mockito的重要方法
實例化虛擬對象
// You can mock concrete classes, not just interfaces
LinkedList mockedList = mock(LinkedList.class);
// Stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
// Following prints "first"
System.out.println(mockedList.get(0));
// Following throws runtime exception
System.out.println(mockedList.get(1));
// Following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
// Although it is possible to verify a stubbed invocation, usually it's just redundant
// If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
// If your code doesn't care what get(0) returns, then it should not be stubbed. Not convinced? See here.
verify(mockedList).get(0);
- 對于所有方法,
mock
?對象默認返回?null
,原始類型/原始類型包裝類?默認值,或者?空集合。比如對于?int/Integer
?類型,則返回?0
,對于?boolean/Boolean
?則返回?false
。 - 行為配置(
stub
)是可以被復寫的:比如通常的對象行為是具有一定的配置,但是測試方法可以復寫這個行為。請謹記行為復寫可能表明潛在的行為太多了。 - 一旦配置了行為,方法總是會返回?配置值,無論該方法被調用了多少次。
- 最后一次行為配置是更加重要的,當你為一個帶有相同參數(shù)的相同方法配置了很多次,最后一次起作用。
參數(shù)匹配
Mockito
?通過參數(shù)對象的?equals()
?方法來驗證參數(shù)是否一致,當需要更多的靈活性時,可以使用參數(shù)匹配器:
// Stubbing using built-in anyInt() argument matcher
when(mockedList.get(anyInt())).thenReturn("element");
// Stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
when(mockedList.contains(argThat(isValid()))).thenReturn("element");
// Following prints "element"
System.out.println(mockedList.get(999));
// You can also verify using an argument matcher
verify(mockedList).get(anyInt());
// Argument matchers can also be written as Java 8 Lambdas
verify(mockedList).add(argThat(someString -> someString.length() > 5));
參數(shù)匹配器?允許更加靈活的?驗證?和?行為配置。更多?內置匹配器?和?自定義參數(shù)匹配器?例子請參考:ArgumentMatchers
,MockitoHamcrest
注意:如果使用了參數(shù)匹配器,那么所有的參數(shù)都需要提供一個參數(shù)匹配器。
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// Above is correct - eq() is also an argument matcher
verify(mock).someMethod(anyInt(), anyString(), "third argument");
// Above is incorrect - exception will be thrown because third argument is given without an argument matcher.
類似?anyObject()
,eq()
?這類匹配器并不返回匹配數(shù)值。他們內部記錄一個?匹配器堆棧?并返回一個空值(通常為?null
)。這個實現(xiàn)是為了匹配?java
?編譯器的?靜態(tài)類型安全,這樣做的后果就是你不能在?檢驗/配置方法?外使用?anyObject()
,eq()
?等方法。
校驗次數(shù)
LinkedList mockedList = mock(LinkedList.class);
// Use mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
// Follow two verifications work exactly the same - times(1) is used by default
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
// Exact number of invocations verification
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
// Verification using never(). never() is an alias to times(0)
verify(mockedList, never()).add("never happened");
// Verification using atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");
校驗次數(shù)方法常用的有如下幾個:
|Method |Meaning| |:------|:-------| |times(n)| 次數(shù)為n,默認為1(times(1))| |never()| 次數(shù)為0,相當于times(0)| |atLeast(n)|最少n次| |atLeastOnce()| 最少一次| |atMost(n)| 最多n次 |
拋出異常
doThrow(new RuntimeException()).when(mockedList).clear();
// following throws RuntimeException
mockedList.clear();
按順序校驗
有時對于一些行為,有先后順序之分,所以,當我們在校驗時,就需要考慮這個行為的先后順序:
// A. Single mock whose methods must be invoked in a particular order
List singleMock = mock(List.class);
// Use a single mock
singleMock.add("was added first");
singleMock.add("was added second");
// Create an inOrder verifier for a single mock
InOrder inOrder = inOrder(singleMock);
// Following will make sure that add is first called with "was added first, then with "was added second"
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
// B. Multiple mocks that must be used in a particular order
List firstMock = mock(List.class);
List secondMock = mock(List.class);
// Use mocks
firstMock.add("was called first");
secondMock.add("was called second");
// Create inOrder object passing any mocks that need to be verified in order
InOrder inOrder = inOrder(firstMock, secondMock);
// Following will make sure that firstMock was called before secondMock
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");
存根連續(xù)調用
對于同一個方法,如果我們想讓其在?多次調用?中分別?返回不同?的數(shù)值,那么就可以使用存根連續(xù)調用:
when(mock.someMethod("some arg"))
.thenThrow(new RuntimeException())
.thenReturn("foo");
// First call: throws runtime exception:
mock.someMethod("some arg");
// Second call: prints "foo"
System.out.println(mock.someMethod("some arg"));
// Any consecutive call: prints "foo" as well (last stubbing wins).
System.out.println(mock.someMethod("some arg"));
也可以使用下面更簡潔的存根連續(xù)調用方法:
when(mock.someMethod("some arg")).thenReturn("one", "two", "three");
注意:存根連續(xù)調用要求必須使用鏈式調用,如果使用的是同個方法的多個存根配置,那么只有最后一個起作用(覆蓋前面的存根配置)。
// All mock.someMethod("some arg") calls will return "two"
when(mock.someMethod("some arg").thenReturn("one")
when(mock.someMethod("some arg").thenReturn("two")
無返回值函數(shù)
對于?返回類型?為?void
?的方法,存根要求使用另一種形式的?when(Object)
?函數(shù),因為編譯器要求括號內不能存在?void
?方法。
例如,存根一個返回類型為?void
?的方法,要求調用時拋出一個異常:
doThrow(new RuntimeException()).when(mockedList).clear();
// Following throws RuntimeException:
mockedList.clear();
監(jiān)視真實對象
前面使用的都是?mock
?出來一個對象。這樣,當?沒有配置/存根?其具體行為的話,結果就會返回?空類型。而如果使用?特務對象(spy
),那么對于?沒有存根?的行為,它會調用?原來對象?的方法??梢园?spy
?想象成局部?mock
。
List list = new LinkedList();
List spy = spy(list);
// Optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);
// Use the spy calls *real* methods
spy.add("one");
spy.add("two");
// Prints "one" - the first element of a list
System.out.println(spy.get(0));
// Size() method was stubbed - 100 is printed
System.out.println(spy.size());
// Optionally, you can verify
verify(spy).add("one");
verify(spy).add("two");
注意:由于 spy 是局部 mock,所以有時候使用 when(Object) 時,無法做到存根作用。此時,就可以考慮使用 doReturn() | Answer() | Throw() 這類方法進行存根:
List list = new LinkedList();
List spy = spy(list);
// Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");
// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);
spy
?并不是?真實對象?的?代理。相反的,它對傳遞過來的?真實對象?進行?克隆。所以,對?真實對象?的任何操作,spy
?對象并不會感知到。同理,對?spy
?對象的任何操作,也不會影響到?真實對象。
當然,如果使用?mock
?進行對象的?局部?mock
,通過?doCallRealMethod() | thenCallRealMethod()
?方法也是可以的:
// You can enable partial mock capabilities selectively on mocks:
Foo mock = mock(Foo.class);
// Be sure the real implementation is 'safe'.
// If real implementation throws exceptions or depends on specific state of the object then you're in trouble.
when(mock.someMethod()).thenCallRealMethod();
測試驅動開發(fā)
以?行為驅動開發(fā)?的格式使用 //given //when //then 注釋為測試用法基石編寫測試用例,這正是?Mockito
?官方編寫測試用例方法,強烈建議使用這種方式測試編寫。
import static org.mockito.BDDMockito.*;
Seller seller = mock(Seller.class);
Shop shop = new Shop(seller);
public void shouldBuyBread() throws Exception {
// Given
given(seller.askForBread()).willReturn(new Bread());
// When
Goods goods = shop.buyBread();
// Then
assertThat(goods, containBread());
}
自定義錯誤校驗輸出信息
// Will print a custom message on verification failure
verify(mock, description("This will print on failure")).someMethod();
// Will work with any verification mode
verify(mock, times(2).description("someMethod should be called twice")).someMethod();
@InjectMock
構造器,方法,成員變量依賴注入 使用?@InjectMock
?注解時,Mockito
?會檢查?類構造器,方法或?成員變量,依據(jù)它們的?類型?進行自動?mock
。
public class InjectMockTest {
@Mock
private User user;
@Mock
private ArticleDatabase database;
@InjectMocks
private ArticleManager manager;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void testInjectMock() {
// Calls addListener with an instance of ArticleListener
manager.initialize();
// Validate that addListener was called
verify(database).addListener(any(ArticleListener.class));
}
public static class ArticleManager {
private User user;
private ArticleDatabase database;
public ArticleManager(User user, ArticleDatabase database) {
super();
this.user = user;
this.database = database;
}
public void initialize() {
database.addListener(new ArticleListener());
}
}
public static class User {
}
public static class ArticleListener {
}
public static class ArticleDatabase {
public void addListener(ArticleListener listener) {
}
}
}
成員變量?manager
?類型為?ArticleManager
,它的上面標識別了?@InjectMocks
。這意味著要?mock
?出?manager
,Mockito
?需要先自動?mock
?出?ArticleManager
?所需的?構造參數(shù)(即:user
?和?database
),最終?mock
?得到一個?ArticleManager
,賦值給?manager
。
參數(shù)捕捉
ArgumentCaptor
?允許在?verify
?的時候獲取?方法參數(shù)內容,這使得我們能在?測試過程?中能對?調用方法參數(shù)?進行?捕捉?并?測試。
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Captor
private ArgumentCaptor<List<String>> captor;
@Test
public void testArgumentCaptor(){
List<String> asList = Arrays.asList("someElement_test", "someElement");
final List<String> mockedList = mock(List.class);
mockedList.addAll(asList);
verify(mockedList).addAll(captor.capture()); // When verify,you can capture the arguments of the calling method
final List<String> capturedArgument = captor.getValue();
assertThat(capturedArgument, hasItem("someElement"));
}
Mocktio的局限
- 不能?
mock
?靜態(tài)方法; - 不能?
mock
?構造器; - 不能?
mock
equals()
?和?hashCode()
?方法。
最后感謝每一個認真閱讀我文章的人,看著粉絲一路的上漲和關注,禮尚往來總是要有的,雖然不是什么很值錢的東西,如果你用得到的話可以直接拿走
文章來源:http://www.zghlxwxcb.cn/news/detail-682809.html
?這些資料,對于從事【軟件測試】的朋友來說應該是最全面最完整的備戰(zhàn)倉庫,這個倉庫也陪伴我走過了最艱難的路程,希望也能幫助到你!凡事要趁早,特別是技術行業(yè),一定要提升技術功底。希望對大家有所幫助…….有需要的小伙伴可以點擊下方小卡片免費領取文章來源地址http://www.zghlxwxcb.cn/news/detail-682809.html
到了這里,關于2023單元測試利器Mockito框架詳解(超詳細~)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!