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

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

让我们看一下项目结构:

如关于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;
    }

    @Override
    public 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);
    }

    @Override
    public void defend(){
        System.out.printf("Defence! %s. Defence!%n", getPlayerDetails().getName());
    }

    @Override
    public void playSomeMusic() {
        System.out.println("Oops I did it again...!");
    }

    @Override
    public 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);
    }

    @Override
    public 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 {

    @Override
    public 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;

    @Override
    public 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 {

    @Mock
    PlayerFactory playerFactory;

    @InjectMocks
    PlayerServiceImpl objectUnderTest;

    @Mock(extraInterfaces = {DJ.class, JavaDeveloper.class})
    DefensivePlayer defensivePlayerWithDjAndJavaDevSkills;

    @Mock
    DefensivePlayer defensivePlayer;

    @Mock
    OffensivePlayer offensivePlayer;

    @Mock
    Player commonPlayer;

    @Test
    public void shouldReturnOffensivePlayerThatRan() throws Exception {
        //given
        given(playerFactory.createPlayer(PositionType.ATT)).willReturn(offensivePlayer);

        //when
        Player createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(PositionType.ATT);

        //then
        assertThat(createdPlayer == offensivePlayer, is(true));
        verify(offensivePlayer).run();
    }

    @Test
    public void shouldReturnDefensivePlayerButHeWontBeADjNorAJavaDev() throws Exception {
        //given
        given(playerFactory.createPlayer(PositionType.GK)).willReturn(defensivePlayer);

        //when
        Player createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(PositionType.GK);

        //then
        assertThat(createdPlayer == defensivePlayer, is(true));
        verify(defensivePlayer).run();
        verify(defensivePlayer).defend();
        verifyNoMoreInteractions(defensivePlayer);
    }

    @Test
    public void shouldReturnDefensivePlayerBeingADjAndAJavaDev() throws Exception {
        //given
        given(playerFactory.createPlayer(PositionType.GK)).willReturn(defensivePlayerWithDjAndJavaDevSkills);
        doAnswer(new Answer<Object>() {
            @Override
            public 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>() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                System.out.println("public static void main(String... args){\n}");
                return null;
            }
        }).when(((JavaDeveloper) defensivePlayerWithDjAndJavaDevSkills)).doSomeSeriousCoding();

        //when
        Player createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(PositionType.GK);

        //then
        assertThat(createdPlayer == defensivePlayerWithDjAndJavaDevSkills, is(true));
        verify(defensivePlayerWithDjAndJavaDevSkills).run();
        verify(defensivePlayerWithDjAndJavaDevSkills).defend();
        verify((DJ) defensivePlayerWithDjAndJavaDevSkills).playSomeMusic();
        verify((JavaDeveloper) defensivePlayerWithDjAndJavaDevSkills).doSomeSeriousCoding();
    }

    @Test
    public void shouldReturnDefensivePlayerBeingADjAndAJavaDevByUsingWithSettings() throws Exception {
        //given
        DefensivePlayer defensivePlayerWithDjAndJavaDevSkills = mock(DefensivePlayer.class, withSettings().extraInterfaces(DJ.class, JavaDeveloper.class));
        given(playerFactory.createPlayer(PositionType.GK)).willReturn(defensivePlayerWithDjAndJavaDevSkills);
        doAnswer(new Answer<Object>() {
            @Override
            public 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>() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                System.out.println("public static void main(String... args){\n}");
                return null;
            }
        }).when(((JavaDeveloper) defensivePlayerWithDjAndJavaDevSkills)).doSomeSeriousCoding();

        //when
        Player createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(PositionType.GK);

        //then
        assertThat(createdPlayer == defensivePlayerWithDjAndJavaDevSkills, is(true));
        verify(defensivePlayerWithDjAndJavaDevSkills).run();
        verify(defensivePlayerWithDjAndJavaDevSkills).defend();
        verify((DJ) defensivePlayerWithDjAndJavaDevSkills).playSomeMusic();
        verify((JavaDeveloper) defensivePlayerWithDjAndJavaDevSkills).doSomeSeriousCoding();
    }

    @Test
    public void shouldReturnCommonPlayer() throws Exception {
        //given
        given(playerFactory.createPlayer(null)).willReturn(commonPlayer);

        //when
        Player createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(null);

        //then
        assertThat(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相关的功能和被测函数的转换部分编写的测试:

@Test
    public void shouldReturnDefensivePlayerBeingADjAndAJavaDev() throws Exception {
        //given
        given(playerFactory.createPlayer(PositionType.GK)).willReturn(defensivePlayerWithDjAndJavaDevSkills);
        doAnswer(new Answer<Object>() {
            @Override
            public 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>() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                System.out.println("public static void main(String... args){\n}");
                return null;
            }
        }).when(((JavaDeveloper) defensivePlayerWithDjAndJavaDevSkills)).doSomeSeriousCoding();

        //when
        Player createdPlayer = objectUnderTest.playAGameWithAPlayerOfPosition(PositionType.GK);

        //then
        assertThat(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 {

    @Override
    public 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
     */
    @Override
    public 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存储库中找到。


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值