保存成功
订阅成功
保存失败,请重试
提交成功

Mock 七宗罪

ADP技术与解决方案架构师,前ThoughtWorks持续交付中国区Lead。目前专注于响应式编程,以及企业系统集成与容器化。
查看本场Chat

天虹大酒店2层小会议室门前,我看了看表1点59分。敏捷大会讲师论坛,下午我第一个发言,题目是“你用错了Mock”。我走进会议室的一瞬间,清晰地感觉到了各位讲师摩拳擦掌准备把我批个体无完肤。“在我开始发言之前,我想强调一下我不是针对在座的哪一位。”我说道,“我是说我们每个人都用错了。”

我做培训的时候经常在开始玩一个自创的游戏。首先我播放一段视频,里面我用手指有节奏的敲桌子。然后让听众猜我敲击的节奏是来自于那首歌。听众被分成两组,一组完全盲听,第二组知道这首歌是《祝你生日快乐》、《小二郎》、《卖报歌》中的一首。我做过很多次实验,第二组大概有80%以上的几率猜对,第一组则从来没有猜对。在被告知答案之后,第一组的人也觉得很明显。

通过这个游戏,我想告诉我的听众,在我的培训中你不会学到任何新的东西,我只是唤醒你已有知识和思考。同时,每个人从中得到的也是不一样的,这取决于你已有的知识结构和思考方式。

为什么要花那么大篇幅引入今天的主题呢?因为这是一个反直觉的主题。如果我们没在一个频道上,这将是一场互相的伤害。如果你完全没有用过Mock,你几乎什么也不会得到。

背景

在下面的论述中,我们尽可能使用同一个案例^。我们所列举的每一个情形都是来自于我本人参加的开发项目或者咨询项目。为了便于理解和陈述,我们把它映射到这个案例上面。

^该案例取自Martin Fowler先生的Mocks Aren't Stubs

我们有一个订单(Order)对象,该对象从仓库(Warehouse)对象中取出产品(Product)。订单对象仅包含一个产品对象和该产品的数量。仓库对象中包含多种不同的产品及其数量。当订单对象试图从仓库中取出产品并填充(fill)自己的时候,有两种结果。如果仓库中有足够的产品,订单填充成功,同时仓库中产品减少相应的数量。否则订单填充失败,并且仓库保持不变。

//产品代码(仅Order类)
public class Order {
    private final String product;
    private final int quantity;
    private boolean filled;

    public Order(String product, int quantity) {
        if (quntity <= 0) throw new InvalidArgumentException("The quantity shall not equal or be less than 0.");
        this.product = product;
        this.quantity = quantity;
    }

    public void fill(Warehouse warehouse) {
        if(warehouse.hasInventory(product, quantity)) {
            warehouse.remove(product, quantity);
            filled = true;
        }
    }

    public boolean isFilled() {
        return filled;
    }
}

第一部分:Mock测试的缺陷

在开始谈缺陷之前,我们先来看一下Mock测试主要解决什么问题?

互动评论
评论
吴平福2 年前
实践中我们要解决一个问题,在异常处理流程中是否输出了安全等级的信息,比如身份证号码;很明显,要执行过了才知道,网络异常,测试人员很难测试到。
评论
肖鹏(作者)2 年前
线下沟通了一下。吴同学的场景是“这个是我们要开发一个检查工具去发现别人的代码中是否存在这个问题。通过单元测试+MOCK去实现。”这是一种集成测试场景,而且不能修改别人的代码。由于mock的是稳定的(不可能重构的)结构,我觉得没啥问题。
评论
傻子才悲伤3 年前
感觉最基本的测试概念都没有搞清楚啊。单元测试,组件测试,集成测试等等,都是不一样的。mock个人认为用在TDD的单元测试部分,再合适不过了。另外为了凑齐七宗罪,强列出七点,也是有点不优雅啊。反对意见。
评论
李志博4 年前
写的不错看了这篇文章叫我知道某些场景下mock 还是有用的。。
评论
嗜血红魔4 年前
好 很好 非常好
评论
Adele4 年前
这样的mock和自动化测试之间的是否有些关系?是不是可以作为自动化测试的基础。很想在项目里来做这件事情,还不知如何来做?有没有些案例分享,或者常见的业务类(比如针对登陆、权限)等有比较通用的Mock代码案例。
评论
果子4 年前
谢谢分享,很有感触,对mock的理解更深入了。
评论
Dragon4 年前
能否详细谈一下数据库相关的层如何测试?如何应该内存数据库达到测试实际数据库的目的?
评论
邓志国2 年前
我的做法是用内存HashMap之类来模拟数据库接口,测试速度非常快。缺点是无法测试SQL
评论
何留留4 年前
老师,前后端开发的情况,前端同学mock后台接口的返回值进行自测,后台同学mock接口被调用进行自测,这样的策略可行吗,好处是什么,谢谢老师?
评论
it劫匪4 年前
(作者的意见是)不严格单元测试和集成测试,以此为前提的话,被mock的对象本身就在测试目标范围内,所以这时候上面mock的缺点是成立的。 如果把两类测试严格分开,对于单元测试,每个测试的目标代码仅限于孤立隔离开来了的代码单元,它所依赖的就可以被mock。被mock的代码本身则会被另外的单元测试所覆盖到。集成测试再关注于测试单元测试未覆盖到的代码单元间的的交互。 我支持区分单元测试和集成测试,出发点就不同,因此持反对意见。
评论
亚锐4 年前
有收获
评论
黄平4 年前
消除单元情结,消除之后单元测试和集成测试的分界线是什么呢?
评论
肖鹏(作者)4 年前
跨进程是指数据库,或者redis,或者REST Api等。
评论
肖鹏(作者)4 年前
我一般是存在跨进程的测试算集成测试,否则单元测试。其实我不赞成严格区分单元测试和集成测试。但是我承认有一个比较容易在团队中达成共识的名词还是挺重要的。
评论
W.Y☞4 年前
如果依赖很多RPC 接口, 有什么好办法避免Mock 吗?
评论
张克强4 年前
赞!赞同!
评论
黄平4 年前
单元测试应该是在任何环境下都可以正常运行的,所以需要排除任何外界因素的干扰,比如:网络,io,数据库等,没有mock,这种情况怎么处理?进行单元测试更多的应该是关注当前方法的逻辑功能,没有mock的话可能怎么保障测试用例专注于单个方法的功能验证?谢谢
评论
赵阳4 年前
没有mock(应该说stub),其实可以根据依赖逻辑抽象出接口,针对接口做一个生产实现,把依赖逻辑转移过来,用构造注入或setter注入把原来的类和依赖类链接起来。在测试的时,做一个测试类把依赖stub掉
评论
Gary4 年前
赞一个,物有所值。
评论
肖鹏(作者)4 年前
很荣幸的被盗版了,希望不是恶意。目前Trello上的版本有一些问题,特别是代码部分,请删除。也请至少在活动完成之前不要通过其他渠道分发。如果你不想花钱,等活动结束了,我可以发给你🙏🏻🙏🏻🙏🏻谢谢
评论
查看更多