Vue中的BDD与集成测试
一、BDD的相关概念
BDD是用户行为驱动开发。
在看BDD之前建议先看https://blog.youkuaiyun.com/fageaaa/article/details/149318569。接着上次内容继续讲:
先新建dev3分支,然后将之前的dev2分支代码合并到dev3分支。
我们先把__test__
下面的unit文件夹删除。
如上我们先开发了Todolist组件,然后才做测试。开发好组件之后,运行起来,模拟用户操作。
然后根据用户行为去编写测试用例。
import { mount } from "@vue/test-utils";
import TodoList from "../../TodoList.vue";
import { findTestWrapper } from "../../../../utils/testUtils";
it(`
1.用户会在Header组件输入框输入内容
2.用户会点击回车按键
1.列表项应该增加用户输入内容的列表项
`, () => {
//多个组件一起测试,不能用shallowMount,得用mount
const wrapper = mount(TodoList);
console.log(wrapper);
const inputElem = findTestWrapper(wrapper, "header-input").at(0);
console.log(inputElem);
const content = "test content";
inputElem.setValue(content);
inputElem.trigger("change");
inputElem.trigger("keyup.enter");
//这里使用了异步处理
wrapper.vm.$nextTick(() => {
const listItems = findTestWrapper(wrapper, "list-item");
expect(listItems.length).toBe(1);
expect(listItems.at(0).text()).toContain(content);
});
});
注意修改脚本:
然后输入npm run test:ing运行测试。
总结一下:
二、使用BDD和集成测试进行Vuex项目的测试
新建vuex-branch分支:
安装vuex:
npm install vuex --save
我们对上述代码使用vuex:
header.vue主要代码如下:
<template>
<div class="header">
<div class="header-content">
TodoList
<input
class="header-input"
data-test="header-input"
:value="inputValue"
@input="(e) => changeInputValue(e.target.value)"
@keyup.enter="addTodoItem"
placeholder="ADD TodoItem"
/>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
name: "Header",
data() {
return {};
},
computed: {
...mapState({
inputValue: (state) => state.inputValue,
}),
},
methods: {
addTodoItem() {
if (this.inputValue) {
this.$emit("add", this.inputValue);
this.changeInputValue("");
}
},
...mapMutations({
changeInputValue: "changeInputValue",
}),
},
};
</script>
运行会报错。提示识别不了state,是因为引入了vuex。
我们对测试文件进行以下改动即可正常运行:
然后我们可以npm run test:cov看看集成测试的覆盖率:
我们会发现该集成测试的覆盖率并不是很高,要想让集成测试覆盖率大幅度提高,需要付出的成本是比较高的。但测试覆盖率并不是最终的目标。测试覆盖率高不一定软件的质量就高。
在一个项目中我们一般会单元测试和集成测试一块使用。比如基于函数这样子的逻辑,我们就可以使用单元测试。
然后在__test__
文件夹可以建立unit文件夹专门用于单元测试:
再比如在刚刚的集成测试中,我们可以对store再进行单元测试:
修改脚本:
三、异步测试
新建一个分支:
安装axios:
npm install axios --save
我们需要用mock来模拟接口数据:
再看一种情景:
import { mount } from "@vue/test-utils";
import TodoList from "../../TodoList.vue";
import { findTestWrapper } from "../../../../utils/testUtils";
import store from "../../../../store";
beforeEach(() => {
//每个测试用例运行之前,执行useFakeTimers
//重新清零
jest.useFakeTimers();
});
it(`
1.用户会在Header组件输入框输入内容
2.用户会点击回车按键
1.列表项应该增加用户输入内容的列表项
`, () => {
//多个组件一起测试,不能用shallowMount,得用mount
const wrapper = mount(TodoList, {
store,
});
const inputElem = findTestWrapper(wrapper, "header-input").at(0);
const content = "test content";
inputElem.setValue(content);
inputElem.trigger("change");
inputElem.trigger("keyup.enter");
//这里使用了异步处理
wrapper.vm.$nextTick(() => {
const listItems = findTestWrapper(wrapper, "list-item");
expect(listItems.length).toBe(1);
expect(listItems.at(0).text()).toContain(content);
});
});
// it(`
// 1.用户进入页面时候,请求远程数据
// 2.列表应该展示远程返回的数据
// `, () => {
// const wrapper = mount(TodoList, {
// store,
// });
// wrapper.vm.$nextTick(() => {
// const listItems = findTestWrapper(wrapper, "list-item");
// console.log(listItems.length);
// expect(listItems.length).toBe(2);
// });
// });
it(`
1.用户进入页面时候,等待5s
2.列表应该展示远程返回的数据
`, () => {
const wrapper = mount(TodoList, {
store,
});
expect(setTimeout).toHaveBeenCalledTimes(1);
jest.runAllTimers();
wrapper.vm.$nextTick(() => {
const listItems = findTestWrapper(wrapper, "list-item");
expect(listItems.length).toBe(2);
});
});
PS:用jest.advanceTimersByTime也是可以的。
再看一个例子,将代码恢复:
编写接口失败的测试用例:
import { mount } from "@vue/test-utils";
import TodoList from "../../TodoList.vue";
import { findTestWrapper } from "../../../../utils/testUtils";
import store from "../../../../store";
import axios from "../../__mocks__/axios";
beforeEach(() => {
axios.success = true;
//每个测试用例运行之前,执行useFakeTimers
//重新清零
// jest.useFakeTimers();
});
it(`
1.用户会在Header组件输入框输入内容
2.用户会点击回车按键
1.列表项应该增加用户输入内容的列表项
`, () => {
//多个组件一起测试,不能用shallowMount,得用mount
const wrapper = mount(TodoList, {
store,
});
const inputElem = findTestWrapper(wrapper, "header-input").at(0);
const content = "test content";
inputElem.setValue(content);
inputElem.trigger("change");
inputElem.trigger("keyup.enter");
//这里使用了异步处理
wrapper.vm.$nextTick(() => {
const listItems = findTestWrapper(wrapper, "list-item");
expect(listItems.length).toBe(1);
expect(listItems.at(0).text()).toContain(content);
});
});
it(`
1.用户进入页面时候,请求远程数据
2.列表应该展示远程返回的数据
`, () => {
const wrapper = mount(TodoList, {
store,
});
wrapper.vm.$nextTick(() => {
const listItems = findTestWrapper(wrapper, "list-item");
expect(listItems.length).toBe(2);
});
});
// it(`
// 1.用户进入页面时候,等待5s
// 2.列表应该展示远程返回的数据
// `, () => {
// const wrapper = mount(TodoList, {
// store,
// });
// expect(setTimeout).toHaveBeenCalledTimes(1);
// jest.runAllTimers();
// wrapper.vm.$nextTick(() => {
// const listItems = findTestWrapper(wrapper, "list-item");
// expect(listItems.length).toBe(2);
// });
// });
it(`
1.用户进入页面时候,请求远程数据失败
2.列表应该展示为空数据,不应该挂掉
`, () => {
axios.success = false;
const wrapper = mount(TodoList, {
store,
});
wrapper.vm.$nextTick(() => {
const listItems = findTestWrapper(wrapper, "list-item");
expect(listItems.length).toBe(0);
});
});
修改axios.js:
const UndoList = {
success: true,
data: [
{
status: "div",
value: "test1",
},
{
status: "div",
value: "test2",
},
],
};
export default {
get(url) {
if (url === "/getUndoList.json") {
return new Promise((resolve, reject) => {
if (this.success) {
resolve(UndoList);
} else {
reject(new Error());
}
});
}
},
};