我应该模拟单元测试中的每一个依赖项吗?

Should I mock every single dependencies in unit testing?

经过大量阅读,我发现人们建议在单元测试下模拟方法中的每个依赖项。

Should I mock all the dependencies when unit testing?

When should I mock?

但是,我有一个创建对象列表(依赖项)并使用这些对象的方法来更改列表的方法。在实际场景中,该方法是将客户端发送的数据负载转换为这些对象。 下面是 Typescript 中的代码实现,但它只是给你一个想法。

import {expect} from 'chai';

import 'mocha';

class Bar {
    private val: number;

    constructor(val: number) {
        this.val = val;
    }

    isValid(): boolean {
        return this.val !== 2;
    }

    canMergeWith(bar: Bar): boolean {
        return this.val === bar.val;
    }
}

class BarBuilder {
    constructor() {
    }

    createBars(payload: number[]): Bar[] {
        const bars: Bar[] = [];
        payload.forEach(p => {
            bars.push(new Bar(p));
        });

        const filtered = this.filterBars(bars);
        const merged = this.mergeBars(filtered);
        return merged;
    }

    private filterBars(bars: Bar[]): Bar[] {
        return bars.filter(b => b.isValid());
    }

    private mergeBars(bars: Bar[]): Bar[] {
        const merged: Bar[] = [];
        bars.forEach((bar, i) => {
            if (i > 0) {
                const before = bars[i - 1];
                if (!bar.canMergeWith(before)) {
                    merged.push(bar);
                }
            } else {
                merged.push(bar);
            }
        });
        return merged;
    }
}

describe('BarBuilder', () => {
    it('Should convert payload into valid bars', () => {
        const payload = [1, 2, 3, 4, 4, 5, 5];
        const barBuilder = new BarBuilder();
        const bars = barBuilder.createBars(payload);
        expect(bars).to.deep.equal([{val: 1}, {val: 3}, {val: 4}, {val: 5}]);
    });
});

目前,测试通过。但它违反了 我应该模拟 class.

中的每个依赖项的规则

我的问题是我应该做以下哪一项:

  1. 我真的应该嘲笑 class 酒吧以遵守该规则吗?

如果是这样,我发现这样做还有另一个困难。如果我模拟 class Bar,我如何根据它们的输入使模拟方法 canMergeWithisValid return 为真或假?是不是等同于重写 class 本身?

  1. 我应该重构我的代码吗?

我也在想问题可能出在我的代码不可测试上。之前,方法 canMergeWithisValid 属于 BarBuilder。为了可读性,我将它们移到了 Bar 中。但是现在,单元测试 BarBuilder 不再简单了。

  1. 保持测试原样,尽管它违反了单元测试规则。

I found that people advice to mock every dependencies in a method under unit test.

有些人这样建议,是的。在链接的线程中,更多人不同意。单元测试其实是一个分裂的课题。

当我第一次得知没有被整个行业广泛接受的单元测试定义时,我感到很惊讶。人们经常谈论单元测试,好像只有一个定义,尤其是“模拟一切”的定义;但是如果您通读链接的线程,您会发现该方法存在许多缺陷,尤其是那些测试的脆弱性。

我认为“模拟一切”的定义仍然如此普遍,因为它是多么微不足道。没有任何思考,只是嘲笑一切。另一种方法是考虑何时模拟,何时不模拟。那更难。你必须证明嘲笑是正当的。将单元测试定义为“模拟一切”会更容易,因为这样您就不会考虑模拟的目的是什么。这只是定义的一部分。

Ian Cooper 发表了关于单元测试的演讲,这是 Kent Beck 在 TDD 上下文中定义的。我发现他的观点比“模拟一切”的方法更现代。

Kent uses unit test in a very specific way. When he uses the phrase, all he means is that you should be able to run all your tests together, as one suite, without one test being run impacting the other tests. The unit of isolation is the test. And a lot of mistakes have been made because people believe the unit of isolation is the class under test. It is not.

我的建议:当它们帮助你时创建模拟。不要创建模拟来满足规则。没有规则,没有共识。