对单测的思考(稳定性建设)

881 阅读5分钟

背景

单测是很常见的技术的名词,但背后的逻辑和原理你是否清楚,让我们一起review一下。

1. 单测是什么?🤔

单测是单元测试

  • 主要是测试一个最小逻辑块。比如一个函数、一个react、vue 组件

2. 为什么要写单测?🤔

这里有短期和长远,两个方面做打算

  1. 短期:

    1. 希望开发者在开发过程中,就要想清楚多种case的情况,来检测这个最小单元的可靠性

    2. 举个例:

      describe('test getUriEnd', () => {
        it('case1', async () => {
          const ret = getUriEnd(...);
          expect(ret).toBe('...');
        });
      
        it('case2', async () => {
          const ret = getUriEnd(...);
          expect(ret).toBe('...');
        });
      
        it('case3', async () => {
          const ret = getUriEnd('');
          expect(ret).toBe('error');
        });
      
        it('case4', async () => {
          const ret = getUriEnd([]);
          expect(ret).toBe('error');
        });
      });
      
  2. 长期

    1. 从长期的维护角度来看的话,各个最小单元都是可能有被修改的风险。如果有完整的单侧的case,是可以保证:迭代、修改后的功能单元,不是会出现一些边缘问题

    2. 并且长期来看:单侧也可以作为一个这个功能单元的使用说明书的作用。帮助开发更好的理解功能

3. 单测是跑在什么环境的?🤔🤔

是跑在node环境的

那为什么可以测试一些和浏览器环境相关的操作?比如: document.createElement

test('use jsdom in this test file', () => {  
    const element = document.createElement('div');  
    expect(element).not.toBeNull();  
});
  • 需要设置当前单测运行的环境,否则上面的单测会报错,因为单测是跑在node环境的(我这里以jest为例)

    • 在jest内,需要配置 testEnvironment: 'jsdom' (不主动配置的话,这里是node,代表node环境)

    • jest文档:jestjs.io/zh-Hans/doc…

那jest是怎么做到模拟浏览器环境的?

  • jest用的是jsdom:github.com/jsdom/jsdom (jsdom模拟浏览器环境的原理,可以参考jsdom的官网)

  • 本质还是运行在node内,只不过用了jsdom来模拟浏览器环境

4. 单测的原理是什么?🤔

原理是:

  • 给最小单元:提供运行时环境(node或浏览器环境,且包含对应依赖)。让这个最小单元在这个环境里面去执行,跑case,检验是否符合预期

5. 单测中需要注意哪些问题?为什么单测会引发这些问题?🤔🤔

单测的核心问题就是准备环境,为了让单测可以正常执行,我们需要准备好上下游环境,还有依赖环境

  1. 比如你要测试一个vue组件的props,那么你就需要为他准备好相关的vue实例,往单测的node环境里面注入Vue,例如 import { shallowMount } from '@vue/test-utils'

    import { shallowMount } from '@vue/test-utils'
    import Drawer from '@/components/common/drawer.vue'
    
    test('test drawer', () => {
      const wrapper = shallowMount(Drawer, {
        propsData: {
          direction: 'top',
          show: true,
          canConfirm: true,
          title: 'my drawer',
          leftText: 'left text',
          rightText: 'right text'
        }
      })
      const text = wrapper.text()
      expect(text).toContain('my drawer')
      expect(text).toContain('left text')
      expect(wrapper.get('.drawer-top')).not.toBeNull()
    
      wrapper.find('.header-right').trigger('click')
      wrapper.find('.header-left').trigger('click')
    
      expect(wrapper.emitted()).toHaveProperty('onConfirm')
      expect(wrapper.emitted()).toHaveProperty('onCancel')
    })
    
    • 如果涉及到路由相关的话,那么还需要准备路由相关依赖

    • 有些依赖实在不好准备的话,需要设置mock(比如依赖一个额外的SDK,但这个SDK没法在node里面的跑)

      import { mount, createLocalVue } from '@vue/test-utils'
      import Register from '@/components/xxx.vue'
      import Vuex from 'vuex'
      
      const localVue = createLocalVue()
      localVue.use(Vuex)
      
      jest.mock('@xxx/sdk.css', () => { // 这里设mock
        return ''
      })
      
      describe('test register', () => {
        const store = new Vuex.Store({
          actions: {},
          state: {
            form: {
              formData: {
                selectCountry: '',
              },
            },
          },
          getters: {
            'user/userCountry': () => ''
          },
        })
        const wrapper = mount(Register, {
          store,
          localVue,
          mocks: { // 这里也可以设mock
            $route: {
              query: {}
            }
          }
        })
        it('should vm', () => {
          expect(wrapper.vm).toBeTruthy()
        })
      
        it('should xxx', () => {
          expect(wrapper.find('#xxxdom').exists()).toBeTruthy()
        })
      })
      
  2. 如果涉及到接口请求的话,接口是下游rpc或者是服务发现的话,那么单侧环境是肯定调不通的。因为有环境隔离。这种情况下就只能Mock数据了

为什么单测会引发这些问题,感觉这么麻烦?

  • 因为单测是运行在node环境里跑,相当于本地,本地去掉下游rpc服务或走服务发现,本来就是不行的,因为环境隔离。本地加代理,也只能调通测试环境rpc服务

  • 单测环境是一个“干净”的环境,不主动准备的话 是没有node_modules的。如果你的测试单元 有各种依赖,单测环境肯定是没有的,除非你自己准备好 或者 mock模拟/代理掉

6. 哪些场景适合写单测?🤔

  1. 首先写单测肯定是有成本的,我个人认为,非常重要的toC项目 才有必要写单测

  2. 先覆盖核心功能

  3. 功能单一的函数 或 组件 是很适合写单测的

7. 怎么定义一个单测写的好不好?🤔

单纯不考虑成本的话,那么一个单测的好坏应该是看这一个单元的功能覆盖率 + 边界case的覆盖情况。越多越好

8. 对单测成本和收益的思考?🤔

团队内的任何决策都是有成本的,单测也不例外,需了解成本和收益后,再考虑在团队内推进。

单测成本:

  • 主要是开发成本:写单测、code review,边界case覆盖

单测收益:

  1. 未来的维护成本会更低,项目质量、稳定性更好
  2. 有利于倒逼开发写出高质量的代码、关注稳定性

我个人思考,还是想低成本得到所有好处(“我都要”):选择性写单测

  1. 个人认为写单测的条件:toC且核心流程 + 很难自测的部分(边界case) + e2e或快照测试很难覆盖到的部分

    • 某些业务场景,后续肯定也是要自测的话,那这部分相关的可以不用写单测

    • e2e或快照测试,能覆盖到的,可以不写单测


码字不易,点赞鼓励!!


本文正在参加「金石计划」