摘要:概述在使用單元測試時(shí)經(jīng)常會(huì)遇到某些依賴了外部資源,或者想主動(dòng)繞過真正的方法執(zhí)行返回結(jié)果而快速得到單元測試最終的期望結(jié)果,可能有以下兩種場景,對于,設(shè)單元測試的方法是的方法和方法,在執(zhí)行和方法時(shí)都會(huì)調(diào)用的不同方法,即依賴了一個(gè)場景是完全對進(jìn)行
概述
在使用單元測試時(shí)經(jīng)常會(huì)遇到某些dependency依賴了外部資源,或者想主動(dòng)繞過真正的方法執(zhí)行mock返回結(jié)果而快速得到單元測試最終的期望結(jié)果,可能有以下兩種場景,
對于TestCase A,設(shè)單元測試的方法是Service A的execute1方法和execute2方法,在執(zhí)行execute1和execute2方法時(shí)都會(huì)調(diào)用ServiceB的不同方法,即ServiceA依賴了ServiceB;一個(gè)場景是完全對ServiceB進(jìn)行Mock,如單元測試ServiceA#execute1方法時(shí)都通過Mock返回結(jié)果;一個(gè)場景是部分ServiceB的方法執(zhí)行真實(shí)的業(yè)務(wù)邏輯(如查詢數(shù)據(jù)庫),一部分方法執(zhí)行Mock返回結(jié)果,或Spy,如如單元測試ServiceA#execute2方法時(shí),只mock ServiceB#b2結(jié)果,真正執(zhí)行ServiceB#b1方法。
當(dāng)對ServiceA的方法執(zhí)行單元測試時(shí),如ServiceA -> ServiceB,此時(shí)對ServiceB進(jìn)行Mock,然后將其設(shè)置到ServiceA的屬性中;后續(xù)ServiceA調(diào)用ServiceB的方法都降得到Mock后的結(jié)果;而對于ServiceB對象的本來的依賴本案暫且將其忽略,后續(xù)改進(jìn);
思路是在TestCase中依賴ServiceA的同時(shí)標(biāo)示Mock ServiceB,待TestCase依賴注入完成后,新建ServiceB的Mock對象替換ServiceA中的ServiceB依賴;
@TestExecutionListeners({MockitoDependencyInjectionTestExecutionListener.class}) public class AServiceMockTest extends BaseTest { @Mock private BService bservice; @Autowired private AService aservice; @Before public void setup(){ doReturn("mock").when(bservice).b1(); } @Test public void test() { a.execute1(); } } @Service public class AServiceImpl implements AService { @Autowired private BService bservice; @Override public String execute1() { return bservice.b1(); //will return mock after Mock } }
當(dāng)a.execute()執(zhí)行時(shí)將調(diào)用aservice的屬性bservice的b1方法,返回結(jié)果就是在setup方法中指定的結(jié)果;
監(jiān)聽TestCase的Service的依賴Bean當(dāng)對ServiceA進(jìn)行單元測試時(shí),依賴了ServiceB,需要獲取ServiceB的b1方法的真正執(zhí)行結(jié)果,Mock b2方法的結(jié)果,此時(shí)可以采用Spy方式;由于ServiceA依賴了ServiceB,而這個(gè)屬性可能是個(gè)AopProxy對象,并不能直接使用Mockito.mock(bservice)或者M(jìn)ockito.spy(bservice),所以這里@Spy注解指定的是實(shí)現(xiàn)類,通過MockitoDependencyInjectionTestExecutionListener處理后,獲得一個(gè)Spy對象,同時(shí)這個(gè)Spy對象設(shè)置到bservice(AopProxy對象)中去;
@TestExecutionListeners({MockitoDependencyInjectionTestExecutionListener.class}) public class AServiceMockTest extends BaseTest { @Spy private BServiceImpl bserviceImpl; @Autowired private AService aservice; @Before public void setup(){ doReturn(true).when(bserviceImpl).b2(any(String.class)); } @Test public void test() { a.execute(); } } @Service public class AServiceImpl implements AService { @Autowired private BService bservice; @Override public boolean execute2() { String str = bservice.b1(); return bservice.b2(str); } }MockitoDependencyInjectionTestExecutionListener的實(shí)現(xiàn)
public class MockitoDependencyInjectionTestExecutionListener extends DependencyInjectionTestExecutionListener { private Set附 maven依賴injectFields = new HashSet<>(); private Map mockObjectMap = new HashMap<>(); @Override protected void injectDependencies(TestContext testContext) throws Exception { super.injectDependencies(testContext); init(testContext); } /** * when A dependences on B * mock B or Spy on targetObject of bean get from Spring IoC Container whose type is B.class or beanName is BImpl * @param testContext */ private void init(TestContext testContext) throws Exception { AutowireCapableBeanFactory factory =testContext.getApplicationContext().getAutowireCapableBeanFactory(); Object bean = testContext.getTestInstance(); Field[] fields = bean.getClass().getDeclaredFields(); for (Field field : fields) { Annotation[] annotations = field.getAnnotations(); for (Annotation annotation : annotations) { if(annotation instanceof Mock){ Class> clazz = field.getType(); Object object = Mockito.mock(clazz); field.setAccessible(true); field.set(bean, object); mockObjectMap.put(field.getName(), object); } else if(annotation instanceof Spy) { Object fb = factory.getBean(field.getName()); //may be a proxy that can not be spy because $Proxy is final Object targetSource = AopTargetUtils.getTarget(fb); Object spyObject = Mockito.spy(targetSource); if (!fb.equals(targetSource)) { //proxy if (AopUtils.isJdkDynamicProxy(fb)) { setJdkDynamicProxyTargetObject(fb, spyObject); } else { //cglib setCglibProxyTargetObject(fb, spyObject); } } else { mockObjectMap.put(field.getName(), spyObject); } field.setAccessible(true); field.set(bean, spyObject); }else if (annotation instanceof Autowired){ injectFields.add(field); } } } for(Field field: injectFields) { field.setAccessible(true); Object fo = field.get(bean); if (AopUtils.isAopProxy(fo)) { Class targetClass = AopUtils.getTargetClass(fo); if(targetClass ==null) return; Object targetSource = AopTargetUtils.getTarget(fo); Field[] targetFields =targetClass.getDeclaredFields(); for(Field targetField : targetFields){ targetField.setAccessible(true); if(mockObjectMap.get(targetField.getName()) ==null){ continue; } ReflectionTestUtils.setField(targetSource,targetField.getName(), mockObjectMap.get(targetField.getName())); } } else { Object realObject = factory.getBean(field.getType()); if(null != realObject) { Field[] targetFields = realObject.getClass().getDeclaredFields(); for(Field targetField : targetFields){ targetField.setAccessible(true); if(mockObjectMap.get(targetField.getName()) ==null){ continue; } ReflectionTestUtils.setField(fo,targetField.getName(), mockObjectMap.get(targetField.getName())); } } } } } private void setCglibProxyTargetObject(Object proxy, Object spyObject) throws NoSuchFieldException, IllegalAccessException { Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0"); h.setAccessible(true); Object dynamicAdvisedInterceptor = h.get(proxy); Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised"); advised.setAccessible(true); ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).setTarget(spyObject); } private void setJdkDynamicProxyTargetObject(Object proxy, Object spyObject) throws NoSuchFieldException, IllegalAccessException { Field h = proxy.getClass().getSuperclass().getDeclaredField("h"); h.setAccessible(true); AopProxy aopProxy = (AopProxy) h.get(proxy); Field advised = aopProxy.getClass().getDeclaredField("advised"); advised.setAccessible(true); ((AdvisedSupport) advised.get(aopProxy)).setTarget(spyObject); } }
JUnit、Mockito
AopTargetUtilsAopTargetUtils工具類參考 在spring中獲取代理對象代理的目標(biāo)對象工具類
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/76729.html
摘要:例子使用源代碼我們先給了一個(gè)的實(shí)現(xiàn)然后又規(guī)定了方法的返回值。源代碼也就是說,得益于,我們能夠很方便地對依賴關(guān)系中任意層級(jí)的任意做。 Github地址 Mock測試技術(shù)能夠避免你為了測試一個(gè)方法,卻需要自行構(gòu)建整個(gè)依賴關(guān)系的工作,并且能夠讓你專注于當(dāng)前被測試對象的邏輯,而不是其依賴的其他對象的邏輯。 舉例來說,比如你需要測試Foo.methodA,而這個(gè)方法依賴了Bar.methodB,...
摘要:首先談到分布式鎖自然也就聯(lián)想到分布式應(yīng)用。如基于的唯一索引。基于的臨時(shí)有序節(jié)點(diǎn)。這里主要基于進(jìn)行討論。該命令可以保證的原子性。所以最好的方式是在每次解鎖時(shí)都需要判斷鎖是否是自己的??偨Y(jié)至此一個(gè)基于的分布式鎖完成,但是依然有些問題。 showImg(https://segmentfault.com/img/remote/1460000014128437?w=2048&h=1365); 前...
摘要:但是,一個(gè)好的單元測試應(yīng)該是毫秒級(jí)的,否則這會(huì)影響的工作方式,這也就是測試驅(qū)動(dòng)開發(fā)的思想。在單元測試中,我們可以像這樣來構(gòu)建一個(gè)實(shí)例。所以,我們在寫單元測試的時(shí)候,應(yīng)該以一種更簡單的方式去構(gòu)建。 本文翻譯自:https://reflectoring.io/unit-...原文作者:Tom Hombergs 譯文原地址:https://weyunx.com/2019/02/04... ...
摘要:節(jié)前沒有新業(yè)務(wù)代碼,正好剛發(fā)布,于是開始為期四天的框架代碼升級(jí)。還好并沒有使用它的,配置上有一個(gè)小坑,的是表示而是表示,之前配置成的,如果到的里面那就要拋異常了。 節(jié)前沒有新業(yè)務(wù)代碼,正好Greenwich剛發(fā)布,于是開始為期四天的框架代碼升級(jí)。 之前的版本是 spring boot 1.5.10 , spring cloud Edgware.SR3 依賴升級(jí) 增加依賴管理插件 ap...
閱讀 1295·2021-10-08 10:04
閱讀 1922·2021-09-04 16:40
閱讀 2536·2019-08-30 13:21
閱讀 2280·2019-08-29 15:10
閱讀 2848·2019-08-29 12:35
閱讀 1189·2019-08-26 17:41
閱讀 3062·2019-08-26 17:03
閱讀 1136·2019-08-26 12:01