什么是 TDD 的开发方式
TDD(Test-Driven Development) 测试驱动开发
- 先根据需求写测试用例
- 测试用例全部是失败的状态
- 开始写代码实现功能
- 将所有的测试用例由失败调为成功状态
以 TDD 的思想开发一个颜色选择器组件
需求分析
显示
- 左侧显示当前颜色
- 右侧显示十种常用的颜色
- 右侧最后一个是透明,点击可以清除颜色效果
点击
- 点击左侧,显示颜色选择器弹框,在颜色选择框中点击,或者修改
input
中的值后,将新的值以事件的形式发射出去。 - 点击右侧的颜色,将新的值以事件的形式发射出去,左侧修改为点击的颜色。
测试用例编写
代码语言:javascript复制import type { VueWrapper } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import ColorPicker from '@/components/ColorPicker.vue';
import rgbHex from 'rgb-hex';
const defaultColors = [
'#ffffff',
'#f5222d',
'#fa541c',
'#fadb14',
'#52c41a',
'#1890ff',
'#722ed1',
'#8c8c8c',
'#000000',
'',
];
// 定义 wrapper
let wrapper: VueWrapper<any>;
// 测试 UserProfile.vue
describe('UserProfile.vue', () => {
beforeAll(() => {
// 获取组件
wrapper = mount(ColorPicker, {
// 传入到组件内部的属性
props: { value: '#ffffff' },
});
});
// 测试页面是否出现了正确的结构
it('should render the current interface', async () => {
// 测试左侧是否为 input , 类型和值是否正确
expect(wrapper.find('input').exists()).toBeTruthy();
const input = wrapper.get('input').element;
expect(input.type).toBe('color');
expect(input.value).toBe('#ffffff');
// 测试右侧是否有颜色的列表
expect(wrapper.findAll('.picked-color-list li').length).toBe(
defaultColors.length,
);
// 检查一个元素的 css backgroundColor 属性是否等于对应的颜色
const firstItem = wrapper.get('li:first-child div').element as HTMLElement;
expect('#' rgbHex(firstItem.style.backgroundColor)).toBe(
defaultColors[0],
);
// 测试最后一个元素是否有特殊的类名
const lastItem = wrapper.get('li:last-child div').element as HTMLElement;
expect(lastItem.classList.contains('transparnet-back ')).toBeTruthy();
});
// 测试 input 修改以后,是否发送对应的事件和对应的值
it('should render username when login is true', async () => {
const blackHex = '#000000';
// 获取 input
const input = wrapper.get('input');
// 修改 input 的值
await input.setValue(blackHex);
// 是否发射了事件
expect(wrapper.emitted()).toHaveProperty('change');
const events = wrapper.emitted('change')!;
// 发射事件的参数是否正确
expect(events[0]).toEqual([blackHex]);
});
// 测试点击右侧颜色列表以后,是否发送对应的值
it('should call logout and show message,call router.push after timeout', async () => {
const firstItem = wrapper.get('li:first-child div');
// 触发点击事件
firstItem.trigger('click');
// 是否发射了事件
expect(wrapper.emitted()).toHaveProperty('change');
const events = wrapper.emitted('change')!;
// 发射事件的参数是否正确
expect(events[1]).toEqual([defaultColors[0]]);
});
});
目前所有的测试用例都是失败的,接下来我们就通过编码让测试用例通过。
代码编写
代码语言:javascript复制<template>
<div class="lego-color-picker">
<div class="native-color-container">
<input type="color"
:value="value"
@input="onChange($event.target.value)">
</div>
<ul class="picked-color-list">
<li v-for="(item, key) in colors"
:key="key"
@click.prevent="onChange(item)">
<div :style="{ backgroundColor: item }"
class="color-item"
v-if="item.startsWith('#')"></div>
<div v-else
class="color-item transparnet-back"></div>
</li>
</ul>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
const defaultColors = ['#ffffff', '#f5222d', '#fa541c', '#fadb14', '#52c41a', '#1890ff', '#722ed1', '#8c8c8c', '#000000', '']
export default defineComponent({
props: {
value: {
type: String
},
colors: {
type: Array as PropType<string[]>,
default: defaultColors
}
},
emits: ['change'],
setup(props, context) {
const onChange = (color: string) => {
// 触发事件
context.emit('change', color)
}
return {
onChange
}
}
})
</script>
<style>
.lego-color-picker {
display: flex;
}
.native-color-container {
width: 40%;
}
.native-color-container input[type="color"] {
width: 100%;
cursor: pointer;
height: 50px;
border: 0;
padding: 0;
background-color: transparent;
}
.picked-color-list {
padding: 0 0 0 5px;
margin: 0;
width: 60%;
display: flex;
list-style-type: none;
flex-wrap: wrap;
justify-content: space-between;
}
.picked-color-list li {
flex: 1;
width: 20%;
min-width: 20%;
max-width: 20%;
}
.color-item {
padding: 3px;
width: 20px;
height: 20px;
border-radius: 3px;
margin-right: 5px;
cursor: pointer;
border: 1px solid #ccc;
}
.transparnet-back {
background: transparent;
}
</style>
现在的测试用例已经全部通过了,大功告成。