前端自动化测试-BDD-集成测试
何为BDD(Behavior Driven Development)行为驱动的开发,是一种敏捷软件开发的技术,他鼓励软件项目中的开发者,QA和非技术人员或商业参与者之间的协作。以上节TDD的代码为例子,将测试文件全部删除。从BDD的角度出发,开发人员不需要先写测试用例,直接编写代码即可。编写完代码之后,第二步就是需要站在用户的角度,怎么去测试,测试的重点是什么?从用户的行为角度出发,编写我们的测试
何为BDD(Behavior Driven Development)
-
行为驱动的开发,是一种敏捷软件开发的技术,他鼓励软件项目中的开发者,QA和非技术人员或商业参与者之间的协作。
-
以上节TDD的代码为例子,将测试文件全部删除。从BDD的角度出发,开发人员不需要先写测试用例,直接编写代码即可。
-
编写完代码之后,第二步就是需要站在用户的角度,怎么去测试,测试的重点是什么?从用户的行为角度出发,编写我们的测试用例。因为用户行为是连续的,比如toDOList案例,用户希望在输入框输入内容按下回车后,list组件可以显示出来。他们需要不同组建的一个协同。
-
所以BDD一般是结合集成测试来开发。
-
还是以todoList案例,等我们编写玩开发代码,站在用户的角度出发,我希望,输入内容之后,按下回车,能在List里面看到我想要的内容。根据这个需求编写测试用例。
it(`
1. 用户输入输入框内容
2. 点击回车
3. 列表中显示输入的内容
`, () => {
// 多个组件,使用mount
const wrapper = mount(<TodoList />);
const inputElm = wrapper.find("[data-test='input']");
const inputValue = "用户输入了内容";
// 模拟用户第一个行为
inputElm.simulate("change", {
target: { value: inputValue },
});
// 点击回车
inputElm.simulate("keyUp", {
keyCode: 13,
});
// 列表中应该显示内容
const listItems = wrapper.find("[data-test='list-item']");
console.log('listItems', listItems.debug());
expect(listItems.length).toBe(1);
expect(listItems.text()).toBe(inputValue);
});
如上,因为涉及到多个组件,所以必须使用mount渲染,然后模拟用户输入内容,触发回车事件,然后判断列表中是不是真的多出一个list。
测试通过。
何为BDD?
通过简单的案例我们可以知道BDD,是以用户行为驱动的开发,
- 流程就是 先根据功能编写代码,等功能开发完毕之后,
- 站在用户的角度出发,去编写测试用例。
- Bdd一般结合集成测试。
- 而TDD则是以测试驱动的开发,先编写测试用例,再开发代码。
- TDD一般结合单元测试。主要是为每个组件的功能进行测试。
TDD & BDD
TDD:
- 先写测试再写代码
- 一般结合单元测试,是白盒测试
- 测试重点在代码
- 安全感第
- 速度快(shallow)
BDD: - 先写代码,再写测试
- 一般结合集成测试使用,是黑盒测试
- 测试重点在UI(DOM)
- 安全感高
- 速度慢(mount)
如何进行Redux的测试
如果使用TDD进行redux的测试,那么action ,reducer等等都需要编写测试用例,代码量大。也不能保证数据流正确。
所以得使用BDD来进行redux的测试比较好。
- 还是以toDOlist案例,把状态存储到redux去。
改造一下demo:
Header组件
function Headers({ onAddItem }: Props) {
const dispatch = useDispatch();
const [value, setValue] = React.useState("");
return (
<div className="Header">
<input
className="header-input"
type="text"
data-test="input"
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
onKeyUp={(e) => {
if (e.keyCode === 13) {
dispatch({ type: "dispatch", value });
setValue("");
} else {
return;
}
}}
/>
</div>
);
}
list组件
function TodoList() {
const { items } = useSelector(
(state: any) => ({ items: state.todo.items }),
shallowEqual
);
return (
<div>
<Headers />
<div>------------</div>
{items.map((ctem) => {
return (
<div data-test="list-item" key={ctem}>
{ctem}
</div>
);
})}
</div>
);
}
将状态存储到redux去。
然后重新编写刚才的测试用例
it(`
1. 用户输入输入框内容
2. 点击回车
3. 列表中显示输入的内容
`, () => {
// 多个组件,使用mount
const wrapper = mount(
<Provider store={store}>
<TodoList />
</Provider>
);
const inputElm = wrapper.find("[data-test='input']");
const inputValue = "用户输入了内容";
// 模拟用户第一个行为
inputElm.simulate("change", {
target: { value: inputValue },
});
// 点击回车
inputElm.simulate("keyUp", {
keyCode: 13,
});
// 列表中应该显示内容
const listItems = wrapper.find("[data-test='list-item']");
console.log("listItems", listItems.debug());
expect(listItems.length).toBe(1);
expect(listItems.text()).toBe(inputValue);
});
这里需要跟外部一样,将store传进去给他。这里只是需要更改一下moutn的内容,其他东西根本不需要更改,这也是BDD的好处。
- 开发业务代码不用考虑测试用例
- 业务代码如果更改比如从state到redux,那么BDD测试用例是很少改动的,因为他不是基于代码的,而是基于用户角度,只要流程跑的通,就正确。(耦合度低)
- BDD驱动的集成测试,安全感较高,因为这个测试用例一通过,就表示这个功能是可以用的。而不会出现单元测试无法保证多个组件结合在一起的可用性。
- BDD的代码覆盖率会降低,但是影响不大。
异步代码的测试
- 还是以todoList为例
- 实现一个功能,在页面初始化的时候i请求数据,展示List。
useEffect(() => {
// 测试的时候我们不需要发送请求
axios.get("/xxx").then((res: any) => {
setItem((pre) => [...pre, ...res.data]);
});
}, []);
功能写完编写测试用例
因为测试用例需要执行组件,所以模拟axios
__mocks__/axios.ts
export default {
get(url) {
if (url === "/xxx") {
return new Promise((r) => {
r({ data: ["test1", "test2"] });
});
}
},
};
当我们使用mount渲染组件的时候,里面遇到axios的地方就会去上面获取,如上,返回了test1 test2。
test(`1.用户打开页面
2. 展示接口返回的数据
`, (done) => {
const wrapper = mount(
<Provider store={store}>
<TodoList />
</Provider>
);
// 异步需要等待
setTimeout(() => {
wrapper.update()
console.log(wrapper.debug())
const listItems = (wrapper as any).find("[data-test='list-item']");
expect(listItems.length).toBe(2);
done()
}, 1000);
});
因为请求时异步的,所以我们需要等待一下才能判断,然后wrapper.update去更新dom,此时dom已经有了请求回来的数据。因为这里是异步,所以需要用到done。
看下更新后的dom:
第一次渲染
console.log
<Provider store={{...}}>
<TodoList>
<div>
<Headers>
<div className="Header">
<input className="header-input" type="text" data-test="input" value="" onChange={[Function: onChange]} onKeyUp={[Function: onKeyUp]} />
</div>
</Headers>
<div>
------------
</div>
</div>
</TodoList>
</Provider>
没有List。
更新后:
<Provider store={{...}}>
<TodoList>
<div>
<Headers>
<div className="Header">
<input className="header-input" type="text" data-test="input" value="" onChange={[Function: onChange]} onKeyUp={[Function: onKeyUp]} />
</div>
</Headers>
<div>
------------
</div>
<div data-test="list-item">
test1
</div>
<div data-test="list-item">
test2
</div>
</div>
</TodoList>
</Provider>
已经渲染出list了。
- setTimeout的异步怎么优化?不可能等待所有setTImeout完成的。
useEffect(() => {
setTimeout(() => {
axios
.get("/xxx")
.then((res: any) => {
console.log("res----------", res);
setItem((pre) => [...pre, ...res.data]);
})
.catch((err) => {
});
}, 5000);
}, []);
需要使用jests提供的setTImoeut
eforeEach(() => {
console.log(123123);
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
test(`1.用户打开页面
2. 展示接口返回的数据
`, (done) => {
// 将所有setTimoeut里面执行
const wrapper = mount(
<Provider store={store}>
<TodoList />
</Provider>
);
jest.runAllTimers();
// 异步需要等待
setTimeout(() => {
wrapper.update();
const listItems = (wrapper as any).find("[data-test='list-item']");
expect(listItems.length).toBe(2);
done();
}, 1000);
});
Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
Snapshots: 1 obsolete, 0 total
Time: 6.854 s
这样就可以了。
总结:
- BDD就是先开发组件的功能,然后从用户角度出发,编写一套流程的测试用例,而不是单个单个的,当测试用例通过的时候,就表示这些组件结合起来的功能是完整的。
- 而TDD是先编写测试用例,(单元测试),然后再开发代码,可以保证每个组件的功能完整性,但是不能保证这些组件结合起来后的功能是否完整。
- BDD+集成测试对于业务代码是一个相对更好的方案,TDD+单元测试对于单独的函数库来说会更好。
前端自动化测试的优势
- 更好的代码组织,项目的可维护性增强
- 更小的bug出现概率,尤其回归测试的bug
- 修改工程质量差的项目,更加安全
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)