<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
首先宣告筆者並不是一個TDD開發的擁躉,我傾向於實用主義。TDD有他的用武之地,但不是銀彈,同理BDD(行為驅動開發)也是如此。筆者堅持寫測試的原因只是為了更加方便的重構,當筆者意識到一個模組會被大範圍使用且會面臨多次迭代的時候,筆者就會認證寫測試,並且每次出bug都會用測試先復現。如果一個專案只是一次性的demo那些個啥的測試,一把梭哈得了。
TDD是測試驅動開發(Test-Driven Development)的英文簡稱,是敏捷開發中的一項核心實踐和技術,也是一種設計方法論。TDD的原理是在開發功能程式碼之前,先編寫單元測試用例程式碼,測試程式碼確定需要編寫什麼產品程式碼。TDD雖是敏捷方法的核心實踐,但不只適用於XP(Extreme Programming),同樣可以適用於其他開發方法和過程。
Mockito is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API. Mockito doesn’t give you hangover because the tests are very readable and they produce clean verification errors.
Mockito 是非常不錯框架。它使您可以使用乾淨簡單的 API 編寫漂亮的測試。 Mockito 不會給你帶來宿醉,因為測試非常易讀並且會產生乾淨的驗證錯誤
在開發程式的時候我們需要測試的類不可能都是簡單的類,很多複雜的邏輯往往需要多個前置條件才能測試到。如果為了測試去滿足這些前置條件未免顯得太繁瑣。比如有的邏輯需要進行HTTP請求某個特定服務,我不想在測試的時候去單獨啟動這個服務。這時候我們就可以mock這個http請求,讓其返回一個特定值,以此簡化測試流程。
引包
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>${mockito.version}</version> <scope>test</scope> </dependency>
靜態匯入
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.mockito.Mockito.*;
使用mock方法
LinkedList mockedList = mock(LinkedList.class);
使用spy方法
PDStateMachineNode mock = spy(new PDStateMachineNode());
mock和spy的區別
mock方法和spy方法都可以對物件進行mock。但是前者是接管了物件的全部方法,而後者只是將有樁實現(stubbing)的呼叫進行mock,其餘方法仍然是實際呼叫。大家先這樣理解後面會具體舉例子。
其實就是對要模擬方法進行包裝,設定返回值或者拋異常之類,直接看官方例子。這裡就是對get(0) 和get(1) 進行插樁了
//You can mock concrete classes, not just interfaces LinkedList mockedList = mock(LinkedList.class); //插樁 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. verify(mockedList).get(0);
可以用來驗證某個方法是否呼叫了
這裡使用了官網例子,大致用途就是你可以用它來驗證某個方法被呼叫了沒有。可以很明顯在的看到他在驗證是否呼叫了add和clear
//Let's import Mockito statically so that the code looks clearer import static org.mockito.Mockito.*; //mock creation List mockedList = mock(List.class); //using mock object mockedList.add("one"); mockedList.clear(); //verification verify(mockedList).add("one"); verify(mockedList).clear();
下面看看我自己專案裡面得例子。這裡可以發現我在傳入引數得時候直接是傳入了any(),這也是mockito
提供得,與此對應得還有anyString()
,anyInt()
等等。
大致意思就是驗證runAviatorScript這個方法被呼叫的了應該有一次。
@Test void testOnEntry2() throws NodeExecuteTimeoutException { PDStateMachineNode mock = spy(PDStateMachineNode.class); mock.onEntry(any()); // 預設第二個引數就是times(1),因此這裡可以不寫 verify(mock,times(1)).runAviatorScript(any(),any(),anyString()); }
方便得模擬一次方法得引數
直接上官方例子,上述程式碼也有使用
//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(true); //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));
如果你使用的引數匹配器,那麼所有引數都必須提供引數匹配器,否則會拋異常。
//正確 verify(mock).someMethod(anyInt(), anyString(), eq("third argument")); //錯誤 verify(mock).someMethod(anyInt(), anyString(), "third argument");
就是驗證某個方法呼叫了多少次
//using mock mockedList.add("once"); mockedList.add("twice"); mockedList.add("twice"); mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times"); //following 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, atMostOnce()).add("once"); verify(mockedList, atLeastOnce()).add("three times"); verify(mockedList, atLeast(2)).add("three times"); verify(mockedList, atMost(5)).add("three times");
筆者專案中得例子
@Test void testDELETE() { Assertions.assertTimeout(Duration.ofSeconds(10), () -> { mockStatic.when(() -> HttpRequest.delete("test").execute().body()).thenReturn("success"); String execute = (String) AviatorEvaluator.execute("return DELETE("test");"); Assertions.assertTrue(execute.contains("success")); //在這裡 mockStatic.verify(() -> HttpRequest.delete(anyString()), times(2)); }); }
專案中的例子
當呼叫onExit的時候啥也不幹
@Test void onExit() throws NodeExecuteTimeoutException { StateMachineInterpreter interpreter = new StateMachineInterpreter(); StateMachineNode spy = spy(new StateMachineNode()); //啥也不幹 doNothing().when(spy).onExit(any()); }
有時候在需要驗證某個方法內部呼叫其他方法的順序。筆者例子如下:
這段測試意思就是模擬 PDStateMachineNode
這個類呼叫verify()方法得邏輯。分兩種情況
onCheck方法返回true(doReturn(true).when(mock).onCheck(any())
)
此種情況下,方法呼叫順序應為:onEntry,onCheck,onMatch,onExit
onCheck返回false(doReturn(false).when(mock).onCheck(any())
)
此種情況下,方法呼叫順序應為:onEntry,onCheck,onFail,onExit
PDStateMachineNode mock = spy(new PDStateMachineNode()); doReturn(true).when(mock).onCheck(any()); mock.verify(any()); InOrder inOrder = inOrder(mock); inOrder.verify(mock).onEntry(any()); inOrder.verify(mock).onCheck(any()); inOrder.verify(mock).onMatch(any()); inOrder.verify(mock).onExit(any()); doReturn(false).when(mock).onCheck(any()); mock.verify(any()); InOrder inOrder2 = inOrder(mock); inOrder2.verify(mock).onEntry(any()); inOrder2.verify(mock).onCheck(any()); inOrder2.verify(mock).onFail(any()); inOrder2.verify(mock).onExit(any());
官方建議大部分情況下你應該使用when(),二者區別後文再說。
doReturn()
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", "bar", "qix"); //You have to use doReturn() for stubbing: doReturn("foo", "bar", "qix").when(spy).get(0);
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", "bar", "qix"); //You have to use doReturn() for stubbing: doReturn("foo", "bar", "qix").when(spy).get(0);
doThrow()
doThrow(new RuntimeException()).when(mock).someVoidMethod(); doThrow(RuntimeException.class).when(mock).someVoidMethod();
doAnswer()
doAnswer(new Answer() { public Object answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); Mock mock = invocation.getMock(); return null; }}) .when(mock).someMethod();
doNothing()
doNothing(). doThrow(new RuntimeException()) .when(mock).someVoidMethod(); //does nothing the first time: mock.someVoidMethod(); //throws RuntimeException the next time: mock.someVoidMethod();
doCallRealMethod()
Foo mock = mock(Foo.class); doCallRealMethod().when(mock).someVoidMethod(); // this will call the real implementation of Foo.someVoidMethod() mock.someVoidMethod();
筆者專案中的使用的例子
@Test void step() throws NodeExecuteTimeoutException, NextNodesNotExistException { PDStateMachineNode spy = spy(new PDStateMachineNode()); PDStateMachineNode node = new PDStateMachineNode(); PDStateMachineNode subSpy = spy(node); doReturn(true).when(subSpy).verify(any()); doReturn(List.of(subSpy)).when(spy).getNextNodes(); PDStateMachineNode step = spy.step(any(PointData.class)); Assertions.assertEquals(subSpy, step); when(spy.getNextNodes()).thenReturn(new ArrayList<>()); doReturn(true).when(spy).isTerminalNode(); Assertions.assertThrows(NextNodesNotExistException.class, () -> spy.step(any(PointData.class))); doReturn(new ArrayList<>()).when(spy).getNextNodes(); doReturn(false).when(spy).isTerminalNode(); Assertions.assertEquals(spy, spy.step(any(PointData.class))); }
直接看筆者專案中的例子,注意這裡下面的例子有些許不同,RETURNS_DEEP_STUBS其實是用來進行巢狀模擬的。因為HttpRequest.get("test").execute().body() 是一個鏈式的呼叫,實際上涉及了多個類的模擬。如果你沒有這個需求就可以不加。
@BeforeAll void init() { mockStatic = mockStatic(HttpRequest.class, RETURNS_DEEP_STUBS); } @Test void testGET() { Assertions.assertTimeout(Duration.ofSeconds(10), () -> { mockStatic.when(() -> HttpRequest.get("test").execute().body()).thenReturn("success"); String execute = (String) AviatorEvaluator.execute("return GET("test");"); Assertions.assertTrue(execute.contains("success")); mockStatic.verify(() -> HttpRequest.get(anyString()), times(2)); }); }
someMock.when(() -> Files.delete(fileToDelete)).thenAnswer((Answer<Void>) invocation -> null); // 也可以是下面這個 // someMock.when(() -> Files.delete(fileToDelete)).thenAnswer(Answers.RETURNS_DEFAULTS);
mock方法和spy方法都可以對物件進行mock。但是前者是接管了物件的全部方法,而後者只是將有樁實現(stubbing)的呼叫進行mock,其餘方法仍然是實際呼叫。
使用mock
PDStateMachineNode mock = mock(PDStateMachineNode.class); mock.onEntry(any()); verify(mock, times(1)).runAviatorScript(any(), any(), anyString());
拋錯如下,意思就是runAviatorScript沒有被呼叫,因此驗證失敗。實際上筆者在onEntry內部是呼叫了runAviatorScript方法的
Wanted but not invoked: pDStateMachineNode.runAviatorScript( <any>, <any>, <any string> ); -> at core.state.GenericStateMachineNode.runAviatorScript(GenericStateMachineNode.java:78)
使用spy,則無任務錯誤。
@Test void testOnEntry2() throws NodeExecuteTimeoutException { PDStateMachineNode mock = spy(PDStateMachineNode.class); mock.onEntry(any()); verify(mock, times(1)).runAviatorScript(any(), any(), anyString()); }
從上述對比就可以理解mock和spy的區別,對於未指定mock的方法,spy預設會呼叫真實的方法,有返回值的返回真實的返回值,而mock預設不執行,有返回值的,預設返回null。具體細節筆者也沒有深究比如實際上mock也能做到類似psy的效果
● when(...) thenReturn(...)會呼叫真實的方法,如果你不想呼叫真實的方法而是想要mock的話,就不要使用這個方法。
● doReturn(...) when(...) 不會呼叫真實方法
因此針對區別一般情況下如果時第三方庫得程式碼在需要測試得方法則可以使用 do...return
進行略過,自己呼叫自己得方法則建議使用 when...return
。但是有時候呼叫得方法需要一些特殊得環境才能起作用,那麼也能使用 do..return
,亦或者被呼叫得方法已經測試過了也可以使用 do..return
。下面看二者區別得例子。
例子:
@Override public void onEntry(T event) throws NodeExecuteTimeoutException { System.out.println("hello"); this.runAviatorScript(this.onEntry, event, "onEntry"); }
測試when..return...:
@Test void testOnEntry2() throws NodeExecuteTimeoutException { PDStateMachineNode mock = spy(PDStateMachineNode.class); when(mock.onCheck(any())).thenReturn(true); mock.onEntry(any()); verify(mock, times(1)).runAviatorScript(any(), any(), anyString()); }
結果可以看到輸出得hello
測試do...return...
@Test void testOnEntry2() throws NodeExecuteTimeoutException { PDStateMachineNode mock = spy(PDStateMachineNode.class); doNothing().when(mock).onEntry(any()); mock.onEntry(any()); verify(mock, times(1)).runAviatorScript(any(), any(), anyString()); }
結果可以看到不僅沒輸出還報錯了,為什麼呢?因為 do..return
實際上不執行包裝得方法,也就沒有執行onEntry方法,自然裡面 runAviatorScript
也就沒有執行,因此就會導致驗證錯誤。
行為驅動開發(英語:Behavior-driven development,縮寫BDD)是一種敏捷軟體開發的技術,它鼓勵軟體專案中的開發者、QA和非技術人員或商業參與者之間的共同作業。BDD最初是由Dan North在2003年命名,它包括驗收測試和客戶測試驅動等的極限程式設計的實踐,作為對測試驅動開發的迴應。在過去數年裡,它得到了很大的發展。行為驅動測試的開發風格使用//given//when//then 作為測試方法的基本部分。
其實還是比較簡單的,粗淺的理解就是換了幾個API。
直接看幾個官方的例子
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()); }
可以發現willThrow就像之前的doThrow差不多
//given willThrow(new RuntimeException("boo")).given(mock).foo(); //when Result result = systemUnderTest.perform(); //then assertEquals(failure, result);
person.ride(bike); person.ride(bike); then(person).should(times(2)).ride(bike); then(person).shouldHaveNoMoreInteractions(); then(police).shouldHaveZeroInteractions();
InOrder inOrder = inOrder(person); person.drive(car); person.ride(bike); person.ride(bike); then(person).should(inOrder).drive(car); then(person).should(inOrder, times(2)).ride(bike);
這裡不僅模擬了方法的返回值,還模擬了springboot中controller的呼叫
@Test void shouldNotListRoles() throws Exception { given(roleService.findAll()).willReturn(new ArrayList<>()); ResultActions actions = this.mvc.perform(get("/api/role/getRoles")); actions.andExpect(status().isOk()).andReturn().getResponse().setCharacterEncoding("UTF-8"); actions.andDo(print()).andExpect(jsonPath("$.data.length()").value(Matchers.is(0))); } @Test void shouldCreateRole() throws Exception { objectMapper.registerModule(new JavaTimeModule()); objectMapper.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS); Role role = new Role(null, "mfine", LocalDateTime.now(), "", 0, null, "admin"); // 這裡 也使用了引數匹配器 given(roleService.insertSelective(BDDMockito.any())).willReturn(1); ResultActions actions = this.mvc.perform(post("/api/role/createRole").content(objectMapper.writeValueAsString(role)) .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON)); actions.andExpect(status().isOk()).andReturn().getResponse().setCharacterEncoding("UTF-8"); actions.andExpect(ResultMatcher.matchAll(result -> { Assert.assertTrue(result.getResponse().getContentAsString().contains("success")); })); }
完整的測試時重構得底氣,當沒有測試得時候一切重構都是扯淡。程式出bug之後第一件事應該是復現bug,增補測試然後再是修復bug,如果是線上緊急情況那也應該在時候補充測試。但是測試也不是銀彈,所謂的TDD和BDD也要看情況使用,沒有萬能方案只有適合方法。
以上就是使用mockito編寫測試用例教學的詳細內容,更多關於mockito測試用例教學的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45