powermock_关于PowerMock滥用

powermock

仍在处理我的旧应用程序,并仍在尝试改进 单元测试

本周,我注意到在整个测试中使用了多少PowerMock来模拟静态或私有方法。 在一个特定的程序包中,删除它可以将测试执行时间缩短一个数量级(从大约20秒减少到2秒)。 这显然是滥用:我看到了使用PowerMock的三个主要原因。

缺乏API知识

可能一定有很好的理由,但是如果开发人员仅检查了基础代码,则可以避免使用某些PowerMock。 此类代码的一个示例如下:

@RunWith(PowerMockRunner.class)
@PrepareForTest(SecurityContextHolder.class)
publicclassExampleTest{

    @MockprivateSecurityContextsecurityContext;

    publicvoidsetUp()throwsException{
        mockStatic(SecurityContextHolder.class);
        when(SecurityContextHolder.getContext()).thenReturn(securityContext);
    }

    // Rest of the test
}

快速浏览一下Spring的SecurityContextHolder就会发现它具有setContext()方法,因此可以轻松地用以下代码替换之前的代码段:

@RunWith(MockitoJUnitRunner.class)
publicclassExampleTest{

    @MockprivateSecurityContextsecurityContext;

    publicvoidsetUp()throwsException{
        SecurityContextHolder.setContext(securityContext);
    }

    // Rest of the test
}

我注意到的另一个常见代码段如下:

@RunWith(PowerMockRunner.class)
@PrepareForTest(WebApplicationContextUtils.class)
publicclassExampleTest{

    @MockprivateWebApplicationContextwac;

    publicvoidsetUp()throwsException{
        mockStatic(WebApplicationContextUtils.class);
        when(WebApplicationContextUtils.getWebApplicationContext(any(ServletContext.class))).thenReturn(wac);
    }

    // Rest of the test
}

虽然比上一个示例稍难一些,但是查看WebApplicationContextUtils源代码可以发现它针对上下文是servlet上下文。

可以轻松更改测试代码以删除PowerMock:

@RunWith(MockitoJUnitRunner.class)
publicclassExampleTest{

    @MockprivateWebApplicationContextwac;
    @MockprivateServletContextsc;

    publicvoidsetUp()throwsException{
        when(sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)).thenReturn(wac);
    }

    // Rest of the test
}

可见度太严格

如上所示,良好的框架(例如Spring)使在测试中易于使用它们。 不幸的是,我们的代码不能总是如此。 在这种情况下,我通过扩大方法和类从私有(或包)到公共的可见性来删除了PowerMock。

您可能会辩解说,破坏封装来改进测试是错误的,但是在这种情况下,我倾向于鲍勃叔叔的观点:

测试王牌封装。
—鲍勃叔叔
https://blog.8thlight.com/uncle-bob/2015/06/30/the-little-singleton.html

实际上,您认为封装可以防止其他开发人员滥用您的代码。 但是,您可以在测试中通过反射来打破它...是什么保证开发人员不会在生产代码中使用反射方式呢?

务实的解决方案是对您的设计进行一些折衷,但这是问题的核心,将其记录下来 。 Guava和Fest库都具有@VisibleForTesting批注,我觉得很方便。 锦上添花的是IDE可以识别它,而不是在src/main/java文件夹中建议自动完成。

直接使用静态方法

最后一点已经被解释了一次又一次,但是一些开发人员仍然不能正确地应用它。 一些非常常见的API仅提供静态方法,而没有其他选择, 例如 Locale.getDefault()Calendar.getInstance() 。 此类方法不应直接在您的生产代码上调用,否则它们将使您的设计只能通过PowerMock进行测试。

publicclassUntestableFoo{
    publicvoiddoStuff(){
        Calendarcal=Calendar.getInstance();
        // Do stuff on calendar;
    }
}

@RunWith(PowerMock.class)
@PrepareForTest(Calendar.class)
publicclassUntestableFooTest{

    @MockprivateCalendarcal;
    privateUntestableFoofoo;

    @Before
    publicvoidsetUp(){
        mockStatic(Calendar.class);
        when(Calendar.getInstance()).thenReturn(cal);
        // Stub cal accordingly

        foo=newUntestableFoo();
    }

    // Add relevant test methods
}

要解决此设计缺陷,只需使用注入,更精确地使用构造函数注入:

publicclassTestableFoo{

    privatefinalCalendarcalendar;

    publicTestableFoo(Calendarcalendar){
        this.calendar=calendar;
    }

    publicvoiddoStuff(){
        // Do stuff on calendar;
    }
}

@RunWith(MockitoJUnitRunner.class)
publicclassTestableFooTest{

    @Mock
    privateCalendarcal;

    privateTestableFoofoo;

    @Before
    publicvoidsetUp(){
        // Stub cal accordingly

        foo=newTestableFoo(cal);
    }

    // Add relevant test methods
}

此时,唯一的问题是如何首先创建实例。 很容易,具体取决于您的注入方法:Spring @Bean方法, CDI @Inject Provider<T>方法或使用您自己的方法之一调用getInstance()方法。 这是Spring的方式:

@Configuration
publicclassMyConfiguration{

    @Bean
    publicCalendarcalendar(){
        returnCalendar.getInstance();
    }

    @Bean
    publicTestableFoofoo(){
        returnnewTestableFoo(calendar());
    }
}

结论

PowerMock是一个非常强大且有用的工具。 但是,仅在绝对必要时才应使用它,因为它会对测试执行时间产生巨大影响。 在本文中,我试图说明如何在3个不同的用例中不使用它:缺乏API知识,过于严格的可见性和直接的静态方法调用。 如果您发现测试代码库中充满了PowerMock的用法,建议您尝试上述方法以摆脱它们。

我从来都不是TDD的粉丝(可能是另一篇文章的主题),但是我相信如果使用TDD,可以很容易地避免最后两点。

翻译自: https://blog.frankel.ch/on-powermock-abuse/

powermock

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值