前端测试:e2e测试

为什么进行测试

你是否有以下烦恼:

  • 当你加班加点完成一个功能后,提交给测试部,立马返回几个bug

  • 当你修改完bug后,并检查了好几遍,确保无误后,提交给测试部,有返回几个bug

    ……

对于以上情境,你是否有过疑问,为什么检查都没问题了还是出现bug?以上这些都是因为没有做好测试。

你可能会问,做了呀,都检查好几遍了。的确,你是测试了,但是你并没有完成测试的闭环。你可能完成测试的一部分,其他的部分并没有完成。既然你说我没完成测试,那何为测试,又怎么进行测试?

什么是测试?

对于前端来说,测试主要是对HTML、CSS、JavaScript进行测试,以确保代码的正常运行。

常见的测试有单元测试、集成测试、端到端(e2e)的测试。

  • 单元测试:对程序中最小可测试单元进行测试。我们可以类比对汽车的测试,在汽车组装之前需要对零件进行测试,这种情况下就和单元测试一致。只有零件正常才会进行下一步的组装
  • 集成测试:对多个可执行单元组成的整体进行测试。类比于汽车的测试,就相当于测试发动机之前,需要把发动机所需的零件组装在一起对组装后的发动机这个整体进行测试。
  • 端到端的测试(e2e):从服务端到客户端的测试。这种测试是对服务端和客户端组成的整个系统进行测试,它是能够执行完整流程的测试。

既然知道了有这些测试种类,接下来就说说这些测试应当如何实现。

如何进行测试

测试的方式可以分为人工测试、自动测试。

人工测试:就是让测试部的人员根据业务流程进行操作当某一步或几步出现问题就说明这部分代码有问题。这种测试方式有很明显的不足:

  1. 它只能测试测试人员看得见的部分,对于测试人员看不见的部分不能测试。比如一些内部的工具函数、逻辑代码等,这些很可能存在问题。

自动测试:利用写好的代码对代码进行测试。这种测试能够弥补人工测试的不足,它的颗粒度是代码级别的,能够准确地识别某个方法的错误

由此,在实际的开发过程中我们可以采用人工测试+自动测试的方式进行测试,力求100%的覆盖测试目标。人工测试暂且不谈,我们先谈谈自动测试的方式实现单元测试、集成测试、e2e测试。本篇博客先讲e2e测试。

e2e测试

实现e2e测试的库和框架有很多,这篇文章以Cypress为例进行讲解。

cypress简介

Cypress是基于JavaScript语言的前端自动化测试工具,无需借助外部工具,自集成了一套完整的端到端测试方法,可以对浏览器中运行的所有内容进行快速、简单、可靠的测试,并且可以进行接口测试

cypress的特点

  • 时间穿梭:Cypress会在测试运行时拍摄快照。只需将鼠标悬停在命令日志上,即可清楚了解每一步都发生了什么
  • 可调试性:无需揣测测试失败原因。直接使用浏览器的DevTools进行调试。清晰的错误原因和堆栈跟踪让调试能够更加快速便捷
  • 实时重载:每次对测试代码进行更改,Cypress都会实时执行新的命令,自动重新加载页面进行测试
  • 自动等待:无需在测试中添加等待。在执行下一条命令或断言前Cypress会自动等待元素加载完成,异步操作不再是问题
  • 间谍,存根和时钟:Cypress允许验证并控制函数行为,Mock服务器响应或更改系统时间,更便于进行单元测试
  • 网络流量控制:Cypress可以Mock服务器返回结果,无须连接后端服务器即可实现轻松控制,模拟网络请求
  • 运行结果一致性:Cypress架构不使用Selenium或Webdriver,在运行速度、可靠性、测试结果一致性上均有良好的保障
  • 截图和视频:Cypress在测试运行失败时自动截图,在使用命令运行时录制整个测试套件的视频,轻松掌握测试运行情况

cypress安装

npm install cypress -D

安装完毕后,执行 npx cypress open 会打开一个窗口根据自己项目使用的框架和打包器进行选择,选择完毕后会自动生成配置文件及其相关文件,然后选择对应的测试类型,这里选择e2e测试。选择完毕后就可以运行测试套件。

cypress自定义mount命令

对于vue项目来说,一般都会使用vue-router和vuex两个插件,因此在测试的时候需要把这两个插件挂载到vue实例上。

挂载router

import {
    
    mount} from 'cypress/vue'

import router from '../../src/router';
Cypress.commands.add('mount',(component, options={
     
     })=>{
    
    
    options.global.plugins = options.global.plugins || [];
options.global.component = options.global.component || {
    
    };
    if(!options.router){
    
    
        options.router = router;
    }
    options.global.plugins.add({
    
    
        install(app){
    
    
           	app.use(options.router) ;
        }
    })
    return mount(component,options);
})

挂载vuex

import {
    
    mount} from 'cypress/vue'
import store from '../../src/store';
Cypress.commands.add('mount',(component,options={
     
     })=>{
    
    
    options.global.plugins = options.global.plugins ||[];
    options.global.component = options.global.component || {
    
    };
    const {
    
    
        store = store,
        ...mountOption,
    } = options;
    options.global.plugins.push({
    
    
        install(app){
    
    
            app.use(store);
        }
    });
    return mount(component,mountOption);
})

挂载全局组件

import {
    
    mount} from 'cypress/vue'
import {
    
    BaseButton} from '../../src/components/BaseButton.vue'
Cypress.commands.add('mount',(component,options)=>{
    
    
	options.global.component =  options.global.component || {
    
    };
    options.global.component.BaseButton = BaseButton;
    return mount(component, options);
});

配置文件(cypress.config.js)

下面列举一些常用的配置项,具体配置说明访问配置说明

const {
    
    defineConfig} = require('cypress');
module.exports = defineConfig({
    
    
  // e2e测试
  e2e:{
    
    
    
  },
  // 组件测试
  component:{
    
    
    
  },
  // 取消测试时录制视频
  video:false,
  // 取消测试失败时截屏
  screenshotOnRunFailure:false
});

语法

cypress框架的测试文件以.spec.js为后缀,测试时的目录可以通过cypress.config.js进行配置

mount()方法用于挂载组件

import HelloWorld from './HelloWorld.vue';
describe('description',()=>{
    
    
  it('description',()=>{
    
    
		cy.mount(HelloWorld);
  });
});

cy.contains()方法用于检测页面中的元素的内容是否与指定的内容相同

it('测试 contains',()=>{
    
    
    cy.contains('hello world');
});

浏览器导航操作

cy.visit(url):访问url,只能访问返回html文件的url

cy.go():

cy.back():

cy.reload(boolean):true:不需要缓存,false:需要缓存

cy.url():获取网页的url

cy.title():获取网页的title

元素定位

运行测试代码之后点击playground按钮用鼠标点击元素然后复制界面上方输入框的内容就可以得到一行代码。

上下级关系

within():在指定的元素中查找元素

cy.get('form').within((form)=>{
    
    
    // 找form的第一个input
	cy.get('input:first').type('username');
})

context():

cy.context()

children(selector):查找子元素

parent():获取元素的父元素

sibling():查找同级元素

prev():找到前一个元素

let pwd_el = cy.get('.username').sibiling('input');
pwd_el.prev('input');

next():找到下一个元素

let pwd_el = cy.get('.username').sibiling('input');
pwd_el.prev('input');

元素的输入操作

focus():输入框聚焦事件

cy.get('.password').focus().type('123');

blur():输入框失焦事件

cy.get('.username').blur();

submit():只有form表单才能调用

cy.get('.form').submit();

元素的点击操作

click():单击元素

dbclick():双击元素

rightclick():右击元素

单选和多选

check():选中单选框或多选框

cy.get('.radio').check();
cy.get('.checkbox').check();
// 强制选择,在禁用状态下依然可用
cy.get('.radio').check(true);
// 强制取消选择
cy.get('.radio').cehck(false);
// 选中其中的一个
cy.get('.checkbox').check('checkboxvalue');
cy.get('.radio').check('radiovalue');
// 选中其中的多个
cy.get('.checkbox').check('checkboxvalue1','checkboxvalue2');

下拉框

select():选中下拉框的指定项

// 通过选项的value值选中选项
cy.get('.select').select('selectvalue');
// 通过选项的文本值选中选项
cy.get('.select').select('selectText');
// 选中多个
cy.get('.multipleSelect').select(['selectText1','selectText2'])

窗口滚动

scrollIntoView():把元素滚动到可视范围内

scrollTo():滚动窗口到指定位置

cy.get('.input').scrollIntoView();
// 滚动到顶端
cy.scrollTo('top');
// 滚动到最底部
cy.scrollTo('bottom');
// 滚动到指定位置
cy.scrollTo(100,200)

测试样式

如果你的项目中的html文件中有引入其他样式,那么在cypress/support/component-index.htmlz中也需要引入相同的样式。

如果你在项目中的main.js中引入其他样式,那么你在cypress/support/component.js中也需要引入相同样式。

例如,我的项目中使用view-ui-plus第三方组件库,引入了它的样式,那么在cypress/support/component.js中就需要引入该样式。
我的项目中的main.js文件
在这里插入图片描述
cypress文件夹下的component.js文件
在这里插入图片描述

测试事件

在测试文件中,为组件提供一个props,props中注册事件名和事件响应函数

import Input from '../components/Input.vue'

describe('测试 input',()=>{
    
    
   it('测试 change',()=>{
    
    
       const changeHandler =  (v)=>{
    
    
           console.log(v);
       } 
       cy.mount(Input,{
    
    
           props:{
    
    
               onChange:changeHandler
           }
       });
       cy.get('.input').type('hello world');
       cy.get('@onChange').should('have.been.calledWidth',1);
   });
});

Cypress的原理

  • 大多数测试工具(如:Selenium/webdriver)通过在外部浏览器运行并在网络上执行远程命令来运行
  • 因为 Webdriver 底层通信协议基于 JSON Wire Protocol,运行需要网络通信
  • Cypress 和 Webdriver 方式完全相反,它与应用程序在相同的生命周期里执行

Cypress 运行测试的大致流程

  1. 运行测试后,Cypress 使用 webpack 将测试代码中的所有模块 bundle 到一个 js 文件中
  2. 然后,运行浏览器,并且将测试代码注入到一个空白页中,然后它将在浏览器中运行测试代码【**可以理解成:**Cypress 将测试代码放到一个 iframe 中运行】
  3. 每次测试首次加载 Cypress 时,内部 Cypress Web 应用程序先把自己托管在本地的一个随机端口上**【如:http://localhost:65874】**
  4. 在识别出测试中发出的第一个 命令后,Cypress 会更改本地 URL 以匹配你远程应用程序的 Origin**【满足同源策略】,这使得你的测试代码和应用程序可以在同一个 Run Loop 中运行**

猜你喜欢

转载自blog.csdn.net/qq_40850839/article/details/131013117