大家好,我是Line产品开发总部的 Youngjin Jang。
你在进行前端开发时会编写测试代码吗?由于工作性质的原因,前端开发人员需要在开发进度的后半段综合可交付成果(规格、设计、API)并创建最终产品,因此由于时间紧迫而很难编写测试代码。相反,在开发进度的前半部分,你可能会比其他职位拥有相对较多的时间。为什么不利用这段时间编写测试呢?
本文将介绍测试驱动开发的基本概念、使用 Cypress 的测试驱动开发方法,以及我们在开发 LINE Doctor 前端时如何实际使用测试驱动开发。
什么是 LINE Doctor?
LINE Doctor是一项在线医疗服务,允许用户通过LINE在家中接受体检、接受处方和药物。可靠性对于LINE Doctor来说非常重要,因此我们在开发前端时采用测试驱动开发来保证质量。
测试驱动开发概述
测试驱动开发是在开发的早期阶段首先编写测试,然后编写代码以使测试用例通过的过程。
首先编写失败的测试代码,然后实现该功能以使测试通过并重构代码。通过重复这个循环,可以构建高质量的代码库。
1.创建测试代码:首先编写函数的测试用例
2.实施:实施该功能直到测试通过
3.重构:整理已实施的代码
使用 Cypress 实践测试驱动开发
Cypress (https://www.cypress.io/)是一款前端测试工具,可以实现在真实浏览器中运行测试;Cypress 易于安装,易于创建测试代码,并可以让你实时检查测试结果。此外,Cypress 测试代码可在真实浏览器中运行,便于调试。Cypress 的这些功能使前端测试驱动开发成为可能。
在这里我们将安装 Cypress 并看看它如何进行测试驱动开发。首先,使用 npm 安装 Cypress 并启动 Cypress。
npm install cypress --save-dev
npx cypress open
当启动Cypress时,Cypress程序将如下所示执行。使用 Cypress 的测试驱动开发基于 E2E 测试。单击“E2E 测试”开始在 Google Chrome 中进行测试。
编写首先失败的测试代码。例如,假设你的任务管理服务实现了一个规范,即当用户访问任务列表页面时应显示任务列表。首先,创建cypress/e2e/todo.cy.js 文件并编写测试代码,如下所示。
// cypress/e2e/todo.cy.js
describe('任务管理', () => {
it('Task list is displayed', () => {
// 访问页面
cy.visit('http://localhost:3000/');
// 检查页面中是否有 data-testid 属性为 "todo-list "的元素
cy.get('[data-testid="todo-list"]').should('exist'); // 此测试失败,因为尚未实现。
});
});
该测试代码验证当用户访问该页面时,“任务列表”是否显示在屏幕上。
当你运行测试时,可以看到测试失败,如下所示。
为了通过测试,我们将实现一个显示任务列表的 UI。
<! -- index.html -->
<div data-testid="todo-list">
<! -- タスクリストが表示される場所 -->
</div>
实现后,可以查看Cypress,看到测试通过了,如下图。
接下来是重构阶段,但是由于没有足够的代码需要重构,所以我在这里跳过它。
随着我们不断开发任务管理服务,我们可能会需要添加任务、完成任务等新的需求。在这种情况下,可以通过重复循环来继续进行测试驱动开发,首先添加测试代码,然后继续实施,如下所示。
// cypress/integration/todo_spec.js
describe('タスク管理', () => {
// ... 以前のテストコード
it('タスクを追加する', () => {
// ページにアクセス
cy.visit('http://localhost:3000/');
// タスクを入力した後、エンターキーを押す
cy.get('[data-testid="new-todo"]').type('新しいタスク{enter}');
// タスクリストに新しいタスクが追加されたことを確認
cy.get('[data-testid="todo-list"]').should('contain', '新しいタスク');
});
});
LINE Doctor 案例研究
LINE Doctor有一项功能,允许用户在在线咨询后到自己选择的药房取药。如果所选药店距离用户地址超过15公里,可能无法亲自去取药,因此有一个功能,显示弹出窗口询问用户是否确实要取药在那家药店。我将介绍我是如何进行该功能的实际测试驱动开发的。
1.创建测试代码
首先,确定创建测试代码所需的信息。由于前端无法检查用户选择的药店距离用户地址是否超过15公里,因此在与后端开发人员讨论API后,我们决定API接口如下。使用此 API,前端可以将用户的地址和所选药房 ID 发送到后端,以检查距离是否超过 15 公里。
// このAPIは実際に使われているAPIではなく、加工したAPIです。
// request
GET /v1/is-pharmacy-nearby
{
"userAddress": "ユーザーの住所"
"pharmacyId": "薬を受け取る薬局のID"
}
// response
true || false
然后,用测试代码替换规范并创建它。编写测试代码时,首先编写一个作为基本功能的案例,然后添加从该案例派生的案例。对于前端无法控制的方面,例如 API 幕后发生的行为,请编写测试代码,重点关注传递给 API 的参数以及是否在适当的时间请求 API。在编写测试代码时,我们的目标是确保与利益相关者的所有讨论都反映在测试代码中。测试代码利用唯一的特性来避免运行所有测试用例,避免由于测试执行速度而导致生产力损失。
describe.only('選択した薬局とユーザーの住所の距離確認', () => {
// 基本になるテストケース
it('薬局がユーザーの住所から15km以上の場合、ポップアップを表示する', () => {
// バックエンドエンジニアと議論したAPI Interfaceをモックする
// 薬局がユーザーの住所から15km以上の場合なので、APIはfalseを返す
cy.intercept('GET', `/v1/is-pharmacy-nearby**`, 'false').as(
'fetchIsPharmacyNearby'
);
// ポップアップを表示するページにアクセスする
cy.visit(`/treatment/review`);
// 予約を完了させる
cy.get('[data-testid=confirm_reservation]').click();
// ポップアップが表示されることを確認する
cy.get('[data-testid="common_modal"]').should('exist');
cy.get('[data-testid="common_modal_description"]').should(
'contain',
'お薬を受け取る薬局の住所がご自宅住所から離れています。このまま予約を完了しますか?'
);
// APIを呼ぶときに正しいパラメータをわたしたか確認する
cy.wait('@fetchIsPharmacyNearby').then(({ request }) => {
const searchParams = new URL(request.url).searchParams;
expect(searchParams.get('userAddress')).to.equal('xxx');
expect(searchParams.get('pharmacyId')).to.equal(`999`);
});
});
// 派生したケース
it('薬局がユーザーの住所から15km未満の場合、ポップアップを表示しない', () => {
/// 薬局がユーザーの住所から15km未満の場合なので、APIはtrueを返す
cy.intercept('GET', `/v1/is-pharmacy-nearby**`, 'true');
// ...省略
// ポップアップが表示されないことを確認する
cy.get('[data-testid="common_modal"]').should('not.exist');
});
});
在我们的基本案例中,我们首先使用cy.intercept来确保 API 结果始终确定用户的地址距离药房至少 15公里。然后使用cy.get找到预约完成按钮,使用cy.click完成预约。此时, cy.should验证屏幕上是否显示了预期的结果(确认弹出窗口) 。
在派生情况下,将 API 结果设置为始终小于 15 公里,并验证当你按“完成预订”时不会出现确认弹出窗口。
当你添加测试代码并运行 Cypress 时,你可以看到测试失败,因为尚未实现显示弹出窗口的功能,因此未显示弹出窗口。
2. 实施
现在我们来编写实际的实现代码来通过测试。当用户按下预约按钮时,我们通过API检查用户选择的药店距离用户的地址是否超过15公里。如果 API 结果为 false,则显示弹出窗口。
// LINEドクターのフロントエンドはVueを利用しているので、サンプルコードはVueのコードになります。
<template>
<main>
<!-- ... -->
<button data-testid="confirm_reservation" @click="handleClickConfirm">
予約する
</button>
<!-- ... -->
</main>
</template>
<script>
export default {
// ...
methods: {
async handleClickConfirm() {
// ...
const response = await this.$axios.get(
'/v1/is-pharmacy-nearby',
{
params: {
userAddress: this.userAddress,
pharmacyId: this.pharmacyId,
},
}
);
if (!response.data) {
await this.$modal.open({
text: 'お薬を受け取る薬局の住所がご自宅住所から離れています。このまま予約を完了しますか?',
});
}
// ...
},
},
};
</script>
添加实现代码后,可以再次运行Cypress测试,确认测试成功。
3. 重构
接下来是重构阶段。在实现过程中,我在预订完成按钮的点击处理程序中编写了代码,但是预订完成按钮的处理程序包含多个处理逻辑,这使得它的可读性较差。因此,我们将相关逻辑分离到一个单独的函数中,并将其重构为从按钮的单击处理程序中调用。
<template>
<main>
<!-- ... -->
<button data-testid="confirm_reservation" @click="handleClickConfirm">
予約する
</button>
<!-- ... -->
</main>
</template>
<script>
export default {
// ...
methods: {
async handleClickConfirm() {
// ...
if (!await this.isPharmacyNearby()) {
await this.$modal.open({
text: 'お薬を受け取る薬局の住所がご自宅住所から離れています。このまま予約を完了しますか?',
});
}
// ...
},
async isPharmacyNearby() {
const response = await this.$axios.get(
'/v1/is-pharmacy-nearby',
{
params: {
userId: this.userId,
pharmacyId: this.pharmacyId,
},
}
);
return response.data;
}
},
};
</script>
这样,我们在开发LINE Doctor的UI时,首先编写测试代码,编写通过测试代码的实现代码,然后进行重构。
概括
本文介绍了测试驱动开发的基础知识、如何使用 Cypress 实践测试驱动开发,以及如何通过 LINE Doctor 中的具体开发示例实际应用测试驱动开发。测试驱动开发是一种支持高质量软件开发的强大方法,而 Cypress 则让前端测试驱动开发变得更加容易。
大家何不尝试一下测试驱动开发,让自己的前端开发更可靠呢?
行动吧,在路上总比一直观望的要好,未来的你肯定会感谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入群: 759968159,里面有各种测试开发资料和技术可以一起交流哦。
最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】
软件测试面试文档
我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。