前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你不知道的Cypress系列(3) -- 是时候重构自己的思维了!

你不知道的Cypress系列(3) -- 是时候重构自己的思维了!

作者头像
iTesting
发布2021-01-04 14:35:14
2.1K0
发布2021-01-04 14:35:14
举报
文章被收录于专栏:iTestingiTesting

在跟同学们的交流中,我也了解到, 原来除了国外优秀的公司(例如Adobe, 迪士尼,AutoDesk等等), 国内也有很多公司在尝试使用Cypress提升测试效率。

在Cypress中国群内、在公众号iTesting里,我每天都能看到大量关于Cypress的使用讨论和私下问询。这让我感到无比荣幸。(买了书的同学们,公众号回复你的微信号,拉你到Cypress中国群)。

除了日常推荐大家通过阅读我的书来解决日常Cypress使用问题外,我也一直在更新着我这边的Cypress知识图谱, 不夸张的说,目前我总结和实践下来知识点多达200多篇。

本着“雕琢自我,普惠他人”的原则,我决定在公众号iTesting上开设<你不知道的Cypress系列>专栏。此专栏目的是分享一些我自己趟过的坑,走过的弯路、以及在选型时抛弃了的实践。希望让大家在选用Cypress作为前端自动化测试框架方案时, 可以借鉴一下,避免再走我走过的弯路。

今天是<你不知道的Cypress系列>的第三篇 -- 是时候重构自己的思维了!

由于Selenium/WebDriver的“荼毒”, 当前在自动化过程中,很多不合理的操作,反而都变成了标准流程。

例如,要进行元素属性值比较,我们首先想到的就是先赋值,再比较。

再比如,自动化过程中,常常会陷入”条件测试(Conditional Testing)“的陷阱! 例如,我见过太多这样的case:”如果我点击了某button,如果弹出框没有出现,我执行A操作,如果出现,我执行B操作“。

由于这种“荼毒”,初次使用Cypress时, 大多数同学都会认为自己掉进坑里了!

下面我们来一个个分析:

(一)诡异的赋值

01

赋值不起作用

赋值操作是最常见的了,赋值最常用的场景是获取元素的某个属性供以后使用。

没接触过JavaScript的同学,在第一次写Cypress脚本时,一定会遇见如下问题:

代码语言:javascript
复制
describe('欢迎关注iTesting', function () {

    it('你以为的赋值', function () {
        // 定义变量那么,用来获取button的text是什么
        let name
        cy.visit('/login')
        //关注公众号iTesting, 玩转Cypress
        cy.get('button[type="submit"]').then(obj => {
            name = obj.text()
            // 第一次打印
            cy.log('循环内有值', name)
        })
        // 第二次打印
        cy.log('循环外没有值', name)
    })
})

如上述代码所示,我定义了一个变量name,并尝试把submit button的text即“Submit” 赋值给name。 上面的代码看起来没有任何毛病,但是运行时,你会发现我第一次打印时有值, 但是二次打印时name的值是null。

02

赋值不起作用的原因

写惯了Python或Java的同学往往会卡在这里觉得莫名其妙。其实也就是同步执行和异步执行的差异了。

同步执行

可以简单理解为,当你执行一个操作,在这个操作没有结果之前,其后续的操作不会执行。

异步执行:

可以简单理解为,当你执行一个操作后,其后续的操作可以立即执行, 当这个操作有结果后,再通过状态,通知或者回调来通知这个操作的调用方。

这个解释相当之不准确,不过也足够我们继续下面的内容了。 你如果感兴趣, 可以搜索同步、异步、阻塞、非阻塞来了解更多进程通信和系统调用的知识。

正常情况下,Python代码,Java代码就是同步执行的,JavaScript代码就是异步执行的。

了解了这一点,你就明白了,当执行到第13行时,name的值还没有被返回,所以打印不出来。

03

99%的情况都无须赋值

使用Selenium/WebDriver比较熟悉的同学,初次转到Cypress后,很容易就自无劝退:”Cypress好难用, 我还是用回Selenium/WebDriver“吧。

拿对元素属性值进行断言为例,大家很容易就沿用Selenium/WebDriver时代的旧思维,认为,必须先拿出元素的属性值赋给一个变量,然后在用这个变量跟给定的期望结果对比。实际上,根本无需如此!

在Cypress中,99%的操作都无须赋值!

下面分别举例:

Selenium/WebDriver

代码语言:javascript
复制
//获取元素的属性值,并比较
value = driver.find_element_by_id('kw').get_attribute('innerHTML')
assert value == "iTesting"

Cypress:

代码语言:javascript
复制
//获取元素的属性值,并比较
cy.get('#kw').should('have.text', 'iTesting')

就这么简单!

(二)Cypress命令是如何运行的?

01

先来看一个大家常常会犯的错误:

假设我们定义了一个自定义方法login,最后返回登录后的凭证:

代码语言:javascript
复制
// cypress/support/index.ts
Cypress.Commands.add('login', (username, password) => {
  //各种代码实现登录
  // 后返回登录凭证
  return auth
})

然后在测试用例里,经常看到这样的使用方式:

代码语言:javascript
复制
const auth = cy.login("iTesting", "iTesting")

最后会发现auth的值是undefined,很多人百撕不得骑姐。

为什么?

这是因为Cypress命令在它们被调用时不会执行任何操作。它们会自我排队(“enqueue themselves”),最后在统一运行。

来看下官方的解释:

代码语言:javascript
复制
it('iTesting邀你看Cypres如何运行', () => {
  cy.visit('/my/resource/path') // 啥也不干

  // 关注iTesting,玩转Cypress
  cy.get('.awesome-selector')   // 还是啥也不干
    .click()                    // 必须啥也不干

  cy.url()                      // 别想了,就是不干
    .should('include', '/hello') // 就在此刻,干!
})

// 好了,运行完了, 哦, 你真是一个快枪手。
// 事实上,所有的Cypress命令会被queue起来,直到所有命令被chain完毕。
// 然后Cypress开始按它们被queue的顺序开始运行。

这个就是Cypress的魔力。这就是为什么JavaScript是异步执行的,但是Cypress命令却能按照你的代码“顺序“执行的原因!

02

那么,知道了Cypress命令是如何运行的,再来看上面的登录例子,你就知道了,

代码语言:javascript
复制
const auth = cy.login("iTesting", "iTesting")

cy.login没有被执行,当然auth里就没有值了。

那么,如何才能确保cy.login被执行呢?

为了让你能够访问到Cypress命令执行的结果,Cypress提供了

代码语言:javascript
复制
.then()

.then是闭包的一个典型应用。(如果想深入理解,可以查下Promise)

所以,这下,我们知道要访问cy.login的返回值,我们应该这样用:

代码语言:javascript
复制
cy.login("iTesting", "iTesting").then( auth => {
const secret = auth
//剩余用到auth的代码。
})

这下,你就能愉快的使用Cypress命令的返回值了,不过也带来一个问题,就是代码层次比较深。。。

(三)拒绝条件测试

01

前面我提到了条件测试(Conditional Testing),实际上,条件测试常见常景如下:

代码语言:javascript
复制
1. 我想在元素存在或者不存在时,执行不同的操作。
2. 我的应用程序有A/B Testing,我需要测试到不同的分支。

为了实现这个功能,在Selenium/WebDriver编程中,我们大量使用if...else,我们以为我们Cover住这种情况了,结果我们就发现我们的测试会薛定谔成功:有时候执行能成功,有时候执行不成功, 在你不执行的时候你永远不知道到底执行能不能成功。

我们来看一个例子:

假设你的应用程序代码如下:

代码语言:javascript
复制
// 你的应用程序代码

// 定义一个随机时间
const random = Math.random() * 100

// 创建一个 <button> 元素
const btn = document.createElement('button')

//关注iTesting,玩转Cypress
// attach 这个元素到body上
document.body.appendChild(btn)

setTimeout(() => {
  // 在random的时间内,给btn一个类属性。
  btn.setAttribute('class', 'active')
}, random)

你的测试代码就会如下:

代码语言:javascript
复制
it('薛定谔的测试', () => {
 // 多执行几遍,你发现,有时候btn的属性是active,有时候不是active。
  cy.get('button').then(($btn) => {
    if ($btn.hasClass('active')) {
      //  active时的代码
    } else {
      // 非active的代码
    }
  })
})

这也是Selenium/WebDriver被诟病的原因之一,不稳定!

02

为了避免这个情况,Cypress告诉你, 不要去做条件测试(Conditional Testing)!

Cypress说,既然你在测试,那么你就应该知道你的每一步下去,其结果是什么。如果你不能确定你的操作下去结果是什么,那么你就不是在测试!(没毛病吧)

相应的,你就要调整你的测试策略,尽量避免让自己的代码处于条件测试(Conditional Testing)下, 具体来说就一句话:

代码语言:javascript
复制
事先做一些操作,确保你的某个操作一定只有一个结果!

如何做到呢?别忘记,Cypress是运行在浏览器之内的,是跟你的应用程序运行在同一个生命周期的,你对你的应用程序有完全的控制权!

听起来很好,不过很可惜。这句话主要是对开发说的,对我们QA来说,用处不大(因为我们QA还是不知道改哪行代码啊!)。

不过,这里还是有一些原则, 比如:

代码语言:javascript
复制
1. A/B Testing, 可以根据AB的策略,构造出一定会走A逻辑的测试数据。
2. 判断元素在不在,一定可以根据业务知道你的什么操作,它一定会在。

总结

当你初次使用Cypress时,特别是当你是从Selenium/WebDrvier转到Cypress来时,你一定会感觉到不习惯。这是必然的。

当你遇见问题时,不妨尝试转换下思维,把老的思维模式抛弃掉,转入到Cypress的思维中来,毕竟,我们做测试是为了:

测试你的代码,而不是你的耐心!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iTesting 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
灰盒安全测试
腾讯知识图谱(Tencent Knowledge Graph,TKG)是一个集成图数据库、图计算引擎和图可视化分析的一站式平台。支持抽取和融合异构数据,支持千亿级节点关系的存储和计算,支持规则匹配、机器学习、图嵌入等图数据挖掘算法,拥有丰富的图数据渲染和展现的可视化方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档