# 单元测试总结

# 单元测试需要具备哪些知识?

很久以前就听说,学习正则可以作为一门专门的职业,在我看来,单元测试亦是如此。熟练地做单元测试不仅需要掌握主流的单元测试的框架、正则、JS、以及各类插件外,还需要有独立于开发逻辑之外的“测试逻辑头脑”,它与开发逻辑紧密相连思维方式却大相径庭。就拿TS-Axios这门课来说,做单元测试你需要掌握:

1:JEST框架搭建与配置运用

2:正则表达式的熟练运用

3:基础JS、ES6、Promise等熟练运用

4:各类插件如jasmine-ajax等基础使用以及在不同框架内部的兼容处理

此外,不同领域的单元测试还要求测试人员掌握其相关涉及的逻辑知识,比如测试请求你需要知道请求相关的额知识,整体来说就如以下代码:

test('should reject on network errors', () => { // 网络错误情况测试
    const resolveSpy = jest.fn((res: AxiosResponse) => { // 模拟函数
      return res
    })

    const rejectSpy = jest.fn((e: AxiosError) => { // 模拟函数
      return e
    })

    jasmine.Ajax.uninstall() // uninstall导致请求发送报异常

    return axios('/foo')
      .then(resolveSpy)
      .catch(rejectSpy)
      .then(next)

    function next(reason: AxiosResponse | AxiosError) {
      expect(resolveSpy).not.toHaveBeenCalled() // 测试请求没有执行
      expect(rejectSpy).toHaveBeenCalled() // 测试请求执行
      expect(reason instanceof Error).toBeTruthy()
      expect((reason as AxiosError).message).toBe('Network Error')
      expect(reason.request).toEqual(expect.any(XMLHttpRequest)) // 断言request是任一XMLHttpRequest实例

      jasmine.Ajax.install()
    }
  })

首先框架的基础搭建和api使用方法自是不用多说,其次你得知道请求测试需要测试哪些问题:比如axios单地址写法测试、method大小写测试、网络错误、超时情况测试等,很多都是容易遗漏的。再者,大方法中你得知道测些什么,这就需要测试人员对测试内容的极高了解程度,就拿上述代码来说,针对网络错误这一块你是否想到该如何测试?你是否会设计测试resolve和reject的执行?是否会断言reason的类型?是否会验证AxiosError的message?等等...每块测试根据测试内容的不同需要测试人员掌握不容的涉及知识,才能更好地完成和保障测试的准确性。

当然,并不是所有测试都需要如此完整的整密性,部分遗漏只要不影响主要功能也是能够认可的,但是其完整程度就是测试人员能力与功底的镜面,能力越强的测试师其测试代码逻辑越是紧密,测试项越是专业,让人一看便很舒服。

# 单元测试应该由谁来做?

经过不断地学习,我们大概对单元测试有了一个初步的了解,如果您是第一次接触完整的、大面积覆盖的测试代码,那么难免会有所困惑--完整的单元测试在开发过程中由谁来完成?普遍存在的争议便是测试工程师与开发工程师。

无论是测试工程师还是开发工程师来做单元测试都是可行的。

测试工程师来做单元测试以保障代码的可靠性听起来是一件天经地义的事情,但是弊端也很明显,那就是对测试工程师的要求极高。单测的话还好,但如果是对于一个框架的覆盖测试,往往要求覆盖率达到90%甚至95%以上。我们知道,常见的UT开发方式有代码驱动测试和测试驱动代码两种方式,如果采用后者,因该让开发去做更为合理,专门做测试的工程师基本无法独立完成(除非参与开发或者设计)。此外,如果测试代码专业性过强,要求专业能力极高并且准确率要求较高的情况下,也应该由开发编写较好。如果采用前者,两者来写利弊都非常明显,接下来就来讨论一下。

# 由测试工程师来做

优点 :不易进入思维惯性从而漏掉测试性逻辑。可与开发交替进行,节约时间成本。

缺点:不清楚代码功能性逻辑从而容易漏掉功能性测试逻辑,同时对于测试代码功底也有一定要求。

比如如果你不清楚processHeaders这个函数的内部实现,那么你一定不会写expect(headers['conTenT-Type']).toBeUndefined()去测试这一步,这仅仅还是最简单的例子。

再比如buildURL这个函数,如果测试人员对url参数的拼接不熟悉的话,各种测试情况必有遗漏,这对纯测试人员提出了更高的要求。

若95%以上的覆盖让一个单纯的测试去做必须与开发保持同步否则你细品。

# 由开发工程师来做

优点 :自个儿写的代码自个儿清楚,不容易漏掉功能性逻辑。

缺点:因为代码逻辑是自己写的,容易进入惯性误区,遗漏测试性逻辑。占用大量开发时间,所有工作由开发完成提升时间成本。

比如你写了deepMerge这个函数,但你未必会想到从边界性、不可变性、递归性、空测试指向去思考如何构建单元测试,毕竟逻辑是你写的,你很容易被思维禁锢。这需要长时间的积累经验。

# 总结

普遍来说,在开发阶段,测试一般在准备测试用例和报告,时间充裕,如果有条件,完全可以让测试交替完成单元测试而不用等到开发彻底完成,当然这对协同开发来说是要有一定要求的。

综上所述,单元测试由谁来写的问题因当结合公司技术构成、时间成本来决定。

在现实情况中其实基本上不用纠结这个问题。往往需要做单元测试的都是中大型公司或者是底层/框架性代码,这种公司有专门的测试/单元测试人员去做代码检测,因为对于底层或者框架性代码来说,一根寒毛都不能少也不能错,再者对于中大型企业来说,即便不是做此类开发,也有比较充裕的人手去安排这些事情。而对于小型企业来说,几乎又没有专门的测试甚至都不需要做单元测试,所以一般还是得开发来完成。所以这个问题也不用过于纠结了。

# 哪些工程需要用到单元测试?

前面说了这么多怎样考量应该由谁来完成单元测试,其实考虑这个问题之前更应该考虑的是是否真的需要单元测试。

前面也提到,如果您开发的是框架性质/组件性质/底层代码等严谨性很高、复用性较强、逻辑性较强、较多的代码时,单元测试是很有必要的,就拿做TS-Axios的单元测试来说,真是不做不知道,一做发现看似能完整运行的代码实则有好多极端性质的错误,单元测试真的方便找出问题了好多。

比如这段代码 return relativeURL ? baseURL.replace(//+$/, '') + '/' + relativeURL.replace(/^/+/, '') : baseURL

我写成了 return relativeURl ? baseURL.replace(//+$/, '') + relativeURl.replace('/^/+/', '') : baseURL

如果不是靠单元测试,真的很难发现。

再者如果您是开发app/web/运营系统等综合性质的代码时候,结合公司人力、时间成本可以选择对utils和核心逻辑代码做单元测试甚至不做。

除此之外,就真没有必要特意去做单元测试了,毕竟人力时间成本在那里摆起的。

# 结语

ts-axios的单元测试算是比较特殊,应为它对测试请求方面的专业知识有所要求,所以测试起来相对困难,在现实开发中,普通逻辑测试即可满足绝大部分需求。

本着技多不压身的老话,不管是由测试工程师来写还是开发工程师来做,单元测试作为代码严谨测试的重要一环,花时间掌握它是有必要的。学习单元测试的环境搭建、构造以及处理的逻辑,对于日常工作溢出多多,对于自身实力提升更是不用多说。当然,要成为一名合格的甚至是资深的单元测试师需要投入大量的时间积累学习相关知识,积累经验,建议和正则师一样,单独分离出来,如果没有特殊需求,掌握基础测试即可。

最后,附上一份完整的单元测试最终结果,100%覆盖每一行代码(可真是不容易😭):

avatar