什么是真正的单元测试

What is a real unit test?

本文关键字:单元测试 什么      更新时间:2023-09-26

所以一段时间以来,我一直在思考真正的单元测试应该由什么组成。最好用一个例子来证明这一点,这是我想要测试的角度服务:

function stringUtils() {
    return {  
        splitFilterString: splitFilterString
    } 
    function splitFilterString (filterString) {
        return filterString.split(':');
    }
}

我一直在思考我的两种方法中哪一种最能描述splitFilterString的"真实"单元测试。(例子是用茉莉花写的)

1测试是否已调用String.prototype.split

你不需要用这个测试任何"真实"的例子,你只需要测试函数是否真的调用了String.prototype.split,并返回了该函数返回的任何内容。到目前为止,这一直是我的测试方式,因为你不测试"外部"API做他们告诉你应该做的事情。

    it('should call split with ":" as an argument and return whatever split returns', function () {
        var filterString = 'foo:bar';
        spyOn(String.prototype, 'split').and.returnValue('foo');
        expect(stringUtils.splitFilterString(filterString)).toBe('foo');
        expect(String.prototype.split).toHaveBeenCalledWith(':');
    });

2测试功能的实际预期输出

我也喜欢这种方法,因为您可以测试实际输入/输出函数的使用方式。不利的一面是,你在这里也间接测试String.prototype.split,你在测试它是否真的做到了它所说的。

    it('should return the correct output', function () {
        expect(stringUtils.splitFilterString('foo:bar')).toEqual(['foo', 'bar']);
        expect(stringUtils.splitFilterString('foobar')).toEqual(['foobar']);
    });

在第一个示例中,您将测试与实现耦合,这是主要区别。

如果您稍后发现要使用superFastSplit,这可能会妨碍您,因为您需要更改代码测试
因此,在这种情况下,测试并没有提供真正的价值,因为当你想重构时,它可能会阻碍你:"我想重构那部分代码,但那些该死的测试意味着我必须做两次!"。

在第二个示例中,您正在测试行为,因此您的代码将允许您使用superFastSplit,因为您对如何获得结果不感兴趣,您感兴趣的是结果在各个实现之间是一致的。

编辑OP评论后

在外部API的情况下,我仍然会遵循"不要伤害我未来的自己/同事"的路径,所以我会做最简单的事情,IMHO就是使用一个模仿外部API的模块,类似这样:

nock('http://external.api.com')
  .get('/end/point')
  .reply(200);

当然,你必须小心,不要试图覆盖太多的场景,因为你基本上是在决定外部API将返回什么,所以我想说,我只在这里测试ok和nok场景,并在集成测试中覆盖所有细节。

在背后,诺克做的是:

Nock的工作原理是覆盖Node的http.request函数。此外,它覆盖http。ClientRequest也无法覆盖直接使用它的模块。

您总是可以测试API调用在代码中产生的任何副作用,但我觉得在应用TDD时,这种方法更难遵循。