🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快
单元测试是很常见的技术的名词,但背后的逻辑和原理你是否清楚,让我们一起review一下。
1、单元测试是什么?
单测是单元测试,主要是测试一个最小逻辑块。比如一个函数、一个react
、vue
组件。
2、为什么要写单元测试?
这里有短期和长远,两个方面做打算:
短期:
- 希望开发者在开发过程中,就要想清楚多种
case
的情况,来检测这个最小单元的可靠性 - 举个例:
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');
});
});
长期:
- 从长期的维护角度来看的话,各个最小单元都是可能有被修改的风险。如果有完整的单侧的
case
,是可以保证:迭代、修改后的功能单元,不是会出现一些边缘问题 - 并且长期来看:单侧也可以作为一个这个功能单元的使用说明书的作用。帮助开发更好的理解功能。
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、单元测试中需要注意哪些问题?为什么单元测试会引发这些问题?
单元测试的核心问题就是准备环境,为了让单测可以正常执行,我们需要准备好上下游环境,还有依赖环境。
- 比如你要测试一个
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()
})
})
为什么单元测试会引发这些问题,感觉这么麻烦?
-
因为单测是运行在
node
环境里跑,相当于本地,本地去掉下游rpc
服务或走服务发现,本来就是不行的,因为环境隔离。本地加代理,也只能调通测试环境rpc
服务 -
单测环境是一个“干净”的环境,不主动准备的话 是没有
node_modules
的。如果你的测试单元 有各种依赖,单测环境肯定是没有的,除非你自己准备好 或者mock
模拟/代理掉
6、哪些场景适合写单元测试?
- 首先写单测肯定是有成本的,我个人认为,非常重要的toC项目才有必要写单测;
- 先覆盖核心功能;
- 功能单一的函数或组件是很适合写单测的。
7、怎么定义一个单元测试写的好不好?
单纯不考虑成本的话,那么一个单测的好坏应该是看这一个单元的功能覆盖率 + 边界case的覆盖情况。越多越好。
8、对单元测试成本和收益的思考?
团队内的任何决策都是有成本的,单测也不例外,需了解成本和收益后,再考虑在团队内推进。
单测成本:
主要是开发成本:写单测、code review,边界case覆盖。
单测收益:
未来的维护成本会更低,项目质量、稳定性更好。
有利于倒逼开发写出高质量的代码、关注稳定性。
我个人思考,还是想低成本得到所有好处(“我都要”):选择性写单元测试
个人认为写单元测试的条件:toC且核心流程 + 很难自测的部分(边界case) + e2e或快照测试很难覆盖到的部分
- 某些业务场景,后续肯定也是要自测的话,那这部分相关的可以不用写单测。
- e2e或快照测试,能覆盖到的,可以不写单测。
最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
这些资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。