饮杯茶的时间认识单元测试

182 阅读4分钟

本文旨在通过下午三点饮一杯茶的时间让从未接触过单元测试的同学认识一下单元测试,笔者也是在最近从完全不会到入门。小白可借鉴,欢迎老鸟吐槽指点!推荐阅读时间:6分钟。

我理解的单元测试

通过测试框架对程序里的1个或N个最小可测试单元做自动化测试。

测试测的是什么内容

本文使用 jest 框架写测试用例,jest 是最常用的 js 单元测试框架之一。

可能是一个方法

一般更关注测试这个方法的输入和输出,特别是对边界情况跟异常情况的测试。 源码:

// utils.ts
export function add(x: number, y: number): number {
  return x + y;
}

测试代码:

// index.spec.ts
import { add } from './utils';
it('test func add ', () => {
  expect(add(1, 2)).toBe(3);
});

可能是一个DOM事件的触发

一般更关注测试模拟一次DOM事件触发后,即将发生的事情,通常是在UI上的反馈,以及对应的数据层面的变更,例如: 源码:

// MyBar.tsx
export default class MyBar {
  ...
  myClick() {
    this.setState({ showText: true });
  }
  render() {
    return (
      <div onClick={this.myClick}>
        Click me!
        {
          this.state.showText
            ? <span className="click-text">click finish!</span>
            : null
        }
      </div>
    );
  }
}

测试代码:

// MyBar.spec.ts
import MyBar from './MyBar';
// 可以将 shallow 理解为是一个渲染的方法
import { shallow } from 'enzyme';

it('trigger MyBar click', () => {
  const MyBarWrapper = shallow(MyBar);
  // 模拟触发点击事件
  MyBarWrapper.simulate('click');
  
  expect(MyBarWrapper.find('.click-text')).toHaveLength(1);
  // MyBarWrapper.instance()会返回react实例
  expect(MyBarWrapper.instance().state.showText).toBe(true);
});

可能是包含一系列行为的一次操作

一般更关注测试操作过程中会触发哪些行为。 源码:

// utils.ts
export function isThreeOClock() { ... }
export function pickUpTheTeaCup() { ... }
export function drinkItAll() { ... }

// app.ts
import { isThreeOClock, pickUpTheTeaCup, drinkItAll } from './utils.ts';
export function drinkTeaWhenThreeOClock() {
    isThreeOClock();
    pickUpTheTeaCup();
    drinkItAll();
}

测试代码:

// utils.spec.ts
import { isThreeOClock, pickUpTheTeaCup, drinkItAll } from './utils.ts';
import { drinkTeaWhenThreeOClock } from './app.ts';

// 模拟 utils 模块,相当于告诉测试程序,有 isThreeOClock, pickUpTheTeaCup, drinkItAll 这三个方法,但具体是什么内容并不关心
jest.mock('./utils.ts');

it('test func drinkTeaWhenThreeOClock', () => {
    drinkTeaWhenThreeOClock();
    expect(isThreeOClock).toBeCalled();
    expect(pickUpTheTeaCup).toBeCalled();
    expect(drinkItAll).toBeCalled();
});

单元测试的意义

我认为包括但不限于这几点:

  • 项目代码质量的最基本保障,衡量项目质量的因素
  • 在写用例时有助于发现源码上的问题,例如一些基本的逻辑错误 相当于是对所开发的代码进行检查,提早发现问题。
  • 代码重构的保障 代码重构的成本降低了,不需太担心因重构带来致命性的问题。
  • 可作为代码逻辑、业务逻辑的描述或验证 测试代码甚至可以是一种新的语言,用来描述代码逻辑、业务逻辑的语言,能够告诉你现在跑在产线的逻辑是什么样的,很多时候产品都不一定知道的逻辑,测试代码也许可以给你答案。
  • 项目生命周期长短的考量因素之一 笔者在最近看到的文章里都有提到说,很多项目因为没有测试,不久后都会走向生命的结尾。
  • 作为集成测试、e2e测试的基础 可以了解一下测试的金字塔模型。
  • 作为项目的必要组成部分,以及CI/CD必要环节之一的呼声越来越高 一个比较健康的项目,CI/CD的流水线里肯定都有测试的节点,每天项目都要跑上几十次几百次的全量/增量测试。

你现在/以前不写单测的可能的原因,相应的反驳点

  • 项目没有要求写 可以主动跟项目负责人发起要求,一点点的补充单元测试,当然前提是不会较大影响目前项目的运转,并且最好是能够看到写单测所带来的收益,这样也有助于推进写单测这个事情。
  • 觉得没有必要写 看完上面的【单元测试的意义】之后,我认为对于一个项目来说是绝对有必要的。
  • 反而浪费时间 在没有单元测试的情况下,所花的迭代开发成本、人工测试成本等等,日积月累下来,肯定要比写单测的成本要高的多,甚至可能是指数级别的增长。
  • 项目历史包袱重,填坑都填不及,还写单测? 很可能填着填着,突然哪天项目因为另一个地狱般的坑而猝死。
  • 觉得写单测很难 可以先从一些最简单的模块、组件开始写起,就像考试时先写简单的题目一样。

了解更多

单元测试框架 jest官网 React 测试 react-testing-library enzyme

最后

一杯茶的时间里不可能介绍太多内容,也怕读者还没入门就选择放弃。主要希望在短短几分钟休息时间里,能够让没接触过单元测试的同学,能够对单元测试有改观,甚至开始看更多文章了解它,最好动起手来写写用例!