在代码中,我最近遇到了一段非常糟糕的代码,这些代码基于对对象执行某些操作的类转换。 当然,代码需要重构,但是如果您首先没有对该功能进行单元测试,则有时您可能无法做到/或者不想这样做(这应该是可以理解的)。 在下面的文章中,我将展示如何测试这种代码,如何重构它以及实际上我对这种代码的看法。

让我们看一下项目结构:

如关于Mocktio的JAXB答复REPOSTS_DEEP_STUBS的帖子所述,在com.blogspot.toomuchcoding.model包中,我们再次使用JAXB编译器生成了JAXB生成的类。 让我们省略对pom.xml文件的讨论,因为它与上一篇文章完全相同。

com.blogspot.toomuchcoding.adapter包中,我们在JAXB PlayerDetails类上具有适配器,该类提供对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());}
}

好的,现在让我们转到更有趣的部分。 让我们假设我们有一个非常简单的玩家工厂:

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的工厂。 让我们看一下使用工厂的服务:

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;}
}

让我们承认吧……这段代码很糟糕。 在内部,当您查看它时(无论它是否使用 operator 实例 ),您都认为它是邪恶的。 正如您在代码中看到的那样,我们正在进行一些类强制转换……我们到底如何进行测试? 在大多数测试框架中,您无法对模拟进行此类类转换,因为它们是使用CGLIB库构建的,并且可能会抛出一些ClassCastExceptions。 您仍然无法返回模拟和真实的实现(假设这些模拟和构建不会在构建过程中执行任何丑陋的工作),并且它实际上可以工作,但仍然如此–这是错误的代码

自带的Mockito给救援(虽然你不应该过度使用此功能-事实上,如果你需要使用它,请考虑重构它)与其extraInterfaces特点:

extraInterfaces

MockSettings extraInterfaces (java.lang.Class <?>…接口)

指定模拟应实现的额外接口。 对于遗留代码或某些极端情况可能很有用。 有关背景信息,请参见此处的问题51。此神秘功能应经常使用。 被测对象应该确切知道其协作者和依赖性。 如果您碰巧经常使用它,请确保您确实在生成简单,干净且可读的代码。

例子:

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;

参数:

interfaces –应该实现的额外接口。

返回值:设置实例,以便您可以流畅地指定其他设置
现在让我们看一下测试:

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));}
}

这里有很多测试,所以让我们看一下最有趣的测试。 但是在开始之前,我们先:提供@RunWith(MockitoJUnitRunner.class)批注,它使我们能够使用Mockito批注,例如@Mock@InjectMocks

说到哪个@Mock注释会创建一个Mock,而@InjectMocks则通过构造函数或setter注入所有模拟(这太棒了吗?)。

对于防御性玩家,我们使用注解extraInterfaces的extra元素,该元素为给定的Mock提供其他接口。 您还可以编写(可以在shouldReturnDefensivePlayerBeingADjAndAJavaDevByUsingWithSettings测试中找到的内容 ):

DefensivePlayer defensivePlayerWithDjAndJavaDevSkills = mock(DefensivePlayer.class, withSettings().extraInterfaces(DJ.class, JavaDeveloper.class));

让我们仔细看看为与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();}

我们正在使用BDDMockito静态方法,如给定(...)。willReturn(...)。willAnswer(...)等。然后,将空方法与自定义Anwsers结合使用。 在下一行中,您可以看到,为了存根另一个接口的方法,必须将模拟转换为给定的接口。 这与验证阶段有关,在该阶段,我不检查是否已执行方法,您必须将模拟转换为给定的接口。

您可以通过从工厂返回一个实际的实现来改进测试,或者如果创建一个繁琐的操作,则可以返回这种实现的模拟。 我想在这里展示的是如何在Mockito中使用额外的接口(也许我的用例不是最好的用例)。 无论如何,测试中提出的实现都是不好的,所以我们应该考虑重构它的方法……

一种想法可能是,假设在Service中完成的附加逻辑是对象创建的一部分,将代码原样移至工厂:

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;}
}

这样就没有强制转换代码是真正干净的。 现在,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;}
}

随之而来的问题是,您的代码库中是否甚至需要这种方法……

总结一下,我希望我能展示如何:

  • 使用MockitoJUnitRunner以干净的方式注入模拟
  • 使用注释或静态方法添加可以被您的模拟使用的额外接口
  • 使用BDDMockito执行方法存根
  • 带有自定义答案的存根无效方法
  • 附加接口的存根和验证方法
  • 重构使用类强制转换的代码

这些源可在TooMuchCoding Bitbucket存储库和TooMuchCoding Github存储库中找到。

参考: Mockito –来自我们的JCG合作伙伴 Marcin Grzejszczak(位于Blog上)的 具有注释和静态方法的Extra Interfaces, 用于编码瘾君子博客。

翻译自: https://www.javacodegeeks.com/2013/06/mockito-extra-interfaces-with-annotations-and-static-methods.html

Mockito –带有注释和静态方法的额外接口相关推荐

  1. mockito 静态方法_Mockito –带有注释和静态方法的额外接口

    mockito 静态方法 在代码中,我最近遇到了一段非常糟糕的代码,该代码基于对对象执行某些操作的类转换. 当然,代码需要重构,但是如果您首先没有对该功能进行单元测试,则有时您可能无法做到/或者不想这 ...

  2. java8接口写静态方法_Java 8接口更改–静态方法,默认方法

    java8接口写静态方法 Java 8 interface changes include static methods and default methods in interfaces. Prio ...

  3. python解析json文件案例_Python加载带有注释的Json文件实例

    由于json文件不支持注释,所以如果在json文件中标记了注释,则使用python中的json.dump()无法加载该json文件. 本文旨在解决当定义"//"为json注释时,如 ...

  4. 无法使用struts2注释_带有注释且没有struts.xml文件的Struts 2 Hello World示例

    无法使用struts2注释 This is the second article in the series of Struts 2 Tutorials. If you have directly c ...

  5. java 接口 静态方法_Java 8 接口静态方法

    Java 8 除了给接口带来了 默认方法之外,还给接口带来了 静态方法.而且,Java 8 中的静态方法还可以有具体的实现. 我们都知道,在 Java 7 及之前的版本中,接口 interface 是 ...

  6. mvc 一般注释_使用带有注释和JQuery的Spring MVC 3的Ajax

    mvc 一般注释 与Ajax一起工作对我来说一直很有趣! 是不是 ? 我将使您轻松将Ajax与Spring MVC 3和JQuery结合使用. 这篇文章将向您说明如何在工业编码的现实生活中使用Ajax ...

  7. 使用带有注释和JQuery的Spring MVC 3的Ajax

    与Ajax一起工作对我来说一直很有趣! 是不是 ? 我将使您轻松将Ajax与Spring MVC 3和JQuery结合使用. 这篇文章将向您说明如何在工业编码的现实生活中使用Ajax. 和往常一样,我 ...

  8. 接口中的静态方法作用_接口一定要实现,才能为自己所用

    好多天没更新了,接下来几天会更新多点. 最近学习的难度越来越大,自己的时间越来越少了,所以这段时间发的文章都比较少. 废话不说了,现在开始说正事,今天说的是接口,所谓的接口,是Java语言中一种引用类 ...

  9. android优雅私有方法注释,带有注释参数的私有方法的Android java.lang.VerifyError

    我有一个非常简单的项目可以编译,但是无法在Emulator上启动.问题在于这种方法: private void bar(@Some String a) {} // java.lang.VerifyEr ...

最新文章

  1. Android 支付宝H5 没有回调
  2. sqoop mysql parquet_sqoop一些语法的使用
  3. 微软WSUS服务器 3.0安装配置详解
  4. Hadoop教程(一):简介、大数据解决方案、介绍快速入门
  5. BZOJ 4732 UOJ #268 [清华集训2016]数据交互 (树链剖分、线段树)
  6. hibernate h2变mysql_懒要懒到底,能自动的就不要手动,Hibernate正向工程完成Oracle数据库到MySql数据库转换(含字段转换、注释)...
  7. 58端口使用技巧跟推送_Kindle使用技巧:定时推送
  8. unity 如何获取到屏幕中间_请问如何获得场景的中心点在屏幕上的坐标?
  9. poythoncode-实战4--读取文本文件,csv文件,存到系统中以大列表方式进行存储
  10. 详细安装sqlmap详细教程
  11. GPFS实施方案的思考
  12. 数字图像处理——Sobel算子锐化、Prewitt算子锐化
  13. MySQL学习笔记.数据的导入和导出
  14. win7不休眠方式设置
  15. 成都有哪些牛批的互联网公司?
  16. 第二章-数据描述(初级统计)
  17. GeoHash算法详解
  18. Android开发:设置背景图片
  19. android 数据传递工具,AnyTrans for Android for Mac(安卓手机数据传输工具)
  20. 04 Android Activity生命周期

热门文章

  1. 服务器 .err文件,IIS 关闭HTTPERR(IIS日志)的方法
  2. rocketmq java home,rocketmq 安装与配置以及遇到的问题
  3. 可以搜python编程答案的软件_python实现百万答题自动百度搜索答案
  4. java泛型程序设计——反射和泛型
  5. jmeter进行性能测试_使用JMeter进行性能测试
  6. lambda :: -_无需再忙了:Lambda-S3缩略图,由SLAppForge Sigma钉牢!
  7. 阿帕奇跨域_阿帕奇光束
  8. Java与Python:哪一个最适合您? [信息图]
  9. 谷歌guava_Google Guava:您永远不会知道的5件事
  10. hazelcast入门教程_Hazelcast入门指南第4部分