Mockito使用指南


轉載請標明出處:http://blog.csdn.net/shensky711/article/details/52771493 
本文出自: 【HansChen的博客】

 

 


mock和Mockito的關系

在軟件開發中提及”mock”,通常理解為模擬對象。

為什么需要模擬? 在我們一開始學編程時,我們所寫的對象通常都是獨立的,並不依賴其他的類,也不會操作別的類。但實際上,軟件中是充滿依賴關系的,比如我們會基於service類寫操作類,而service類又是基於數據訪問類(DAO)的,依次下去,形成復雜的依賴關系。

單元測試的思路就是我們想在不涉及依賴關系的情況下測試代碼。這種測試可以讓你無視代碼的依賴關系去測試代碼的有效性。核心思想就是如果代碼按設計正常工作,並且依賴關系也正常,那么他們應該會同時工作正常。

有些時候,我們代碼所需要的依賴可能尚未開發完成,甚至還不存在,那如何讓我們的開發進行下去呢?使用mock可以讓開發進行下去,mock技術的目的和作用就是模擬一些在應用中不容易構造或者比較復雜的對象,從而把測試與測試邊界以外的對象隔離開

我們可以自己編寫自定義的Mock對象實現mock技術,但是編寫自定義的Mock對象需要額外的編碼工作,同時也可能引入錯誤。現在實現mock技術的優秀開源框架有很多,Mockito就是一個優秀的用於單元測試的mock框架。Mockito已經在github上開源,詳細請點擊:https://github.com/mockito/mockito

除了Mockito以外,還有一些類似的框架,比如:

  • EasyMock:早期比較流行的MocK測試框架。它提供對接口的模擬,能夠通過錄制、回放、檢查三步來完成大體的測試過程,可以驗證方法的調用種類、次數、順序,可以令 Mock 對象返回指定的值或拋出指定異常
  • PowerMock:這個工具是在EasyMock和Mockito上擴展出來的,目的是為了解決EasyMock和Mockito不能解決的問題,比如對static, final, private方法均不能mock。其實測試架構設計良好的代碼,一般並不需要這些功能,但如果是在已有項目上增加單元測試,老代碼有問題且不能改時,就不得不使用這些功能了
  • JMockit:JMockit 是一個輕量級的mock框架是用以幫助開發人員編寫測試程序的一組工具和API,該項目完全基於 Java 5 SE 的 java.lang.instrument 包開發,內部使用 ASM 庫來修改Java的Bytecode

Mockito已經被廣泛應用,所以這里重點介紹Mockito。

Mockito使用舉例

這里我們直接通過一個代碼來說明mockito對單元測試的幫助,代碼有三個類,分別如下: 
Person類:

public class Person { private final int id; private final String name; public Person(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; } }

PersonDAO:

public interface PersonDao { Person getPerson(int id); boolean update(Person person); }

PersonService:

public class PersonService { private final PersonDao personDao; public PersonService(PersonDao personDao) { this.personDao = personDao; } public boolean update(int id, String name) { Person person = personDao.getPerson(id); if (person == null) { return false; } Person personUpdate = new Person(person.getId(), name); return personDao.update(personUpdate); } }

在這里,我們要進行測試的是PersonService類的update方法,我們發現,update方法依賴PersonDAO,在開發過程中,PersonDAO很可能尚未開發完成,所以我們測試PersonService的時候,所以該怎么測試update方法呢?連接口都還沒實現,怎么知道返回的是true還是false?在這里,我們可以這樣認為,單元測試的思路就是我們想在不涉及依賴關系的情況下測試代碼。這種測試可以讓你無視代碼的依賴關系去測試代碼的有效性。核心思想就是如果代碼按設計正常工作,並且依賴關系也正常,那么他們應該會同時工作正常。所以我們的做法是mock一個PersonDAO對象,至於實際環境中,PersonDAO行為是否能按照預期執行,比如update是否能成功,查詢是否返回正確的數據,就跟PersonService沒關系了。PersonService的單元測試只測試自己的邏輯是否有問題

下面編寫測試代碼:

public class PersonServiceTest { private PersonDao mockDao; private PersonService personService; @Before public void setUp() throws Exception { //模擬PersonDao對象 mockDao = mock(PersonDao.class); when(mockDao.getPerson(1)).thenReturn(new Person(1, "Person1")); when(mockDao.update(isA(Person.class))).thenReturn(true); personService = new PersonService(mockDao); } @Test public void testUpdate() throws Exception { boolean result = personService.update(1, "new name"); assertTrue("must true", result); //驗證是否執行過一次getPerson(1) verify(mockDao, times(1)).getPerson(eq(1)); //驗證是否執行過一次update verify(mockDao, times(1)).update(isA(Person.class)); } @Test public void testUpdateNotFind() throws Exception { boolean result = personService.update(2, "new name"); assertFalse("must true", result); //驗證是否執行過一次getPerson(1) verify(mockDao, times(1)).getPerson(eq(1)); //驗證是否執行過一次update verify(mockDao, never()).update(isA(Person.class)); } }

我們對PersonDAO進行mock,並且設置stubbing,stubbing設置如下:

  • 當getPerson方法傳入1的時候,返回一個Person對象,否則默認返回空
  • 當調update方法的時候,返回true

我們驗證了兩種情況:

  • 更新id為1的Person的名字,預期:能在DAO中找到Person並更新成功
  • 更新id為2的Person的名字,預期:不能在DAO中找到Person,更新失敗

這樣,根據PersonService的update方法的邏輯,通過這兩個test case之后,我們認為代碼是沒有問題的。mockito在這里扮演了一個為我們模擬DAO對象,並且幫助我們驗證行為(比如驗證是否調用了getPerson方法及update方法)的角色

Android Studio工程配置Mockito

Android Studio中使用Mockito非常簡單,只需要在build.gradle文件中加入依賴即可。如圖:

dependencies {
    ... testCompile 'org.mockito:mockito-core:1.10.19' ... }

Mockito使用方法

Mockito的使用,有詳細的api文檔,具體可以查看:http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html,下面是整理的一些常用的使用方式。

驗證行為

一旦創建,mock會記錄所有交互,你可以驗證所有你想要驗證的東西

@Test public void testVerify() throws Exception { //mock creation List mockedList = mock(List.class); //using mock object mockedList.add("one"); mockedList.add("two"); mockedList.add("two"); mockedList.clear(); //verification verify(mockedList).add("one");//驗證是否調用過一次 mockedList.add("one")方法,若不是(0次或者大於一次),測試將不通過 verify(mockedList, times(2)).add("two"); //驗證調用過2次 mockedList.add("two")方法,若不是,測試將不通過 verify(mockedList).clear();//驗證是否調用過一次 mockedList.clear()方法,若沒有(0次或者大於一次),測試將不通過 }

 

Stubbing

@Test public void testStubbing() throws Exception { //你可以mock具體的類,而不僅僅是接口 LinkedList mockedList = mock(LinkedList.class); //設置樁 when(mockedList.get(0)).thenReturn("first"); when(mockedList.get(1)).thenThrow(new RuntimeException()); //打印 "first" System.out.println(mockedList.get(0)); //這里會拋runtime exception System.out.println(mockedList.get(1)); //這里會打印 "null" 因為 get(999) 沒有設置 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); }

對於stubbing,有以下幾點需要注意:

  • 對於有返回值的方法,mock會默認返回null、空集合、默認值。比如,為int/Integer返回0,為boolean/Boolean返回false
  • stubbing可以被覆蓋,但是請注意覆蓋已有的stubbing有可能不是很好
  • 一旦stubbing,不管調用多少次,方法都會永遠返回stubbing的值
  • 當你對同一個方法進行多次stubbing,最后一次stubbing是最重要的

參數匹配

@Test public void testArgumentMatcher() throws Exception { LinkedList mockedList = mock(LinkedList.class); //用內置的參數匹配器來stub when(mockedList.get(anyInt())).thenReturn("element"); //打印 "element" System.out.println(mockedList.get(999)); //你也可以用參數匹配器來驗證,此處測試通過 verify(mockedList).get(anyInt()); //此處測試將不通過,因為沒調用get(33) verify(mockedList).get(eq(33)); }

如果你使用了參數匹配器,那么所有參數都應該使用參數匹配器

 verify(mock).someMethod(anyInt(), anyString(), eq("third argument")); //上面是正確的,因為eq返回參數匹配器 verify(mock).someMethod(anyInt(), anyString(), "third argument"); //上面將會拋異常,因為第三個參數不是參數匹配器,一旦使用了參數匹配器來驗證,那么所有參數都應該使用參數匹配

驗證准確的調用次數,最多、最少、從未等

@Test public void testInvocationTimes() throws Exception { LinkedList mockedList = mock(LinkedList.class); //using mock mockedList.add("once"); mockedList.add("twice"); mockedList.add("twice"); mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times"); //下面兩個是等價的, 默認使用times(1) verify(mockedList).add("once"); verify(mockedList, times(1)).add("once"); //驗證准確的調用次數 verify(mockedList, times(2)).add("twice"); verify(mockedList, times(3)).add("three times"); //從未調用過. never()是times(0)的別名 verify(mockedList, never()).add("never happened"); //用atLeast()/atMost()驗證 verify(mockedList, atLeastOnce()).add("three times"); //下面這句將不能通過測試 verify(mockedList, atLeast(2)).add("five times"); verify(mockedList, atMost(5)).add("three times"); }

為void方法拋異常

@Test public void testVoidMethodsWithExceptions() throws Exception { LinkedList mockedList = mock(LinkedList.class); doThrow(new RuntimeException()).when(mockedList).clear(); //下面會拋RuntimeException mockedList.clear(); }

驗證調用順序

@Test public void testVerificationInOrder() throws Exception { // A. Single mock whose methods must be invoked in a particular order List singleMock = mock(List.class); //使用單個mock對象 singleMock.add("was added first"); singleMock.add("was added second"); //創建inOrder InOrder inOrder = inOrder(singleMock); //驗證調用次數,若是調換兩句,將會出錯,因為singleMock.add("was added first")是先調用的 inOrder.verify(singleMock).add("was added first"); inOrder.verify(singleMock).add("was added second"); // 多個mock對象 List firstMock = mock(List.class); List secondMock = mock(List.class); //using mocks firstMock.add("was called first"); secondMock.add("was called second"); //創建多個mock對象的inOrder inOrder = inOrder(firstMock, secondMock); //驗證firstMock先於secondMock調用 inOrder.verify(firstMock).add("was called first"); inOrder.verify(secondMock).add("was called second"); }

驗證mock對象沒有產生過交互

@Test public void testInteractionNeverHappened() { List mockOne = mock(List.class); List mockTwo = mock(List.class); //測試通過 verifyZeroInteractions(mockOne, mockTwo); mockOne.add(""); //測試不通過,因為mockTwo已經發生過交互了 verifyZeroInteractions(mockOne, mockTwo); }

查找是否有未驗證的交互

不建議過多使用,api原文:A word of warning: Some users who did a lot of classic, expect-run-verify mocking tend to use verifyNoMoreInteractions() very often, even in every test method. verifyNoMoreInteractions() is not recommended to use in every test method. verifyNoMoreInteractions() is a handy assertion from the interaction testing toolkit. Use it only when it’s relevant. Abusing it leads to overspecified, less maintainable tests.

@Test public void testFindingRedundantInvocations() throws Exception { List mockedList = mock(List.class); //using mocks mockedList.add("one"); mockedList.add("two"); verify(mockedList).add("one"); //驗證失敗,因為mockedList.add("two")尚未驗證 verifyNoMoreInteractions(mockedList); }

@Mock注解

  • 減少代碼
  • 增強可讀性
  • 讓verify出錯信息更易讀,因為變量名可用來描述標記mock對象
public class MockTest { @Mock List<String> mockedList; @Before public void initMocks() { //必須,否則注解無效 MockitoAnnotations.initMocks(this); } @Test public void testMock() throws Exception { mockedList.add("one"); verify(mockedList).add("one"); } }

根據調用順序設置不同的stubbing

private interface MockTest { String someMethod(String arg); } @Test public void testStubbingConsecutiveCalls() throws Exception { MockTest mock = mock(MockTest.class); when(mock.someMethod("some arg")).thenThrow(new RuntimeException("")).thenReturn("foo"); //第一次調用,拋RuntimeException mock.someMethod("some arg"); //第二次調用返回foo System.out.println(mock.someMethod("some arg")); //后續繼續調用,返回“foo”,以最后一個stub為准 System.out.println(mock.someMethod("some arg")); //下面是一個更簡潔的寫法 when(mock.someMethod("some arg")).thenReturn("one", "two", "three"); }

doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod()等用法

@Test public void testDoXXX() throws Exception { List mockedList = mock(List.class); doThrow(new RuntimeException()).when(mockedList).clear(); //以下會拋異常 mockedList.clear(); }

spy監視真正的對象

  • spy是創建一個拷貝,如果你保留原始的list,並用它來進行操作,那么spy並不能檢測到其交互
  • spy一個真正的對象+試圖stub一個final方法,這樣是會有問題的
@Test public void testSpy() throws Exception { List list = new LinkedList(); List spy = spy(list); //可選的,你可以stub某些方法 when(spy.size()).thenReturn(100); //調用"真正"的方法 spy.add("one"); spy.add("two"); //打印one System.out.println(spy.get(0)); //size()方法被stub了,打印100 System.out.println(spy.size()); //可選,驗證spy對象的行為 verify(spy).add("one"); verify(spy).add("two"); //下面寫法有問題,spy.get(10)會拋IndexOutOfBoundsException異常 when(spy.get(10)).thenReturn("foo"); //可用以下方式 doReturn("foo").when(spy).get(10); }

 

為未stub的方法設置默認返回值

@Test public void testDefaultValue() throws Exception { List listOne = mock(List.class, Mockito.RETURNS_SMART_NULLS); List listTwo = mock(List.class, new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { // TODO: 2016/6/13 return default value here return null; } }); }

參數捕捉

@Test public void testCapturingArguments() throws Exception { List mockedList = mock(List.class); ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class); mockedList.add("John"); //驗證后再捕捉參數 verify(mockedList).add(argument.capture()); //驗證參數 assertEquals("John", argument.getValue()); }

 

真正的部分模擬(TODO:尚未搞清楚啥意思。。。)

    //you can create partial mock with spy() method: List list = spy(new LinkedList()); //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();

重置mocks

Don’t harm yourself. reset() in the middle of the test method is a code smell (you’re probably testing too much).

@Test public void testReset() throws Exception { List mock = mock(List.class); when(mock.size()).thenReturn(10); mock.add(1); reset(mock); //從這開始,之前的交互和stub將全部失效 }

Serializable mocks

WARNING: This should be rarely used in unit testing.

@Test public void testSerializableMocks() throws Exception { List serializableMock = mock(List.class, withSettings().serializable()); }

更多的注解:@Captor, @Spy, @InjectMocks

  • @Captor 創建ArgumentCaptor
  • @Spy 可以代替spy(Object).
  • @InjectMocks 如果此注解聲明的變量需要用到mock對象,mockito會自動注入mock或spy成員
//可以這樣寫 @Spy BeerDrinker drinker = new BeerDrinker(); //也可以這樣寫,mockito會自動實例化drinker @Spy BeerDrinker drinker; //會自動實例化 @InjectMocks LocalPub;

超時驗證

private interface TimeMockTest { void someMethod(); } @Test public void testTimeout() throws Exception { TimeMockTest mock = mock(TimeMockTest.class); //測試程序將會在下面這句阻塞100毫秒,timeout的時候再進行驗證是否執行過someMethod() verify(mock, timeout(100)).someMethod(); //和上面代碼等價 verify(mock, timeout(100).times(1)).someMethod(); //阻塞100ms,timeout的時候再驗證是否剛好執行了2次 verify(mock, timeout(100).times(2)).someMethod(); //timeout的時候,驗證至少執行了2次 verify(mock, timeout(100).atLeast(2)).someMethod(); //timeout時間后,用自定義的檢驗模式驗證someMethod() VerificationMode yourOwnVerificationMode = new VerificationMode() { @Override public void verify(VerificationData data) { // TODO: 2016/12/4 implement me } }; verify(mock, new Timeout(100, yourOwnVerificationMode)).someMethod(); }

查看是否mock或者spy

   Mockito.mockingDetails(someObject).isMock();
   Mockito.mockingDetails(someObject).isSpy();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM