Mockito –带有注释和静态方法的额外接口
在代碼中,我最近遇到了一段非常糟糕的代碼,這些代碼基于對(duì)對(duì)象執(zhí)行某些操作的類轉(zhuǎn)換。 當(dāng)然,代碼需要重構(gòu),但是如果您首先沒有對(duì)該功能進(jìn)行單元測(cè)試,則有時(shí)您可能無法做到/或者不想這樣做(這應(yīng)該是可以理解的)。 在下面的文章中,我將展示如何測(cè)試這種代碼,如何重構(gòu)它以及實(shí)際上我對(duì)這種代碼的看法。
讓我們看一下項(xiàng)目結(jié)構(gòu):
如關(guān)于Mocktio的JAXB答復(fù)REPOSTS_DEEP_STUBS的帖子所述,在com.blogspot.toomuchcoding.model包中,我們?cè)俅问褂肑AXB編譯器生成了JAXB生成的類。 讓我們省略對(duì)pom.xml文件的討論,因?yàn)樗c上一篇文章完全相同。
在com.blogspot.toomuchcoding.adapter包中,我們?cè)贘AXB PlayerDetails類上具有適配器,該類提供對(duì)Player接口的訪問。 有:
CommonPlayerAdapter.java
package com.blogspot.toomuchcoding.adapter;import com.blogspot.toomuchcoding.model.Player; import com.blogspot.toomuchcoding.model.PlayerDetails;/*** User: mgrzejszczak* Date: 09.06.13* Time: 15:42*/ public class CommonPlayerAdapter implements Player {private final PlayerDetails playerDetails;public CommonPlayerAdapter(PlayerDetails playerDetails){this.playerDetails = playerDetails;}@Overridepublic void run() {System.out.printf("Run %s. Run!%n", playerDetails.getName());}public PlayerDetails getPlayerDetails() {return playerDetails;} }DefencePlayerAdapter.java
package com.blogspot.toomuchcoding.adapter;import com.blogspot.toomuchcoding.model.DJ; import com.blogspot.toomuchcoding.model.DefensivePlayer; import com.blogspot.toomuchcoding.model.JavaDeveloper; import com.blogspot.toomuchcoding.model.PlayerDetails;/*** User: mgrzejszczak* Date: 09.06.13* Time: 15:42*/ public class DefencePlayerAdapter extends CommonPlayerAdapter implements DefensivePlayer, DJ, JavaDeveloper {public DefencePlayerAdapter(PlayerDetails playerDetails){super(playerDetails);}@Overridepublic void defend(){System.out.printf("Defence! %s. Defence!%n", getPlayerDetails().getName());}@Overridepublic void playSomeMusic() {System.out.println("Oops I did it again...!");}@Overridepublic void doSomeSeriousCoding() {System.out.println("System.out.println(\"Hello world\");");} }OffensivePlayerAdapter.java
package com.blogspot.toomuchcoding.adapter;import com.blogspot.toomuchcoding.model.OffensivePlayer; import com.blogspot.toomuchcoding.model.PlayerDetails;/*** User: mgrzejszczak* Date: 09.06.13* Time: 15:42*/ public class OffensivePlayerAdapter extends CommonPlayerAdapter implements OffensivePlayer {public OffensivePlayerAdapter(PlayerDetails playerDetails){super(playerDetails);}@Overridepublic void shoot(){System.out.printf("%s Shooooot!.%n", getPlayerDetails().getName());} }好的,現(xiàn)在讓我們轉(zhuǎn)到更有趣的部分。 讓我們假設(shè)我們有一個(gè)非常簡(jiǎn)單的玩家工廠:
PlayerFactoryImpl.java
package com.blogspot.toomuchcoding.factory;import com.blogspot.toomuchcoding.adapter.CommonPlayerAdapter; import com.blogspot.toomuchcoding.adapter.DefencePlayerAdapter; import com.blogspot.toomuchcoding.adapter.OffensivePlayerAdapter; import com.blogspot.toomuchcoding.model.Player; import com.blogspot.toomuchcoding.model.PlayerDetails; import com.blogspot.toomuchcoding.model.PositionType;/*** User: mgrzejszczak* Date: 09.06.13* Time: 15:53*/public class PlayerFactoryImpl implements PlayerFactory {@Overridepublic Player createPlayer(PositionType positionType) {PlayerDetails player = createCommonPlayer(positionType);switch (positionType){case ATT:return new OffensivePlayerAdapter(player);case MID:return new OffensivePlayerAdapter(player);case DEF:return new DefencePlayerAdapter(player);case GK:return new DefencePlayerAdapter(player);default:return new CommonPlayerAdapter(player);}}private PlayerDetails createCommonPlayer(PositionType positionType){PlayerDetails playerDetails = new PlayerDetails();playerDetails.setPosition(positionType);return playerDetails;} }好的,我們有一家制造Player的工廠。 讓我們看一下使用工廠的服務(wù):
PlayerServiceImpl.java
package com.blogspot.toomuchcoding.service;import com.blogspot.toomuchcoding.factory.PlayerFactory; import com.blogspot.toomuchcoding.model.*;/*** User: mgrzejszczak* Date: 08.06.13* Time: 19:02*/ public class PlayerServiceImpl implements PlayerService {private PlayerFactory playerFactory;@Overridepublic Player playAGameWithAPlayerOfPosition(PositionType positionType) {Player player = playerFactory.createPlayer(positionType);player.run();performAdditionalActions(player);return player;}private void performAdditionalActions(Player player) {if(player instanceof OffensivePlayer){OffensivePlayer offensivePlayer = (OffensivePlayer) player;performAdditionalActionsForTheOffensivePlayer(offensivePlayer);}else if(player instanceof DefensivePlayer){DefensivePlayer defensivePlayer = (DefensivePlayer) player;performAdditionalActionsForTheDefensivePlayer(defensivePlayer);}}private void performAdditionalActionsForTheOffensivePlayer(OffensivePlayer offensivePlayer){offensivePlayer.shoot();}private void performAdditionalActionsForTheDefensivePlayer(DefensivePlayer defensivePlayer){defensivePlayer.defend();try{DJ dj = (DJ)defensivePlayer;dj.playSomeMusic();JavaDeveloper javaDeveloper = (JavaDeveloper)defensivePlayer;javaDeveloper.doSomeSeriousCoding();}catch(ClassCastException exception){System.err.println("Sorry, I can't do more than just play football...");}}public PlayerFactory getPlayerFactory() {return playerFactory;}public void setPlayerFactory(PlayerFactory playerFactory) {this.playerFactory = playerFactory;} }讓我們承認(rèn)吧……這段代碼很糟糕。 在內(nèi)部,當(dāng)您查看它時(shí)(無論它是否使用了 operator 實(shí)例 ),您都認(rèn)為它是邪惡的。 正如您在代碼中看到的那樣,我們正在進(jìn)行一些類強(qiáng)制轉(zhuǎn)換……我們到底如何進(jìn)行測(cè)試? 在大多數(shù)測(cè)試框架中,您無法對(duì)模擬進(jìn)行此類類轉(zhuǎn)換,因?yàn)樗鼈兪鞘褂肅GLIB庫構(gòu)建的,并且可能會(huì)拋出一些ClassCastExceptions。 您仍然無法返回模擬和真實(shí)的實(shí)現(xiàn)(假設(shè)這些模擬和構(gòu)建不會(huì)在構(gòu)建過程中執(zhí)行任何丑陋的工作),并且它實(shí)際上可以工作,但仍然如此–這是錯(cuò)誤的代碼
自帶的Mockito給救援(雖然你不應(yīng)該過度使用此功能-事實(shí)上,如果你需要使用它,請(qǐng)考慮重構(gòu)它)與其extraInterfaces特點(diǎn):
extraInterfaces
MockSettings extraInterfaces (java.lang.Class <?>…接口)
指定模擬應(yīng)實(shí)現(xiàn)的額外接口。 對(duì)于遺留代碼或某些極端情況可能很有用。 有關(guān)背景信息,請(qǐng)參見此處的問題51。此神秘功能應(yīng)經(jīng)常使用。 被測(cè)對(duì)象應(yīng)該確切知道其協(xié)作者和依賴性。 如果您碰巧經(jīng)常使用它,請(qǐng)確保您確實(shí)在生成簡(jiǎn)單,干凈且可讀的代碼。
例子:
Foo foo = mock(Foo.class, withSettings().extraInterfaces(Bar.class, Baz.class));//now, the mock implements extra interfaces, so following casting is possible:Bar bar = (Bar) foo;Baz baz = (Baz) foo;參數(shù):
interfaces –應(yīng)該實(shí)現(xiàn)的額外接口。
返回值:設(shè)置實(shí)例,以便您可以流暢地指定其他設(shè)置
現(xiàn)在讓我們看一下測(cè)試:
PlayerServiceImplTest.java
package com.blogspot.toomuchcoding.service;import com.blogspot.toomuchcoding.factory.PlayerFactory; import com.blogspot.toomuchcoding.model.*; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer;import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.*;/*** User: mgrzejszczak* Date: 08.06.13* Time: 19:26*/ @RunWith(MockitoJUnitRunner.class) public class PlayerServiceImplTest {@MockPlayerFactory playerFactory;@InjectMocksPlayerServiceImpl objectUnderTest;@Mock(extraInterfaces = {DJ.class, JavaDeveloper.class})DefensivePlayer defensivePlayerWithDjAndJavaDevSkills;@MockDefensivePlayer defensivePlayer;@MockOffensivePlayer offensivePlayer;@MockPlayer commonPlayer;@Testpublic void shouldReturnOffensivePlayerThatRan() throws Exception {//givengiven(playerFactory.createPlayer(PositionType.ATT)).willReturn(offensivePlayer);//whenPlayer createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(PositionType.ATT);//thenassertThat(createdPlayer == offensivePlayer, is(true));verify(offensivePlayer).run();}@Testpublic void shouldReturnDefensivePlayerButHeWontBeADjNorAJavaDev() throws Exception {//givengiven(playerFactory.createPlayer(PositionType.GK)).willReturn(defensivePlayer);//whenPlayer createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(PositionType.GK);//thenassertThat(createdPlayer == defensivePlayer, is(true));verify(defensivePlayer).run();verify(defensivePlayer).defend();verifyNoMoreInteractions(defensivePlayer);}@Testpublic void shouldReturnDefensivePlayerBeingADjAndAJavaDev() throws Exception {//givengiven(playerFactory.createPlayer(PositionType.GK)).willReturn(defensivePlayerWithDjAndJavaDevSkills);doAnswer(new Answer<Object>() {@Overridepublic Object answer(InvocationOnMock invocationOnMock) throws Throwable {System.out.println("Hit me baby one more time!");return null;}}).when(((DJ) defensivePlayerWithDjAndJavaDevSkills)).playSomeMusic();doAnswer(new Answer<Object>() {@Overridepublic Object answer(InvocationOnMock invocationOnMock) throws Throwable {System.out.println("public static void main(String... args){\n}");return null;}}).when(((JavaDeveloper) defensivePlayerWithDjAndJavaDevSkills)).doSomeSeriousCoding();//whenPlayer createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(PositionType.GK);//thenassertThat(createdPlayer == defensivePlayerWithDjAndJavaDevSkills, is(true));verify(defensivePlayerWithDjAndJavaDevSkills).run();verify(defensivePlayerWithDjAndJavaDevSkills).defend();verify((DJ) defensivePlayerWithDjAndJavaDevSkills).playSomeMusic();verify((JavaDeveloper) defensivePlayerWithDjAndJavaDevSkills).doSomeSeriousCoding();}@Testpublic void shouldReturnDefensivePlayerBeingADjAndAJavaDevByUsingWithSettings() throws Exception {//givenDefensivePlayer defensivePlayerWithDjAndJavaDevSkills = mock(DefensivePlayer.class, withSettings().extraInterfaces(DJ.class, JavaDeveloper.class));given(playerFactory.createPlayer(PositionType.GK)).willReturn(defensivePlayerWithDjAndJavaDevSkills);doAnswer(new Answer<Object>() {@Overridepublic Object answer(InvocationOnMock invocationOnMock) throws Throwable {System.out.println("Hit me baby one more time!");return null;}}).when(((DJ) defensivePlayerWithDjAndJavaDevSkills)).playSomeMusic();doAnswer(new Answer<Object>() {@Overridepublic Object answer(InvocationOnMock invocationOnMock) throws Throwable {System.out.println("public static void main(String... args){\n}");return null;}}).when(((JavaDeveloper) defensivePlayerWithDjAndJavaDevSkills)).doSomeSeriousCoding();//whenPlayer createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(PositionType.GK);//thenassertThat(createdPlayer == defensivePlayerWithDjAndJavaDevSkills, is(true));verify(defensivePlayerWithDjAndJavaDevSkills).run();verify(defensivePlayerWithDjAndJavaDevSkills).defend();verify((DJ) defensivePlayerWithDjAndJavaDevSkills).playSomeMusic();verify((JavaDeveloper) defensivePlayerWithDjAndJavaDevSkills).doSomeSeriousCoding();}@Testpublic void shouldReturnCommonPlayer() throws Exception {//givengiven(playerFactory.createPlayer(null)).willReturn(commonPlayer);//whenPlayer createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(null);//thenassertThat(createdPlayer, is(commonPlayer));} }這里有很多測(cè)試,所以讓我們看一下最有趣的測(cè)試。 但是在開始之前,我們先:提供@RunWith(MockitoJUnitRunner.class)批注,它使我們能夠使用Mockito批注,例如@Mock和@InjectMocks 。
說到哪個(gè)@Mock注釋會(huì)創(chuàng)建一個(gè)Mock,而@InjectMocks則通過構(gòu)造函數(shù)或setter注入所有模擬(這太棒了嗎?)。
對(duì)于防御性玩家,我們使用注解extraInterfaces的extra元素,該元素為給定的Mock提供其他接口。 您還可以編寫(可以在shouldReturnDefensivePlayerBeingADjAndAJavaDevByUsingWithSettings測(cè)試中找到的內(nèi)容 ):
DefensivePlayer defensivePlayerWithDjAndJavaDevSkills = mock(DefensivePlayer.class, withSettings().extraInterfaces(DJ.class, JavaDeveloper.class));讓我們仔細(xì)看看為與DefensivePlayer相關(guān)的功能和被測(cè)函數(shù)的轉(zhuǎn)換部分編寫的測(cè)試:
@Testpublic void shouldReturnDefensivePlayerBeingADjAndAJavaDev() throws Exception {//givengiven(playerFactory.createPlayer(PositionType.GK)).willReturn(defensivePlayerWithDjAndJavaDevSkills);doAnswer(new Answer<Object>() {@Overridepublic Object answer(InvocationOnMock invocationOnMock) throws Throwable {System.out.println("Hit me baby one more time!");return null;}}).when(((DJ) defensivePlayerWithDjAndJavaDevSkills)).playSomeMusic();doAnswer(new Answer<Object>() {@Overridepublic Object answer(InvocationOnMock invocationOnMock) throws Throwable {System.out.println("public static void main(String... args){\n}");return null;}}).when(((JavaDeveloper) defensivePlayerWithDjAndJavaDevSkills)).doSomeSeriousCoding();//whenPlayer createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(PositionType.GK);//thenassertThat(createdPlayer == defensivePlayerWithDjAndJavaDevSkills, is(true));verify(defensivePlayerWithDjAndJavaDevSkills).run();verify(defensivePlayerWithDjAndJavaDevSkills).defend();verify((DJ) defensivePlayerWithDjAndJavaDevSkills).playSomeMusic();verify((JavaDeveloper) defensivePlayerWithDjAndJavaDevSkills).doSomeSeriousCoding();}我們正在使用BDDMockito靜態(tài)方法,如給定(...)。willReturn(...)。willAnswer(...)等。然后,將空方法與自定義Anwsers結(jié)合使用。 在下一行中,您可以看到,為了存根另一個(gè)接口的方法,必須將模擬轉(zhuǎn)換為給定的接口。 這與驗(yàn)證階段有關(guān),在該階段,我不檢查是否已執(zhí)行方法,您必須將模擬轉(zhuǎn)換為給定的接口。
您可以通過從工廠返回一個(gè)實(shí)際的實(shí)現(xiàn)來改進(jìn)測(cè)試,或者如果創(chuàng)建一個(gè)繁瑣的操作,則可以返回這種實(shí)現(xiàn)的模擬。 我想在這里展示的是如何在Mockito中使用額外的接口(也許我的用例不是最好的用例)。 無論如何,測(cè)試中提出的實(shí)現(xiàn)都是不好的,所以我們應(yīng)該考慮重構(gòu)它的方法……
一種想法可能是,假設(shè)在Service中完成的附加邏輯是對(duì)象創(chuàng)建的一部分,將代碼原樣移至工廠:
PlayFactoryImplWithFieldSettingLogic.java
package com.blogspot.toomuchcoding.factory;import com.blogspot.toomuchcoding.adapter.CommonPlayerAdapter; import com.blogspot.toomuchcoding.adapter.DefencePlayerAdapter; import com.blogspot.toomuchcoding.adapter.OffensivePlayerAdapter; import com.blogspot.toomuchcoding.model.*;/*** User: mgrzejszczak* Date: 09.06.13* Time: 15:53*/public class PlayerFactoryImplWithFieldSettingLogic implements PlayerFactory {@Overridepublic Player createPlayer(PositionType positionType) {PlayerDetails player = createCommonPlayer(positionType);switch (positionType){case ATT:return createOffensivePlayer(player);case MID:return createOffensivePlayer(player);case DEF:return createDefensivePlayer(player);case GK:return createDefensivePlayer(player);default:return new CommonPlayerAdapter(player);}}private Player createDefensivePlayer(PlayerDetails player) {DefencePlayerAdapter defencePlayerAdapter = new DefencePlayerAdapter(player);defencePlayerAdapter.defend();defencePlayerAdapter.playSomeMusic();defencePlayerAdapter.doSomeSeriousCoding();return defencePlayerAdapter;}private OffensivePlayer createOffensivePlayer(PlayerDetails player) {OffensivePlayer offensivePlayer = new OffensivePlayerAdapter(player);offensivePlayer.shoot();return offensivePlayer;}private PlayerDetails createCommonPlayer(PositionType positionType){PlayerDetails playerDetails = new PlayerDetails();playerDetails.setPosition(positionType);return playerDetails;} }這樣就沒有強(qiáng)制轉(zhuǎn)換代碼是真正干凈的。 現(xiàn)在,PlayerService如下所示:
PlayerServiceImplWIthoutUnnecessaryLogic.java
package com.blogspot.toomuchcoding.service;import com.blogspot.toomuchcoding.factory.PlayerFactory; import com.blogspot.toomuchcoding.model.*;/*** User: mgrzejszczak* Date: 08.06.13* Time: 19:02*/ public class PlayerServiceImplWithoutUnnecessaryLogic implements PlayerService {private PlayerFactory playerFactory;/*** What's the point in having this method then?* @param positionType* @return*/@Overridepublic Player playAGameWithAPlayerOfPosition(PositionType positionType) {return playerFactory.createPlayer(positionType);}public PlayerFactory getPlayerFactory() {return playerFactory;}public void setPlayerFactory(PlayerFactory playerFactory) {this.playerFactory = playerFactory;} }隨之而來的問題是,您的代碼庫中是否甚至需要這種方法……
總結(jié)一下,我希望我能展示如何:
- 使用MockitoJUnitRunner以干凈的方式注入模擬
- 使用注釋或靜態(tài)方法添加可以被您的模擬使用的額外接口
- 使用BDDMockito執(zhí)行方法存根
- 帶有自定義答案的存根無效方法
- 附加接口的存根和驗(yàn)證方法
- 重構(gòu)使用類強(qiáng)制轉(zhuǎn)換的代碼
這些源可在TooMuchCoding Bitbucket存儲(chǔ)庫和TooMuchCoding Github存儲(chǔ)庫中找到。
翻譯自: https://www.javacodegeeks.com/2013/06/mockito-extra-interfaces-with-annotations-and-static-methods.html
總結(jié)
以上是生活随笔為你收集整理的Mockito –带有注释和静态方法的额外接口的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 魂银安卓下载(魂银安卓)
- 下一篇: 记录合规性–关于TCK,规格和测试