1、為什么要用mock
我的一本書的解釋:
(1)創建所需的DB數據可能需要很長時間,如:調用別的接口,模擬很多數據
(2)調用第三方API接口,測試很慢,
(3)編寫滿足所有外部依賴的測試可能很復雜,復雜到不值得編寫,Mock模擬內部或外部依賴可以幫助我們解決這些問題
另一本TDD書的解釋:
(1)對象的結果不確定,如每獲取當前時間,得到的結果都不一樣,無法符合我們的預期;
(2)實現這個接口的對象不存在;
(3)對象速度緩慢
對於TDD還有一個更重要原因:通過模擬可以隔離當前方法使用的的所有依賴,讓我們更加專注於單個單元,忽略其調用的代碼的內部工作原理
一本博客的干貨:
(1)Mock可以用來解除測試對象對外部服務的依賴(比如數據庫,第三方接口等),使得測試用例可以獨立運行。不管是傳統的單體應用,還是現在流行的微服務,這點都特別重要,因為任何外部依賴的存在都會極大的限制測試用例的可遷移性和穩定性。
(2)Mock的第二個好處是替換外部服務調用,提升測試用例的運行速度。任何外部服務調用至少是跨進程級別的消耗,甚至是跨系統、跨網絡的消耗,而Mock可以把消耗降低到進程內。比如原來一次秒級的網絡請求,通過Mock可以降至毫秒級,整整3個數量級的差別。
(3)Mock的第三個好處是提升測試效率。這里說的測試效率有兩層含義。第一層含義是單位時間運行的測試用例數,這是運行速度提升帶來的直接好處。而第二層含義是一個測試人員單位時間創建的測試用例數。
以單體應用為例,隨着業務復雜度的上升,為了運行一個測試用例可能需要准備很多測試數據,與此同時還要盡量保證多個測試用例之間的測試數據互不干擾。為了做到這一點,測試人員往往需要花費大量的時間來維護一套可運行的測試數據。有了Mock之后,由於去除了測試用例之間共享的數據庫依賴,測試人員就可以針對每一個或者每一組測試用例設計一套獨立的測試數據,從而很容易的做到不同測試用例之間的數據隔離性。而對於微服務,由於一個微服務可能級聯依賴很多其他的微服務,運行一個測試用例甚至需要跨系統准備一套測試數據,如果沒有Mock,基本上可以說是不可能的。因此,不管是單體應用還是微服務,有了Mock之后,QE就可以省去大量的准備測試數據的時間,專注於測試用例本身,自然也就提升了單人的測試效率。
現如今比較流行的Mock工具如jMock 、EasyMock 、Mockito等都有一個共同的缺點:不能mock靜態、final、私有方法等。而PowerMock能夠完美的彌補以上三個Mock工具的不足
2、實戰:
好了,我們用PoweMockito框架,直接上代碼:如何mock私有方法,靜態方法,測試私有方法,final類
依賴:
<dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>1.7.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>1.7.4</version> <scope>test</scope> </dependency>
powermock可能與mockito有版本沖突,我們可以講mockito版本改成2.8.47 :
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <mockito.version>2.8.47</mockito.version> </properties>
service層代碼:一個簡單的保存,返回user,這里就不調用持久層了
@Override public User save(User user) { return user; }
controller層代碼:一些簡單的調用,接下來寫個test 調用controller方法,mock service里面的保存,mock controller的私有方法和靜態方法,測試controller的private的私有方法
package com.example.demo.controller; import com.example.demo.entity.User; import com.example.demo.service.UserService; public final class UserController { private final UserService service; public UserController(UserService service) { this.service = service; } public User saveUser(User user) { User save = service.save(user); return save; } public String returnName(){ return getStaticName("ljw1"); } public static String getStaticName(String name) { return "A_" + name; } public String getPrivateName(String name) { if (publicCheck()){ return "public 被mock 了"; } if (check(name)){ return "private 被mock 了"; } return "A_" + name; } public boolean publicCheck() { return false; } private boolean check(String name) { return false; } private String say(String content) { return "ljw say " + content; } }
test:測試,注釋寫的很清楚,把代碼拷貝過去,就可以用了,我已經驗證過,user類就一個字段id,不附代碼了,注意:測試private方法和mock private方法的區別
package com.example.demo.demo1; import com.example.demo.controller.UserController; import com.example.demo.entity.User; import com.example.demo.service.impl.UserServiceImpl; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) @PrepareForTest(UserController.class) public class LastMock { @Mock private UserServiceImpl serviceImpl; @InjectMocks private UserController controller; /** * mock service的保存方法 */ @Test public void mockSave() { User user1 = new User(); User user2 = new User(); user1.setId("1"); user2.setId("2"); Mockito.when(serviceImpl.save(user1)).thenReturn(user2); //當調用service的save()時,mock讓他返回user2 User saveUser = controller.saveUser(user1); //調用 Mockito.verify(serviceImpl,Mockito.times(1)).save(user1);//verify驗證mock次數 assertEquals(user2, saveUser);//斷言是否mock返回的是user2 } /** * mock spy public方法 * @throws Exception xx */ @Test public void spy_public_method() throws Exception { UserController spy = PowerMockito.spy(controller); //監視controller的publicCheck方法,讓他返回true Mockito.when(spy.publicCheck()).thenReturn(true); String name = spy.getPrivateName("ljw");//執行該方法 assertEquals("public 被mock 了", name);//驗證 } /** * mock私有方法 * @throws Exception xx */ @Test public void spy_private_method() throws Exception { UserController spy = PowerMockito.spy(controller); PowerMockito.when(spy, "check", any()).thenReturn(true);//私有方法mockito不行了,需要用無所不能的PowerMock監視spy String name = spy.getPrivateName("ljw"); assertEquals("private 被mock 了", name); } /** * mock 靜態方法 */ @Test public void mockStaticMethod() { PowerMockito.mockStatic(UserController.class);//mock靜態方法 when(UserController.getStaticName(any())).thenReturn("hi"); String staticName = UserController.getStaticName("ljw");//執行 assertEquals("hi", staticName);//驗證 } @Test public void mockStaticMethod_2() { PowerMockito.mockStatic(UserController.class); when(UserController.getStaticName(any())).thenReturn("hi"); String staticName = controller.returnName();//通過returnName()調用,看能否被mock assertEquals("hi", staticName); } /** * 測試私有方法一 * @throws InvocationTargetException xx * @throws IllegalAccessException xx */ @Test public void testPrivateMethod() throws InvocationTargetException, IllegalAccessException { Method method = PowerMockito.method(UserController.class, "say", String.class); Object say = method.invoke(controller, "hi"); assertEquals("ljw say hi", say); } /** * 測試私有方法二 * @throws Exception xx */ @Test public void testPrivateMethod_2() throws Exception { Object say = Whitebox.invokeMethod(controller, "say", "hi"); assertEquals("ljw say hi", say); } }
package cn.vv.web.vdc.core.util; import cn.vv.fw.common.api.R; import cn.vv.fw.common.exception.ServiceException; import org.assertj.core.api.Assertions; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.modules.junit4.PowerMockRunner; import java.util.Arrays; import java.util.Collection; /** * @see cn.vv.web.vdc.core.util.BizAssert Tester. * @author zhongmaming * @date 03/10/2020 * @version 1.0 */ @RunWith(PowerMockRunner.class) public class BizAssertTest{ // @InjectMocks private BizAssert bizAssert; @Before public void before() throws Exception { // PowerMockito.mockStatic(BizAssert.class); } @After public void after() throws Exception { } /** * @see cn.vv.web.vdc.core.util.BizAssert#isTrue(Boolean b, String msg) */ @Test public void testIsTrue() throws Exception { // Mockito.doAnswer(invocation -> { // Object[] args = invocation.getArguments(); // return "called with arguments: " + args; // }).when(Mockito.mock(BizAssert.class)).isTrue(true, "success"); // PowerMockito.when(bizAssert, "isTrue", false, "error").thenThrow(new ServiceException("error")); // PowerMockito.doThrow(new ServiceException("error")).when(Mockito.mock(BizAssert.class)).isTrue(false, "error"); // PowerMockito.doNothing().when(Mockito.mock(BizAssert.class)).isTrue(true, ""); BizAssert.isTrue(Boolean.TRUE, "success"); // PowerMockito.verifyStatic(BizAssert.class); try { BizAssert.isTrue(Boolean.FALSE, "error"); Assert.fail("isTrue test fail"); } catch (ServiceException e) { Assertions.assertThat(e).isInstanceOf(ServiceException.class); Assertions.assertThat(e.getMessage()).isEqualTo("error"); } } /** * @see cn.vv.web.vdc.core.util.BizAssert#isFalse(Boolean b, String msg) */ @Test public void testIsFalse() throws Exception { // PowerMockito.doNothing().when(Mockito.mock(BizAssert.class)).isFalse(false, ""); BizAssert.isFalse(Boolean.FALSE, "success"); // PowerMockito.verifyStatic(BizAssert.class); try { BizAssert.isFalse(true, "error"); Assert.fail("isFalse test fail"); } catch (ServiceException e) { Assertions.assertThat(e).isInstanceOf(ServiceException.class); Assertions.assertThat(e.getMessage()).isEqualTo("error"); } } /** * @see cn.vv.web.vdc.core.util.BizAssert#isNull(Object a, String msg) */ @Test public void testIsNull() throws Exception { BizAssert.isNull(null, "success"); // PowerMockito.verifyStatic(BizAssert.class); try { BizAssert.isNull("hello", "error"); Assert.fail("isNull test fail"); } catch (ServiceException e) { Assertions.assertThat(e).isInstanceOf(ServiceException.class); Assertions.assertThat(e.getMessage()).isEqualTo("error"); } } /** * @see cn.vv.web.vdc.core.util.BizAssert#notNull(Object a, String msg) */ @Test public void testNotNull() throws Exception { BizAssert.notNull("hello", "success"); // PowerMockito.verifyStatic(BizAssert.class); try { BizAssert.notNull(null, "error"); Assert.fail("notNull test fail"); } catch (ServiceException e) { Assertions.assertThat(e).isInstanceOf(ServiceException.class); Assertions.assertThat(e.getMessage()).isEqualTo("error"); } } /** * @see cn.vv.web.vdc.core.util.BizAssert#isSuccess(R r) */ @Test public void testIsSuccess() throws Exception { BizAssert.isSuccess(R.data(null)); // PowerMockito.verifyStatic(BizAssert.class); try { BizAssert.isSuccess(R.fail("error")); Assert.fail("isSuccess test fail"); } catch (ServiceException e) { Assertions.assertThat(e).isInstanceOf(ServiceException.class); Assertions.assertThat(e.getMessage()).isEqualTo("error"); } } /** * @see cn.vv.web.vdc.core.util.BizAssert#isNotEmpty(Collection collection, String msg) */ @Test public void testIsNotEmpty() throws Exception { BizAssert.isNotEmpty(Arrays.asList("a"), "success"); // PowerMockito.verifyStatic(BizAssert.class); try { BizAssert.isNotEmpty(Arrays.asList(), "error"); Assert.fail("isNotEmpty test fail"); } catch (ServiceException e) { Assertions.assertThat(e).isInstanceOf(ServiceException.class); Assertions.assertThat(e.getMessage()).isEqualTo("error"); } } }