自动化测试框架cypress
为了保障软件质量,并减少重复性的测试工作,自动化测试已经被广泛运用。
自动化测试是一种测试方法,是指使用特定的软件,去控制测试流程,并比较实际结果与预期结果之间的差异。通过将测试自动化,可以把人对软件的测试行为转化为由机器自动执行测试的行为,从而替代大量的手工测试操作,使得测试可以快速,反复的进行。
关于自动化测试,有一个测试金字塔模型,该模型把测试从下到上分为了单元测试、集成测试和UI自动化测试(E2E测试/UI界面测试)。越往金字塔底层,测试成本越低,效率也越高,而越往金字塔的顶层,测试成本会逐渐增高,收益也会越低。
- UI自动化测试(端到端测试)
UI测试的主要目的是,从软件使用者的角度来检验软件的质量,而UI自动化测试则是以自动化的方式来代替人工执行测试。在测试金字塔模型中,UI层测试是各种测试中投入最大、收益最低、运行最慢的一种。
- 接口自动化测试(集成测试)
接口自动化主要包括模块接口测试,子功能模块集成起来的功能模块测试等,目的是为了验证在单元测试的基础上,所有模块集成起来的子系统、子功能是否仍然满足质量目标。
- 单元测试
单元测试又称为模块测试,主要针对程序中最小可测试单元(一般指方法,类)的测试,具备投入小、收益产出高的特征,可以较早期地发现代码缺陷,适用于公共函数库的测试。
总之,越往金字塔底层,测试成本越低,效率也越高,而越往金字塔的顶层,测试成本会逐渐增高,收益也会越低。
Cypress简介
- Cypress是为现代网络打造的,基于JavaScript的下一代前端测试工具。他可以对浏览器中运行的任何内容进行快速,简单和可靠的测试。
- Cypress是自集成的,它提供了一套完整的端到端测试体验。无须借助其他外部工具,在简单安装后即可允许用户快速的创建、编写、运行、测试用例,并且针对每一步操作均支持回看。
- 不同于其他只能测试UI层的前端测试工具,Cypress允许你编写所有类型的测试,覆盖了测试金字塔模型涉及的所有测试类型:端到端测试、集成测试、单元测试。
- web在进化,测试也一样
Cypress优点
-
阅读性高,易于理解
-
界面美观友好。
- 测试的每一步都有对应的截图,在运行测试的时候,cypress会获取快照,记录了测试执行过程的每一步细节。
- 全程都会有录屏。
- 支持使用web浏览器上的开发工具直接调试,有丰富错误和堆栈跟踪信息,支持debug调试,随时暂停。
- 自动等待ui更新,减少异步代码,在页面某些元素还没出来的时候,通常我们会添加等待的代码。但是在cypress中,是自动等待的,直到 元素出现,或者超过了你设置的超时时间。
- 环境安装:快速安装。没有服务器,驱动程序,或任何其他依赖需要安装或配置。
测试流程
- 环境安装:快速安装。没有服务器,驱动程序,或任何其他依赖需要安装或配置,短短60s内就可以搞定。
npm install cypress --save-dev
cypress //cypress目录
---- fixtures //测试数据配置文件,可以使用fixture方法读取
---- integration //测试脚本文件
---- plugin //插件支持
---- support //支持文件
-cypress.json //cypress全局配置文件
- 编写测试:测试脚本可阅读性高,易于理解。
describe('登陆/注册', () => {
beforeEach(() => {
cy.visit('/home')
})
it('登陆', function () {
cy.contains('登录/注册').click()
cy.get('.slipbox__content--show input[name=username]')
.type(Cypress.env('username'))
.should('have.value', Cypress.env('username'))
cy.get('.slipbox__content--show input[name=password]')
.type(Cypress.env('password'))
.should('have.value', Cypress.env('password'))
cy.get('button[type=submit]').click()
cy.get('.login-info').contains(Cypress.env('username'))
})
})
配置package.json
"scripts": {
"e2e": "npx cypress open --config baseUrl=http://10.1.241.35:8086"
},
配置cypress.json
{
"baseUrl": "http://localhost:3000",
"env": {
"workId": "ab1a7c02-999f-4c8f-ae91-8879b8b1ce0a",
"workName": "cypressTestWork",
"workType": "全景",
"workCateGory": "空间设计",
"workDesc": "cypressTestDescription",
"username": "cypress",
"password": "123456",
}
}
运行测试
npm run e2e
你应该可以看到cypress打开的页面:
最佳实践
- 合理管理测试用例尽量不要依赖UI去做登录,命令式去做登录
// 反模式, 利用UI登陆
it('登陆', function () {
cy.get('[data-cy=login-register-btn]').click()
cy.get('[data-cy=login-username]').type(Cypress.env('username'))
cy.get('[data-cy=login-password]').type(Cypress.env('password'))
cy.get('[data-cy=login-submit-btn]').click()
cy.get('.login-info').contains(Cypress.env('username'))
})
// 最佳实践
Cypress.Commands.add('login', (params) => {
cy.request('POST', 'http://10.1.241.35:8086/gateway/backend/api/v1/public/login', params)
.then((resp) => {
expect(resp).property('body').property('data').property('token')
window.localStorage.setItem('3d_ppt_token', resp.body.data.token)
})
})
-
不要使用易改动的选择器来选择元素,推荐使用专门用来做UI测试的具有统一前缀的选择器,比如data-cy="login-btn" ,与业务逻辑解耦。
// 反模式 cy.get('button').click() // 最佳实践 cy.get('[data-cy=submit]').click() -
Contains 用与获取dom,或者断言是否包含某内容。使用法则是如果元素的内容发生更改,您是否希望测试失败?如果是则可使用,否则使用其他方式
// 反模式 cy.contains('submit').click() -
不要试图去保存、修改选择器的返回值,命令都是异步执行的,会自动等待。
// 反模式 // DONT DO THIS. IT DOES NOT WORK // THE WAY YOU THINK IT DOES. const a = cy.get('a') // nope, fails a.first().click() // 最佳实践 cy.get('a').as('link') cy.get('@link').first().click() -
只测试在自己控制范围之内的事情,尽量避免访问第三方服务等
<!-- <a href="xxx" target='_blank'>打开网页</a> // 反模式 cy.visit('xxx') // 最佳实践 cy.get(a).should('have.attr', 'href').and('eq', 'xxx') -
进行测试依赖于先前测试的状态
如何解决这个问题:
- 将先前测试中重复的代码移至
before或beforeEach挂钩。 - 将多个测试组合成一个更大的测试。
// 反模式 describe('my form', () => { it('visits the form', () => { cy.visit('/users/new') }) it('requires first name', () => { cy.get('#first').type('Johnny') }) it('requires last name', () => { cy.get('#last').type('Appleseed') }) it('can submit a valid form', () => { cy.get('form').submit() }) }) // 最佳实践 // 合并成一个测试 describe('my form', () => { it('can submit a valid form', () => { cy.visit('/users/new') cy.log('filling out first name') // if you really need this cy.get('#first').type('Johnny') cy.log('filling out last name') // if you really need this cy.get('#last').type('Appleseed') cy.log('submitting form') // if you really need this cy.get('form').submit() }) }) // 测试前运行共享代码 describe('my form', () => { beforeEach(() => { cy.visit('/users/new') cy.get('#first').type('Johnny') cy.get('#last').type('Appleseed') }) it('displays form validation', () => { cy.get('#first').clear() // clear out first name cy.get('form').submit() cy.get('#errors').should('contain', 'First name is required') }) it('can submit a valid form', () => { cy.get('form').submit() }) }) - 将先前测试中重复的代码移至
常用api
选择器
页面元素基本操作方式
常见操作