资讯专栏INFORMATION COLUMN

静态方法,mock 还是不 mock,这是个问题

waterc / 874人阅读

摘要:单元测试中是否要静态方法,一直争论不休,网上有一个一个又一个的讨论,各种意见都有。真要用来静态方法,一般都是结合使用。等工具不支持静态方法,原理上是因为它们都是基于的,只能通过创建子类或实现接口的方式去。什么静态方法构造函数,随时随地想就。

王者 Mockito

不知从何时开始,Mockito 成了 Java 的单元测试框架王者,目前(2019年7月)Github 上 star 数直逼 10K。看看其他的单元测试工具:PowerMock 2K(无疑是沾了 Mockito 的光),easymock 600,JMockit 300。跟 Mockito 一比,好可怜啊,一个能打的都没有。

Mockito 当然很好。我从2012年还是2013年开始用 Mockito,看着它从 1.0x 版本一路走来,今年晚些时候估计会正式发布 3.0 版本。应该有不少人都跟我有类似的体验,从 Mockito 开始接触 mock / stub,一边赞叹 Mockito 语法的简练,一边享受着 mock 带来的单元测试的便利性。总说单元测试应该要隔离外部依赖和实现,很难想象,如果没有 mock,怎么写单元测试呢?

public void test() {
    when(userDao.update(any(User.class))).thenReturn(1);
    int actual = userService.update(aUser);
    Assert.assertTrue(acutal > 0);
    verify(userDao).update(aUser);
}

看看上面这个 Mockito 的例子,when(...).thenReturn(...)verify(...).doSomething(),这代码就像人类语言,多么简明易懂!

但是(没错转折来了),已经2019年了,Mockito 依然不支持 mock 静态方法、构造方法等。你可以说,这是设计理念,Mockito 首页上一直写着一句话 "Don’t mock everything" ,认为说应该做好功能代码的设计,尽量避免静态方法等,尽量使你的代码易于测试。这个理念,在理论上没问题,但这么多年的开发经验告诉我,理想归理想,实际上要你去维护的遗留代码总是一箩筐一箩筐的,避无可避。

Static methods, to mock or not to mock, that is the question

单元测试中是否要 mock 静态方法,一直争论不休,网上有 一个 一个 又一个 的讨论,各种意见都有。

我的个人意见,跟 这个观点 一样,我认为测试工具不应该替用户决定什么是好、什么是不好,而应该尽量提供选择,让用户自行判断、采取合适的方案。理论很美好,但实际情况就是,google 搜 "mockito how to mock static methods",有近15万条结果,可想而知,全世界的开发者在这个问题上浪费了多少时间。

真要用 Mockito 来 mock 静态方法,一般都是结合 PowerMock 使用。这两年 PowerMock 发展的怎么样我不太清楚,但14、15年那会儿我用过 PowerMock,感受就是,真他妈累啊!理论上来说是可以的,但实际做起来就总是各种问题,然后各种 google 、解决,然后又继续各种问题,排查的我都快怀疑人生了。最终我是放弃了 PowerMock 的,这么费力地去结合两个工具一起用,往后很难说还有多少坑。

Mockito、EasyMock 等工具不支持 mock 静态方法,原理上是因为它们都是基于 cglib 的,只能通过创建子类或实现接口的方式去 mock。那除了 cglib ,就没有其他的 mock 实现方法了吗?当然有,修改字节码呀!

另辟蹊径的 JMockit

和其他大多数使用 cglib 实现的单元测试工具不同,JMockit 使用 JDK6 的 java.lang.instrument 包和 ASM,动态地在运行时修改字节码,从而实现 "Mock Anything" 。什么静态方法、构造函数,随时随地想 mock 就 mock。一个 JMockit ,解决了 Mockito + PowerMock 两个工具都解决不了的问题,那为啥不用 JMockit 呢?JMockit 为啥流行不起来呢?

public class UserServiceTest {

    @Tested
    private UserService userService;
    @Injectable
    private UserDao userDao;

    public void test() {
        new Expectations() {
            {
                userDao.update(withInstanceOf(User.class));
                result = 1;
            }
        };

        int actual = userService.update(aUser);
        Assert.assertTrue(acutal > 0);

        new Verifications() {
            {
                userDao.update(withInstanceOf(User.class));
            }
        };
    }
}

功能更强大的 JMockit 却流行不起来,我觉得其中一个原因,是它的语法不太友好。看看上面这个 JMockit 的例子,这坨 new Expectations(){...}new Verifications(){...} 是什么鬼?匿名类?为啥里面又有一层大括号?别说测试代码了,在普通的功能代码中,我们都极少见到这样的语法。多数人可能觉得不习惯,然后就此打住,放弃 JMockit 了。

JMockit 的这种语法,是基于它的 record-replay-verify 模型。new Expectations() 是录制期望,new Verifications() 是校验,二者中间的就是回放——正常调用业务方法。而在匿名内部类类中间的那层大括号,是 Java 的“实例初始化块” (Instance Initialization Blocks),我们平时可能用“静态初始化块”比较多,“实例初始化块”确实较少见,它的其中一种用途,就是用来初始化匿名内部类,因为匿名内部类不能有构造函数。理解了这些语法之后,其实 JMockit 不难懂,用法跟其他测试框架也大致一样,就是功能更强大了。

JMockit 不够流行的另一个原因,我猜可能跟社区有关。没办法,Mockito 太受欢迎了,社区一片火热,贡献者一大堆。反观 JMockit,虽然开源,但只有原作者 Rogério Liesenfeld 自己一个人在开发维护。这种单人维护的项目,说不定哪一天就停更了,大家都会有这种担忧。我也担心啊,但看看近几年 JMockit 的 release notes,基本上固定每一、两个月一次发布,并且还会提前订好下一次发布的计划,真想对作者说一句:老哥,稳!所以,至少目前看来,JMockit 的稳定性、活跃性是不用担心的,毕竟有个这么稳的作者。

JUnit5 + JMockit + Surefire + Jacoco 的配置例子

想要安心用上 Junit5 和 JMockit,还想要单元测试覆盖率?那还是有些坑要踩的。以 Maven 为例,有几个留意点:

默认的 maven-surefire-plugin (运行单元测试的) 的版本不支持 JUnit5,得手动指定新版本号才行。

想用 jacoco-maven-plugin 得到单元测试覆盖率的话,因为 jacoco 也用了修改字节码的方案,默认配置下会和 JMockit 有冲突。需要一些额外配置才行,具体参考下面的例子。

完整的 Maven 配置例子:



    4.0.0

    io.github.renial
    java-utils
    1.0.0
    jar
    java-utils

    
        UTF-8
        UTF-8
        1.8
        1.8

        1.46
        5.4.2 
        2.22.2 
        0.8.4
    

    
        
            org.jmockit
            jmockit
            ${jmockit.version}
            test
        
        
            org.junit.jupiter
            junit-jupiter
            ${jupiter.version}
            test
        
    

    
        
            
                org.apache.maven.plugins
                maven-surefire-plugin
                ${surefire.version}
                
                
                
                    
                        -javaagent:${settings.localRepository}/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar -javaagent:"${settings.localRepository}"/org/jacoco/org.jacoco.agent/${jacoco.version}/org.jacoco.agent-${jacoco.version}-runtime.jar=destfile=${project.build.directory}/jacoco.exec
                    
                
            

            
                org.jacoco
                jacoco-maven-plugin
                ${jacoco.version}
                
                    
                    
                    
                    
                    
                        
                            prepare-agent
                        
                    
                    
                        report
                        test
                        
                            report
                        
                    
                
            
        
    

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75656.html

相关文章

  • Android单元测试 - 几重要问题

    摘要:言归正传,上一篇文章单元测试如何开始介绍了几款单元测试框架基本用法依赖隔离概念,本篇主要解答单元测试中几个重要问题。在单元测试交流微信群,很多新进来的小伙伴,都会几个大同小异的问题。 showImg(/img/bVEpaD?w=1080&h=715); 原文链接:http://www.jianshu.com/p/f5d197a4d83a 前言 已经一个月没写文章了,由于9月份在plan...

    ChristmasBoy 评论0 收藏0
  • moky = (mock + proxy) // 一简洁通用的前后端协作工具

    摘要:现在一般需要分前后端,因为大量前端框架和工具链的涌入根源是需求复杂了,让前端可以跟后端独立开来。但是,无论是前端去写模板,亦或是完全前后端分离去写,都脱离不了与后端进行数据交互。 showImg(https://segmentfault.com/img/remote/1460000007317424?w=350&h=113); --> GitHub地址 旧石器时代,Web 开发并不会去...

    miqt 评论0 收藏0
  • 谈谈前后端的分工协作

    摘要:针对上面看到的问题,现在也有一些团队在尝试前后端之间加一个中间层比如淘宝的。淘宝有很多类似的文章,这里不赘述。淘宝团队做了两套接口文档的维护工具,以及,不知道有没有对外开放,两个东西都是基于的一个尝试,各有优劣。 原文出处: 小胡子哥的博客(@Barret李靖) 前后端分工协作是一个老生常谈的大话题,很多公司都在尝试用工程化的方式去提升前后端之间交流的效率,降低沟通成本,并且也开发了...

    Scholer 评论0 收藏0
  • 谈谈前后端的分工协作

    摘要:针对上面看到的问题,现在也有一些团队在尝试前后端之间加一个中间层比如淘宝的。淘宝有很多类似的文章,这里不赘述。淘宝团队做了两套接口文档的维护工具,以及,不知道有没有对外开放,两个东西都是基于的一个尝试,各有优劣。 原文出处: 小胡子哥的博客(@Barret李靖) 前后端分工协作是一个老生常谈的大话题,很多公司都在尝试用工程化的方式去提升前后端之间交流的效率,降低沟通成本,并且也开发了...

    OBKoro1 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<