前端自动化测试框架cypress

524 阅读6分钟

自动化测试框架cypress

为了保障软件质量,并减少重复性的测试工作,自动化测试已经被广泛运用。

自动化测试是一种测试方法,是指使用特定的软件,去控制测试流程,并比较实际结果与预期结果之间的差异。通过将测试自动化,可以把人对软件的测试行为转化为由机器自动执行测试的行为,从而替代大量的手工测试操作,使得测试可以快速,反复的进行。

关于自动化测试,有一个测试金字塔模型,该模型把测试从下到上分为了单元测试、集成测试和UI自动化测试(E2E测试/UI界面测试)。越往金字塔底层,测试成本越低,效率也越高,而越往金字塔的顶层,测试成本会逐渐增高,收益也会越低。 img

  • UI自动化测试(端到端测试)

UI测试的主要目的是,从软件使用者的角度来检验软件的质量,而UI自动化测试则是以自动化的方式来代替人工执行测试。在测试金字塔模型中,UI层测试是各种测试中投入最大、收益最低、运行最慢的一种。

  • 接口自动化测试(集成测试)

接口自动化主要包括模块接口测试,子功能模块集成起来的功能模块测试等,目的是为了验证在单元测试的基础上,所有模块集成起来的子系统、子功能是否仍然满足质量目标。

  • 单元测试

单元测试又称为模块测试,主要针对程序中最小可测试单元(一般指方法,类)的测试,具备投入小、收益产出高的特征,可以较早期地发现代码缺陷,适用于公共函数库的测试。

总之,越往金字塔底层,测试成本越低,效率也越高,而越往金字塔的顶层,测试成本会逐渐增高,收益也会越低。

Cypress简介

  • Cypress是为现代网络打造的,基于JavaScript的下一代前端测试工具。他可以对浏览器中运行的任何内容进行快速,简单和可靠的测试。
  • Cypress是自集成的,它提供了一套完整的端到端测试体验。无须借助其他外部工具,在简单安装后即可允许用户快速的创建、编写、运行、测试用例,并且针对每一步操作均支持回看。
  • 不同于其他只能测试UI层的前端测试工具,Cypress允许你编写所有类型的测试,覆盖了测试金字塔模型涉及的所有测试类型:端到端测试、集成测试、单元测试。

img

  • web在进化,测试也一样 img

Cypress优点

img

  • 阅读性高,易于理解 img

  • 界面美观友好。

img

  • 测试的每一步都有对应的截图,在运行测试的时候,cypress会获取快照,记录了测试执行过程的每一步细节。

img

  • 全程都会有录屏。
  • 支持使用web浏览器上的开发工具直接调试,有丰富错误和堆栈跟踪信息,支持debug调试,随时暂停。
  • 自动等待ui更新,减少异步代码,在页面某些元素还没出来的时候,通常我们会添加等待的代码。但是在cypress中,是自动等待的,直到 元素出现,或者超过了你设置的超时时间。
  • 环境安装:快速安装。没有服务器,驱动程序,或任何其他依赖需要安装或配置。 img

测试流程

  • 环境安装:快速安装。没有服务器,驱动程序,或任何其他依赖需要安装或配置,短短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打开的页面:

image-20220416130951861

最佳实践

  1. 合理管理测试用例尽量不要依赖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)
    })
})
  1. 不要使用易改动的选择器来选择元素,推荐使用专门用来做UI测试的具有统一前缀的选择器,比如data-cy="login-btn" ,与业务逻辑解耦。

    // 反模式
    cy.get('button').click()
    ​
    // 最佳实践
    cy.get('[data-cy=submit]').click()
    ​
    
  2. Contains 用与获取dom,或者断言是否包含某内容。使用法则是如果元素的内容发生更改,您是否希望测试失败?如果是则可使用,否则使用其他方式

    // 反模式
    cy.contains('submit').click()
    
  3. 不要试图去保存、修改选择器的返回值,命令都是异步执行的,会自动等待。

    // 反模式
    // 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()
    ​
    
  4. 只测试在自己控制范围之内的事情,尽量避免访问第三方服务等

    <!-- <a href="xxx" target='_blank'>打开网页</a>
    // 反模式
    cy.visit('xxx')
    
    // 最佳实践
    cy.get(a).should('have.attr', 'href').and('eq', 'xxx')
    
  5. 进行测试依赖于先前测试的状态

    如何解决这个问题:

    • 将先前测试中重复的代码移至beforebeforeEach挂钩。
    • 将多个测试组合成一个更大的测试。
    // 反模式
    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

选择器 img

页面元素基本操作方式

img

常见操作 img