如何写单元测试

作者: 这事情急不得 | 来源:发表于2019-04-12 00:02 被阅读22次

    二话不说,先上结论王道:

    测试代码的优雅和产品代码的优雅,两者一般不可得兼,舍测试代码的优雅而取产品代码的优雅,方为王道。

    然后给出全球唯一的参考书籍,因为市面上讲怎么写测试代码的书,应该只有这一本权威。

    然而,尽信书不如无书,这本里讲的都是到了万不得已的时候,不得已而为之的手段。如果还有更简单的手段,千万不要用这本里讲的东西,我下文反对的某些手段就是源自本书。

    现在,回归正题,首先,为什么要舍测试代码的优雅而取产品代码的优雅?因为一旦出了bug,必然是首先看的产品代码。而测试代码,其帮助理解产品业务逻辑的功效完全无法和产品代码本身相比,所以,产品代码比测试代码更重要。产品代码的可维护性比测试代码的可维护性要重要的多。

    现在,问题的重点,为什么测试代码的优雅和产品代码的优雅,两者一般不可得兼?这是因为你写测试的时候很多时候得mock,而mock并不是这么容易的。

    比如class A 要用到 function B,你的测试要mock fuction B,然而不巧这个B正好是private function,无法mock。

    很多人有这样的想法,当我无法写一个test的时候就说明了我的产品代码的design是有问题的,通过写test能够揭露我的产品代码是不是有design问题。这种想法是错误的,因为可能是你的技艺不精所以导致你写不出test,而不是真的没法写test。

    比如说,对于这个例子,很多人想我把这个B变成public不是就可以mock了吗?但是把B变成public貌似就扩大了B的职责这违反了单一职责原则,所以我最好的做法是把B封装在class C里面,然后把C的object传入给A的构造函数。这样来理解单一职责原则是有问题的。

    单一职责原则说的是一个class只做一件事,但是这一件事的理解各个人都各有不同,是非常主观的。比如说起床,可以包含穿衣服,洗脸,刷牙等一系列事情,那么你说起床是一件事呢还是几件事的集合呢?更加夸张的说,如果一定要分的很细,那么我可以认为每一行code就是做了一件事了,是不是每一行code都要封装成一个class呢?显然不是的。

    问题的关键是,没有搞清楚整个面向对象要解决的根本问题。其实有一条凌驾于所有原则之上的原则,那就是DRY(Do not repeat yourself)原则。所有的其他原则,设计模式等等最终就是要解决代码重复的问题,代码重复问题是所有代码可维护性问题的根本问题。

    所以,如果说世界上只有我一个人起床,那么我大可认为起床整个过程,包括洗脸刷牙等都是一件事。但是如果世界上有2个人以上要洗脸,那么洗脸的代码必须抽象出来,这2个人洗脸的时候只要调用同一份代码就可以了。这是follow了 DRY原则。

    所以在单一职责中到底怎么看是不是单一的职责,怎么划分职责的大小,就是看这个职责有没有可能有代码重复,划分的依据是有没有代码重复,而不是你主观的认为,不知道到这里各位想通了没有。

    回到之前的例子,如果class A 当前只有一个object,那么再弄一个class C去封装function B就是多余的,因为A并没有违反DRY原则,而再弄出来的C也没法复用。当没有办法复用的时候,为什么要去封装呢?本来B在A的内部封装的好好的,现在A要和C通讯,这就违反了高内聚低耦合原则。你看,为了不违反单一职责原则,反而违反了高内聚低耦合原则,这是为什么呢?各位想通了吗?

    本来只有A,现在又有了C,这是为了写测试,而把产品代码弄的不优雅了,违背了我们的结论:舍测试代码的优雅而取产品代码的优雅。

    不要跟我说以后可能会被复用,以后谁也说不准,我们的design要从当前出发,兼顾扩展性,主次不能颠倒,记住唐纳德的名言:

    过早的优化是万恶之源

    那么在这种情况下,我到底要怎么写test呢?此时必然是要改一下产品代码了,但是能不能把产品代码修改造成的不优雅程度降到最低呢?

    我们可以把B从private改成protected,然后在测试代码里写一个MockA去继承A,然后在MockA里改写B来mock掉B。这样虽然测试代码绕了个圈子,但是不至于在产品代码中引入A的新构造函数和C,只是把private稍微扩大了点范围变成了protected,这样就对产品代码的优雅程度的影响降到了最低。

    其实,很多情况下通过测public函数就可以覆盖到private函数了,所以一般不需要去测private函数,但是事情无绝对,经常有时候class并不需要复用,所以private占大头,也只能测private。

    还有一种常见的就是面向接口编程。有些人写任何class都喜欢加一个接口。只有在你有预感到不久的将来这个class会有别的派生类的时候,才定义需要接口。否则,接口放在那里毫无意义。再重复一次:

    过早的优化是万恶之源

    《重构》一书中说:只有出现2次或2次以上相同代码的时候,才考虑重构。有些时候,为了简单方便起见,重复2次也是可以接受的。

    运用之妙,存乎一心。妙法自然,各位慢慢体会吧。。。

    相关文章

      网友评论

        本文标题:如何写单元测试

        本文链接:https://www.haomeiwen.com/subject/wcsiiqtx.html