Vue2 应用测试学习 03 - TDD 案例(其他测试用例、快照测试、配置代码覆盖率统计、codecov 上传覆盖率统计报告、Github Actions 自动化测试和持续集成)

本文主要是介绍Vue2 应用测试学习 03 - TDD 案例(其他测试用例、快照测试、配置代码覆盖率统计、codecov 上传覆盖率统计报告、Github Actions 自动化测试和持续集成),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

TodoApp 切换全选

点击全选按钮,修改全部任务项的状态,并且按钮的样式随着全部任务项的选中状态改变。

编写测试用例

test('Toggle All', async () => {const toggleAll = wrapper.findComponent('input[data-testid="toggle-all"]')// 选中全选按钮await toggleAll.setChecked()// 断言所有的任务都被选中wrapper.vm.todos.forEach(todo => {expect(todo.done).toBeTruthy()})// 取消完成状态await toggleAll.setChecked(false)wrapper.vm.todos.forEach(todo => {expect(todo.done).toBeFalsy()})
})test('Toggle All State', async () => {const toggleAll = wrapper.findComponent('input[data-testid="toggle-all"]')// 让所有任务都变成完成状态wrapper.vm.todos.forEach(todo => {todo.done = true})// 等待视图更新await wrapper.vm.$nextTick()// 断言 toggleAll 选中expect(toggleAll.element.checked).toBeTruthy()// 取消某个任务未完成,断言 toggleAll 未选中wrapper.vm.todos[0].done = falseawait wrapper.vm.$nextTick()expect(toggleAll.element.checked).toBeFalsy()// 当没有任务的时候,断言 toggleAll 未选中await wrapper.setData({todos: [],})expect(toggleAll.element.checked).toBeFalsy()
})

完善组件功能

// template
<inputid="toggle-all"v-model="toggleAll"data-testid="toggle-all"class="toggle-all"type="checkbox"
/>// js
computed: {toggleAll: {get() {// 获取 toggleAll 的选中状态return this.todos.length && this.todos.every(t => t.done)},set(checked) {this.todos.forEach(todo => {todo.done = checked})},},
},

TodoFooter 未完成任务数量

编写测试用例

// src\components\TodoApp\__tests__\TodoFooter.js
import { shallowMount } from '@vue/test-utils'
import TodoFooter from '@/components/TodoApp/TodoFooter'describe('TodoFooter.js', () => {/** @type {import('@vue/test-utils').Wrapper} */let wrapper = nullbeforeEach(async () => {const todos = [{ id: 1, text: 'eat', done: false },{ id: 2, text: 'play', done: true },{ id: 3, text: 'sleep', done: false },]wrapper = shallowMount(TodoFooter, {propsData: {todos,},})})test('Done Todos Count', () => {const count = wrapper.vm.todos.filter(t => !t.done).lengthconst countEl = wrapper.findComponent('[data-testid="done-todos-count"]')expect(Number.parseInt(countEl.text())).toBe(count)})
})

完善组件功能

<!-- src\components\TodoApp\TodoFooter.vue -->
<template><footer class="footer"><!-- This should be `0 items left` by default --><span class="todo-count"><strong data-testid="done-todos-count">{{ doneTodosCount }}</strong> item left</span><!-- Remove this if you don't implement routing --><ul class="filters"><li><a class="selected" href="#/">All</a></li><li><a href="#/active">Active</a></li><li><a href="#/completed">Completed</a></li></ul><!-- Hidden if no completed items are left ↓ --><button class="clear-completed">Clear completed</button></footer>
</template><script>
export default {name: 'TodoFooter',props: {todos: {type: Array,required: true,},},computed: {doneTodosCount() {return this.todos.filter(t => !t.done).length},},
}
</script>

注意在 TodoApp 组件中向 TodoFooter 组件传入必传属性:<TodoFooter :todos="todos" />

TodoFooter 清除已完成任务按钮显示状态

编写测试用例

test('Clear Completed Show', () => {// beforeEach 中初始化的数据是 props// 而 props 是不能被子组件直接修改的// 所以这里要单独初始化数据const todos = [{ id: 1, text: 'eat', done: false },{ id: 2, text: 'play', done: false },{ id: 3, text: 'sleep', done: false },]wrapper = shallowMount(TodoFooter, {propsData: {todos,},})const button = wrapper.findComponent('[data-testid="clear-completed"]')expect(button.exists()).toBeFalsy()
})

完善组件功能

// template
<buttonv-if="isClearCompletedShow"data-testid="clear-completed"class="clear-completed"@click="$emit('clear-completed')"
>Clear completed
</button>// js
computed: {...isClearCompletedShow() {return this.todos.some(t => t.done)},
},

清除已完成任务

TodoFooter

编写测试用例

test('Clear Completed', async () => {const button = wrapper.findComponent('[data-testid="clear-completed"]')await button.trigger('click')expect(wrapper.emitted()['clear-completed']).toBeTruthy()
})

完善组件功能

// template
<buttonv-if="isClearCompletedShow"data-testid="clear-completed"class="clear-completed"@click="$emit('clear-completed')"
>Clear completed
</button>

TodoApp

编写测试用例

test('Clear All Completed', async () => {wrapper.vm.handleClearCompleted()await wrapper.vm.$nextTick()expect(wrapper.vm.todos).toEqual([{ id: 1, text: 'eat', done: false },{ id: 3, text: 'sleep', done: false },])
})

完善组件功能

// template
<TodoFooter:todos="todos"@clear-completed="handleClearCompleted"
/>// js
methods: {...handleClearCompleted() {// 清除所有已完成的任务项this.todos = this.todos.filter(t => !t.done)},
},

TodoApp 数据筛选($route)

根据路由路径筛选任务项,需要配合 Vue Router 使用:

  • 可以创建一个局部的 Vue(localVue),为其注册 vue-router,只对这个局部的 Vue 生效
    • 不过这样又会增加外部依赖(vue-router 实例),并且每次测试都要加载 vue-router 还会存在性能损耗
  • 建议伪造(mock) $route$router

将组件配置为路由

<!-- src\App.vue -->
<template><div id="app"><!-- <TodoApp /> --><<router-view /></div>
</template><script>
// import TodoApp from '@/components/TodoApp'export default {name: 'App',// components: { TodoApp },
}
</script>
// src\router\index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import TodoApp from '@/components/TodoApp'Vue.use(VueRouter)const routes = [{path: '/',component: TodoApp,},{path: '/active',component: TodoApp,},{path: '/completed',component: TodoApp,},
]const router = new VueRouter({routes,
})export default router

编写测试用例

beforeEach(async () => {const $route = {path: '/',}wrapper = shallowMount(TodoApp, {mocks: {// 伪造 $route$route,},})const todos = [{ id: 1, text: 'eat', done: false },{ id: 2, text: 'play', done: true },{ id: 3, text: 'sleep', done: false },]// 初始化默认数据,并等待视图更新await wrapper.setData({todos,})
})...test('Filter Todos', async () => {// 将路由导航到 /wrapper.vm.$route.path = '/'await wrapper.vm.$nextTick()// 断言 filterTodos = 所有的任务expect(wrapper.vm.filterTodos).toEqual([{ id: 1, text: 'eat', done: false },{ id: 2, text: 'play', done: true },{ id: 3, text: 'sleep', done: false },])// 将路由导航到 /activewrapper.vm.$route.path = '/active'await wrapper.vm.$nextTick()// 断言 filterTodos = 所有的未完成任务expect(wrapper.vm.filterTodos).toEqual([{ id: 1, text: 'eat', done: false },{ id: 3, text: 'sleep', done: false },])// 将路由导航到 /completedwrapper.vm.$route.path = '/completed'await wrapper.vm.$nextTick()// 断言 filterTodos = 所有的已完成任务expect(wrapper.vm.filterTodos).toEqual([{ id: 2, text: 'play', done: true },])
})

完善组件功能

// template
<TodoItemv-for="todo in filterTodos":key="todo.id":todo="todo"@delete-todo="handleDeleteTodo"@edit-todo="handleEditTodo"
/>// js
computed: {...// 过滤数据filterTodos() {// 获取路由路径const path = this.$route.path// 根据路由路径过滤数据switch (path) {// 所有未完成任务case '/active':return this.todos.filter(t => !t.done)// 所有已完成任务case '/completed':return this.todos.filter(t => t.done)// 所有任务列表default:return this.todos}},
},

TodoFooter 高亮导航链接(router-link)

完善导航高亮功能

因为要使用路由导航,可以先实现功能,再编写测试

<ul class="filters"><li><router-link to="/" exact>All</router-link></li><li><router-link to="/active">Active</router-link></li><li><router-link to="/completed">Completed</router-link></li>
</ul>

设置路由高亮样式:

// src\router\index.js
...
const router = new VueRouter({routes,linkActiveClass: 'selected',
})export default router

编写测试用例

现在 TodoFooter 组件使用了 <router-link> 组件,就需要引入 Vue Router 了,否则运行测试会报错组件未注册。

测试的行为可以是只有需要高亮的导航链接有 selected classname。

// src\components\TodoApp\__tests__\TodoFooter.js
import { shallowMount, createLocalVue, mount } from '@vue/test-utils'
import TodoFooter from '@/components/TodoApp/TodoFooter'
import VueRouter from 'vue-router'// 创建局部 Vue
const localVue = createLocalVue()
// 为局部 Vue 注册 VueRouter,不影响其他 Vue
localVue.use(VueRouter)
const router = new VueRouter({linkActiveClass: 'selected',
})describe('TodoFooter.js', () => {/** @type {import('@vue/test-utils').Wrapper} */let wrapper = nullbeforeEach(async () => {const todos = [{ id: 1, text: 'eat', done: false },{ id: 2, text: 'play', done: true },{ id: 3, text: 'sleep', done: false },]// 注意:使用原来的 shallowMount 不会渲染 router-link 子组件// 这里需改用 mountwrapper = mount(TodoFooter, {propsData: {todos,},// 挂载局部 Vue 和 routerlocalVue,router,})})test('Done Todos Count', () => {...})test('Clear Completed Show', () => {...wrapper = shallowMount(TodoFooter, {propsData: {todos,},// 挂载局部 Vue 和 routerlocalVue,router,})...})test('Clear Completed', async () => {...})test('Router Link ActiveClass', async () => {// findAllComponents 返回 WrapperArray,它并不是一个数组类型// 需要使用内部方法来访问const links = wrapper.findAllComponents({ name: 'RouterLink' })// 切换路由router.push('/completed')await localVue.nextTick()for (let i = 0; i < links.length; i++) {const link = links.at(i)if (link.vm.to === '/completed') {expect(link.classes()).toContain('selected')} else {expect(link.classes()).not.toContain('selected')}}// 切换路由router.push('/active')await localVue.nextTick()for (let i = 0; i < links.length; i++) {const link = links.at(i)if (link.vm.to === '/active') {expect(link.classes()).toContain('selected')} else {expect(link.classes()).not.toContain('selected')}}// 切换路由router.push('/')await localVue.nextTick()for (let i = 0; i < links.length; i++) {const link = links.at(i)if (link.vm.to === '/') {expect(link.classes()).toContain('selected')} else {expect(link.classes()).not.toContain('selected')}}})
})

快照测试

现在这个应用的业务功能基本已经开发完成了,之后希望在这些组件开发的比较稳定的时候(样式、结构不再需要做大量改动),可以给它们加上快照测试,可以保证在无意中修改了 UI 结构时及时的测试出来。

编写测试用例

// src\components\TodoApp\__tests__\TodoHeader.js
import { shallowMount } from '@vue/test-utils'
import TodoHeader from '@/components/TodoApp/TodoHeader'describe('TodoHeader.vue', () => {// 将渲染组件放到 beforeEachlet wrapper = nullbeforeEach(() => {wrapper = shallowMount(TodoHeader)})test('New todo', async () => {// 可以给元素添加一个专门用于测试的 `data-testid`,方便测试的时候获取这个元素const input = wrapper.findComponent('input[data-testid="new-todo"]')const text = 'play'...})test('snapshot', () => {expect(wrapper.html()).toMatchSnapshot()})
})

默认会在编写了快照测试的测试文件所在目录下创建一个 __snapshots__ 文件夹,存放快照文件,例如 src\components\TodoApp\__tests__\__snapshots__\TodoHeader.js.snap

下面给其他组件添加快照测试:

// src\components\TodoApp\__tests__\TodoApp.js
// src\components\TodoApp\__tests__\TodoFooter.js
// src\components\TodoApp\__tests__\TodoItem.js// 添加
test('snapshot', () => {expect(wrapper.html()).toMatchSnapshot()
})

更新快照

在监视模式下,如果快照测试失败,且修改是有意的,可以使用 u 命令更新所有快照文件。

也可以使用 i 命令,进入交互式快照模式,它会重新执行测试用例,每个测试失败后都会询问如何处理,然后再执行下一个:

  • u 更新当前测试的快照文件
  • s 跳过当前测试
  • q 退出交互式快照模式
  • Enter 重新运行当前测试
  • r 重新运行交互式快照模式

配置代码覆盖率统计

可能有的代码并没有测试,在运行的过程中可能会有 BUG,所以最好让测试覆盖率保持在 80% 以上。

建议新增一个脚本运行 Jest 测试,使用命令参数指定统计代码覆盖率,避免开发时实时统计消耗性能:

"scripts": {..."coverage": "vue-cli-service test:unit --coverage"
},

npm run coverage 运行脚本会在命令行打印统计结果

在这里插入图片描述

并在项目根目录生成 coverage 文件夹,存放覆盖率报告,可以打开 coverage\lcov-report\index.html 在页面上查看:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

可以看到 TodoHeader 组件中有一个 if 分支未测试,补充测试用例:

// src\components\TodoApp\__tests__\TodoHeader.js
test('New todo with empty text', async () => {const input = wrapper.findComponent('input[data-testid="new-todo"]')const text = ''await input.setValue(text)await input.trigger('keyup.enter')// 断言不会对外发布自定义事件expect(wrapper.emitted()['new-todo']).toBeFalsy()
})

npm run coverage 重新统计测试覆盖率,刷新报告页面重新查看 TodoHeader 的覆盖率。

上传测试覆盖率到 codecov

通常情况下不建议将测试覆盖率报告保存在项目仓库中,在 .gitignore 中添加忽略 coverage 目录:

# .gitignore
coverage

可以将报告上传到专业的报告分析平台,例如 Codecov。

打开 Codecov 官网,绑定 Github 账号登录之后,选择要展示测试覆盖率的仓库

注意:上传报告的项目 git 必须是选择的 git 仓库,否则上传命令虽然不会报错,但并不会上传到 codecov 平台显示。

在这里插入图片描述

拷贝 Codecov token(未上传过报告的仓库默认会显示入门指南,Step2 中有 token;上传过报告的仓库可以从 Settings 面板复制 token)

在这里插入图片描述

然后安装 Codecov:

npm i -D codecov
# 或者安装到全局
# npm i -g codecov

生成测试覆盖率报告:

# coverage 是运行 `jest -- coverrage` 的脚本 
npm run coverage

将测试覆盖率报告上传到 codecov:

# 运行项目安装的 codecov 上传报告
npx codecov --token=xxx
# 使用全局安装的 codecov
codecov --token=xxx

重新查看 Codecov 可以看到报告分析,同样包括每个组件的测试覆盖率。

在这里插入图片描述

在 Settings 面板复制 Badge 链接到 README.md 中可以展示 codecov 徽章,显示测试覆盖率,可以让其他开发者了解应用是否安全可靠。

在这里插入图片描述

效果如下

在这里插入图片描述

自动化测试和持续集成

本项目使用 Github Actions 实现持续集成。

配置 Github Actions

项目根目录新建目录和文件 .github/workflows/main.yml

# .github\workflows\main.yml
name: Publish And Deploy Demoon:# 当提交 main 分支的代码的时候触发 actionpush:branches:- main# 或对 main 分支进行 pull request 的时候pull_request:branches:- mainjobs:build-and-deploy:# 运行环境runs-on: ubuntu-lateststeps:# 下载仓库源码- name: Checkoutuses: actions/checkout@main# 安装依赖 & 运行测试并生成覆盖率报告 & 项目打包- name: Install and Buildrun: |npm installnpm run coveragenpm run build# 发布到 GitHub Pages- name: Deployuses: JamesIves/github-pages-deploy-action@4.1.0with:branch: gh-pages # The branch the action should deploy to.folder: dist # The folder the action should deploy.# 上传测试覆盖率报告到 codecov- name: codecov# 使用 codecov 官方提供的 actionuses: codecov/codecov-action@v1with:token: ${{ secrets.CODECOV_TOKEN }}

如果测试失败,自动构建就会中断,不会部署 Github Pages 和上传覆盖率报告。

Github 添加存储 codecov Token 的环境变量

在这里插入图片描述

修改打包路径

Github Pages 访问地址默认会带二级域名(仓库名),例如http://xxx.github.io/vue-testing-demo/,需要修改打包路径:

// vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,// 添加publicPath: '/vue-testing-demo'
})

提交代码

push 代码,触发 action

在这里插入图片描述

在这里插入图片描述

运行成功后,可以访问 Codecov 查看覆盖率报告。

指定托管 Github Pages 的分支

在这里插入图片描述

修改完成后,需要重新 push 触发 Github Action 自动构建,发布 Github Pages。

添加工作流程状态徽章

添加 Github Actions 状态徽章,向 README.md 添加链接:![example workflow](https://github.com/<OWNER>/<REPOSITORY>/actions/workflows/<WORKFLOW_FILE>/badge.svg)

  • <WORKFLOW_FILE>.github/workflows/ 目录下的 .yml 工作流程文件名。
  • <OWNER>github 组织名
  • <REPOSITORY>:仓库名

对应当前项目就是:![](https://github.com/<你的 github 用户名>/vue-testing-demo/actions/workflows/main.yml/badge.svg)

效果:

在这里插入图片描述

这篇关于Vue2 应用测试学习 03 - TDD 案例(其他测试用例、快照测试、配置代码覆盖率统计、codecov 上传覆盖率统计报告、Github Actions 自动化测试和持续集成)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1097916

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

Zookeeper安装和配置说明

一、Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。 ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境; ■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例; ■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble) Zookeeper通过复制来实现

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

CentOS7安装配置mysql5.7 tar免安装版

一、CentOS7.4系统自带mariadb # 查看系统自带的Mariadb[root@localhost~]# rpm -qa|grep mariadbmariadb-libs-5.5.44-2.el7.centos.x86_64# 卸载系统自带的Mariadb[root@localhost ~]# rpm -e --nodeps mariadb-libs-5.5.44-2.el7

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️