专注测试技术的课程订阅站点

在2023使用playwright进行自动化测试

playwright 一直是我最看好的新一代自动化测试框架,2022 年底 playwright 在 npm 上的下载量超过了 100 万,尽管不如 selenium 和 cypress,不过势头还是相当强劲的。最近正好发现一篇文章简单的介绍了使用 typescript,pageobject 和 fixture 配合 playwright 进行用例编写的文章,这里把里面的精华拿出来分享一下。

老生常谈,playwright 的优势

  • 有个好爹,微软出品,看好长期更新维护和迭代,但也可能突然被砍掉,毕竟大公司都在裁员
  • 运行速度快
  • 自动等待元素出现
  • 报告的呈现很多元化,可以设置重试机制,捕获执行日志,截屏录屏等
  • 支持多个浏览器并行执行
  • 提供自动生成代码能力以及 Inspector GUI
  • 一套代码,跨浏览器执行的能力

目录结构

框架整体的目录结构如下。

.
├── config
   ├── global-setup.ts
   └── playwright.config.ts
├── package-lock.json
├── package.json
└── src
    ├── data
       └── data.json
    ├── fixtures
       ├── AxeFixture.ts
       └── TodoFixture.ts
    ├── pages
       └── TodoPage.ts
    └── tests
        ├── a11y.spec.ts
        └── demo-pom-todo-app.spec.ts

config 目录

  • playwright.config.ts playwright 的配置文件
  • **global-setup.ts** 在所有用例执行前运行一次,主要的目的是登录一次被测系统并保存浏览器的全局状态到 storageState.json 文件中。这样就不需要每个用例都去单独登录一次了。更多信息可以参考文档。https://playwright.dev/docs/test-advanced#global-setup-and-teardown

Page Object

po 基本上是自建框架的必选项了。具体的实现如下

强行为新项目写接口测试是一种什么样的体验

继续上次的话题,为新项目写 ui 自动化测试是一件非常有挑战的事情,写接口测试会不会容易一点呢?这次我就尝试了一下。

现阶段我们的管理端接口其实不多,就 8 个左右,所以从工作量上评估其实还可以。

测试策略

讲策略之前我们先看一下项目的简单业务属性。该项目的管理后台其实就是稍微复杂一点的增删改查。增加一条记录,编辑记录,各种组合条件查询记录,删除功能暂时没有,后面可能会跟进。

我的测试策略也很简单,首先搭建 1 个单独的测试环境,防止跟其他测试形式冲突,然后把最大的精力放在数据的准备和清理上。

  • 准备数据:为了测试查询,比较好的方式是每次都先清空数据库,然后动态创建一些固定的数据,我指的固定是比如固定 100 条,每条的排序规则,各个字段都是确定的。
  • 数据清理:完成测试后清空数据库,比如搜索测试完成之后清空数据库去测试创建和编辑,这样创建的时候就没有存量数据造成的断言干扰,就可以实现每次创建之前数据都是 0 条,创建成功之后变成 1,这样断言就相对容易,而且用例能够以随机的顺序运行,减少了依赖;

代码实现

这里最有挑战的是数据准备和清理的代码实现,我需要准备下面的数据

  • 通过接口创建一些数据到 db,之所以用接口创建是因为一些联动的操作直接写 db 的话无法触发。我们的接口是 http 的,用 python+requests 就可以了,稍微麻烦的点是构造的数据需要通过接口的有效性校验,比如时间区间之类的,因为我们的服务会跑在多个不同的国家和地区,timezone 也是需要关注的
  • 把创建成功的数据 id 以及一些需要用到的字段存到 redis 里。我习惯于用 redis 的 set,因为天生去重,并且可以使用srandmember来随机返回几条数据,在做查询校验的时候非常方便
  • 查询一些关联表的信息或者需要用到的数据,保存在 redis 里。这里我用的是 redis 的 string 类型,需要保存的信息直接序列化成 json 字符串,非常的方便;
  • 直接写 sql 做数据库的清理,直接调用 es 的 resetful api 做索引的清理。我们的查询使用的是 es,所以清理数据的时候除了用 sql 去 delete 之外,es 的 index 也是要清理的
  • 使用配置文件来进行多环境多地区的配置,比如 mysql 每个地区的配置都不一样,同样的地区不同的业务数据也在不同的库里,把配置弄起来还是很有必要的

最终实现的效果是运行TEST_ENV=test REGION=cn python data_builder.py这条命令之后就可以在对应的环境创建初始化数据了。代码比较啰嗦我就不粘贴了。

用例编写

用例编写无非是增删改查。

假如有一天我躺平了

最近躺平几乎成了内卷的反义词,我了解了一下躺平最初的由来,发现是源自百度贴吧,帖子的主人公过着消费极低而且自在悠闲的生活。没有钱了就去横店躺平扮演尸体,赚了一点点钱就不再工作,反正他的消费非常少,躺几天尸够他活个一阵子了。

这种生活方式突然受到了追捧,当然,很多人只是说说而已,真让他们去躺平难度还是非常大的,毕竟上有老下有小,现实总是不能允许你做任性的事情。这可能表现了喝彩者对内卷化下意识的反对和厌恶,也算是反内卷方程式的一个潜在的解法。

我不知道内卷是不是经济发展变缓这个特殊缓冲区的产物。因为之前经济高速增长,人们已经有了努力就能获得一切的惯性,当经济稍微停滞一些的时候,努力的惯性也许就成了内卷的核反应堆。等经济增速回归到一个较低水平的时候,也许各种花式躺平就成了主流,比如现在的台湾和日本,到时候可能一部分人卷上加卷,大部分人默默躺平。这也没什么不好,之前旅行的时候经过一些小城市,穿梭在人流中竟然感受到了许久不见烟火气,心里也曾经有过这样一个念头,假如有一天我躺平了,我会做些什么?

如何生存

做为一个测试人,我总是信奉一条原则,在项目越早期想的越多越清楚,后面的坑可能就会相对少一些。所以在躺平之前,我应该会重点思考一个问题,那就是生存还是毁灭的终极问题。

躺平了就意味着不能在职场打工干饭了,因为如果打工的话,你的身体告诉你要躺平,但你的老板却花式要求你要更加努力,修更多福报。

不能打工如何活下去呢?记得《穷爸爸富爸爸》里就有类似的讨论,不打工可能凭借自己的资产活下去,比如投资的公司的收入,隐形资产比如知识产权的收入。不过这些离我比较遥远,我觉得比较有可能的收入来源是

  • 稳健理财的收入。长期定期投入的话,可能会有年化 5%左右的收入吧;
  • 激进型理财的收入。比如股票和以股票为主的基金收入;我有个朋友曾经跟我分享过一个例子,他有个同事很多年前买了几百万的大头股,现在不仅股票涨了,而且每年都有十几万的分红,过的相当滋润,当然了,这是极其典型的幸存者偏差,大家听完一笑而过就好。另外我也见过有一对台湾夫妻分享他们财务独立的案例,他们定投了十几年的基金,后来财务独立环游世界,这个例子我找到了好几个佐证和类似的例子,所以应该有一定的可行性。好的年份激进型理财的年化收益可能会有 10%左右;
  • 零散收入。这部分收入可遇不可求,比如帮别人开发一些自动化工具或者自动化脚本之类的,随缘就好;
  • 潜在收入。学一门手艺,比如木工电工之类的,接一些零活;

总的来说收入主要靠投资和理财,其他的只能随缘,不能糊口。

下面来算支出。

  • 房租水电。在一线城市躺平成本比较高,而且周围的人在疯狂搞钱,受他们的影响容易焦虑,所以可以考虑搬去二线城市;一些优质教育资源集中的二线城市的头部学校可能比一线城市的普通学校综合实力更强,也能缓解一些鸡娃焦虑。那么二线城市的房租是多少呢?我随便搜了一下,好点的位置大概 5000 左右,如果稍微降低一下标准,算上水电等,一年 6 万应该是可以搞定的;
  • 吃饭。在深圳自己买菜的话,一天 100 块应该够 3 口之家吃的很好了;现在物价基本上大部分城市都差不多,所以在吃上面的支出一二线城市 100 块一天是差不多了,加上偶尔出去吃一顿,那么一年 4 万应该是可以搞定的;
  • 出行。去二线城市应该可以卖掉现在的车,因为一台车一年的成本差不多要 1 万块,然后换一个小电驴,周边的出行应该就没大问题了;出远门可以地铁或者打车,想开车自驾的话可以租车,租车很便宜,一天 100 多一点可以搞定。所以综合算来一年 1 万左右吧;
  • 穿衣。我自己的衣服很简单,随便找个店买点基本款就可以了,如果住在南方几件 t 恤就能过一年,所以一年穿衣千把块就可以了。算上全家的一年大概 1 万左右可以搞定;
  • 其他支出。比如换手机或者买点乱七八糟东西之类的,一年大概 2 万吧;

这样算下来,总支出应该是 6+4+1+1+2=14 万,再加上一些无法预计的支出,再放宽 2 万,一年的生活成本应该是 16 万。

如果有 400 万左右的存款,放 100 万去做高风险投资,年化 10%,300 万做稳健理财,大概 5%,这样一年的收入应该能有 25 万,粗茶淡饭一年生活下来,还有 9 万的盈余可以投入到下一年的激进理财里,因此除了跑不赢通胀的焦虑之外,拿 400 现金应该就可以躺平了吧。记得前不久有则新闻,一对北京土著夫妻卖了北京的房产,一家三口着 1000 万去昆明买房定居,其实算下投资收益,他们哪怕找不到工作一家三口平平淡淡过一生也应该足够了吧。

可以做些什么

不工作的话可以做的事情就很多了。

  • 旅行。可以自驾也可以公共交通,丰俭由人,预算够的话就稍微放飞一些,预算吃紧就经济一些,行万里路,给生活带来不同的节奏;
  • 读书。可以泡图书馆和书店,书非借不能读也;
  • 带娃。我前几天听说一个数据,中国家庭在义务制教育阶段对孩子的教育投资大概占家庭总收入的 12%左右,这还只是平均数,在一线鸡娃焦虑的城市这个数字只会更高,我估计可能有 20%以上,所以如果孩子不上各种补习班,自己带孩子出去旅行或者教点什么的话,每年节省的费用是可观的;不过自己教娃很难,我觉得这个比上班累;
  • 学一些实用的技能。比如木工电工自己理发之类的生存能力,做到万事不求人,这样长期看来也能节约不少支出;
  • 学一些不实用的技能。比如英语钢琴之类的,这样可以教娃,省培训班的钱;
  • 打游戏。我觉得我可以在家打一个月的足球经理或者三国志,足不出户;
  • 锻炼身体。这只是说说,哪怕有时间了坚持锻炼身体还是挺难的,不过好的身体可以省药钱,还是要锻炼的;

这样算下来,有 400 万存款就可以在一个二线城市非常平静的生活下去了,有这样经济能力的人不在少数,起码我身边就有不少。不过大家还是坚持在一线城市卷珠帘,低绮户,照无眠,我猜大概率还是有激情有理想,另外伴随着深深的通货膨胀以及教育焦虑吧。

做一个没有感情的用例生成机器

最近我们准备把一些服务从一个二次开发的 k8s 平台迁移到另一个二次开发的 k8s 平台去,这时候我们遇到了一个比较棘手的问题:因为迁移是有损的,也就是说迁移的过程中伴随着一定量的代码修改和容器编排方式的适配,那么我们如何保证服务迁移以后的功能是正常可用的呢?

想了一些方法,这些办法其实比较常见:

  • 增加监控指标和告警指标,迁移后如果服务有问题那么这些指标会让我们尽可能快的发现问题;
  • 增加自动化的测试用例,通过用例尽可能的对现有服务功能进行覆盖;

那么问题就来了,如何用最低的成本去实现这些自动化用例呢?

首先我们应该选择合适类型的自动化用例,目前我们可以实现的用例有

  • 单元测试用例,实现成本高,但是运行成本低,运行速度快,服务没有启动的情况下也能跑,如果有的选,那么单元测试用例是首选;
  • 接口级的测试用例,实现成本相对较低,运行成本高,需要服务跑起来才能进行测试,运行速度相对单元测试是慢一些的;
  • ui 自动化测试用例,实现成本高,运行成本也高,运行速度也慢;

最终我们选择增加一些核心的单元测试用例和 ui 自动化用例,然后尽可能多写接口测试用例。

我们的服务实现了一些微服务化,请求从最前面接入层进来之后会到 http 层,该层会调用更下层的 rpc 微服务层实现具体的业务逻辑,因此我们的接口就有两种,分别是

  • http 接口,客户端直接调用
  • rpc 接口,http 层以及微服务之间进行调用

因为业务逻辑往往需要多次的 rpc 调用才能实现,所以直接写 http 层的接口测试用例相对来说是一种比较经济的方式,因为一次的 http 接口调用会产生多次的 rpc 调用,从业务逻辑上和服务的触达性上来说都是令人满意的。

那么怎么去用尽可能最低的成本去实现这些 http 接口的用例呢?我之前有过使用 postman 把自己变成一个没有感情的用例生成机器的经历,在这里可以给大家分享一下。

举一个具体的例子,看这个接口。

POST /api/users

请求
{
  "user":{
    "username": "Jacob",
    "email": "jake@jake.jake",
    "password": "jakejake"
  }
}

响应
{
  "user": {
    "email": "jake@jake.jake",
    "token": "jwt.token.here",
    "username": "jake",
    "bio": "I work at statefarm",
    "image": null
  }
}

这是一个用户注册的 POST 接口,需要向后端传 json 类型的数据,我们可以看成是传递一个 user 对象,这个对象有 username, email 和 password 属性,用 json 字符串的形式表达出来而已。

用wiremock自建mock服务

我们很多时候都需要 mock 服务,比如

  • 在做性能测试的时候,我们希望调用第三方服务的接口可以被 mock 掉,这样就不会因为压测而对第三方依赖造成巨大的负载
  • 第三方服务归属于其他团队,不同环境之间的沟通和协调其实比较麻烦
  • 消除不同测试环境之间的差异
  • 控制请求的时延
  • 如果是使用云服务的话,使用 mock 接口还可以节约出口的带宽
  • 提升第三方服务的稳定性

所以 mock 的作用就是用最小的代价实现跟第三方接口几乎一样的假的接口,最好返回的内容是动态可配置的,这样我们在测试环境就可以直接去调用这个假接口,从而消除了对第三方的依赖。

实现 mock 的方式很多,最简单的做法就是随机应变,自行实现,比如在 python 技术栈里,我们可以用 flask 去这样实现

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/get-json')
def hello():
    return jsonify(hello='world') # Returns HTTP Response with {"hello": "world"}

用 go 实现起来也不难,下面的例子来自 go 的官方文档,使用了 gin 框架。https://go.dev/doc/tutorial/web-service-gin

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

// album represents data about a record album.
type album struct {
    ID     string  `json:"id"`
    Title  string  `json:"title"`
    Artist string  `json:"artist"`
    Price  float64 `json:"price"`
}

// albums slice to seed record album data.
var albums = []album{
    {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
    {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
    {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

func main() {
    router := gin.Default()
    router.GET("/albums", getAlbums)
    router.GET("/albums/:id", getAlbumByID)
    router.POST("/albums", postAlbums)

    router.Run("localhost:8080")
}

// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, albums)
}

// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
    var newAlbum album

    // Call BindJSON to bind the received JSON to
    // newAlbum.
    if err := c.BindJSON(&newAlbum); err != nil {
        return
    }

    // Add the new album to the slice.
    albums = append(albums, newAlbum)
    c.IndentedJSON(http.StatusCreated, newAlbum)
}

// getAlbumByID locates the album whose ID value matches the id
// parameter sent by the client, then returns that album as a response.
func getAlbumByID(c *gin.Context) {
    id := c.Param("id")

    // Loop through the list of albums, looking for
    // an album whose ID value matches the parameter.
    for _, a := range albums {
        if a.ID == id {
            c.IndentedJSON(http.StatusOK, a)
            return
        }
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}

从上面的代码可以看出来,如果不需要实现创建接口的话,代码量还能少三分之一左右,总的来看其实难度也不算大。

可持续软件测试:为更绿色和更高效的未来制定 5 项策略

最近看到一篇文章是关于软件测试与节能减排的讨论的,可持续发展这个观点在西方国家目前经常被拿出来探讨,因为这与气候变化等的人类世问题关系非常密切。把这篇可持续软件测试:为更绿色和更高效的未来制定 5 项策略 简单翻译了一下,里面还是有很多中肯的建议的,一些看法其实与效能提升关系密切,毕竟效率提高了,消耗和浪费可能会有所减少。

背景

在技术进步与进步同义的时代,软件开发的生态足迹往往被忽视。

  • ShiftProject 的一项研究估计,2018 年信息和通信技术(ICT)行业的碳足迹约占全球温室气体排放的 2-3%。
  • 彭博社预测,到 2030 年,IT 系统将占全球能源需求的惊人 20%。从这个角度来看,交通运输业目前占全球能源消耗的 25%。
  • 根据国际能源署(IEA)的数据,2018 年数据中心在全球消耗了约 200 太瓦时(TWh)的电力。

绿色软件开发的出现宛如灯塔,提供了一种转向生态友好实践的范式转变,与全球对可持续发展的迫切呼吁一致。这种方法不仅仅是一个流行语,它呼吁在软件生命周期中致力于资源效率、减少能源消耗和全面考虑环境。

通过优化软件测试流程中的资源利用和能源需求,组织可以降低整体测试成本。对于大型测试项目来说,这可以节省大量的资金。此外,测试过程变得更加高效和流线化,从而实现更快的交付周期和更短的上市时间。

目标很简单:优化测试策略和自动化实践,以节省资源,从而节省能源。

以下是 5 项策略,可立即在您的组织中启动“绿色 QA”。

1. 使所有测试执行“上下文相关”

  • 仅执行与代码更改相关的测试。避免盲目执行;不要在每个环境上运行每个测试。避免大批量的测试执行。这将减少资源消耗并最大限度地减少能源使用。
  • 避免像晚上、一天多次等计划执行。这些执行会消耗太多资源,如果自上次执行以来部署的代码没有变化,则可能不会提供任何价值。
  • 将测试隔离,确保每个测试套件或测试用例在不影响其他测试套件或测试用例的情况下独立运行。这可以防止资源争用并提高并行执行的整体效率。减少测试间依赖,以便更容易地在不等待其他测试完成的情况下并发执行测试。
  • 利用云平台进行测试执行,因为它们可以根据需要动态分配资源。这可以为能源效率做出重大贡献。
  • 重新设计 CI 和 CD pipeline,以在各种“测试”阶段结合上述原则。

可以通过转向 Trunk-based 开发和使用注释在代码中标记测试并将其作为参数传递给 CI/CD pipeline 来实现。

2. 实施节能的测试自动化 (编码) 实践

测试执行能耗 = 活动代码能耗 + 等待代码能耗 + 空闲代码能耗

  • 将测试自动化框架和脚本容器化。容器提供了一个轻量级且孤立的环境,使其易于水平扩展。它们还减少了每次代码下载和构建过程生成的网络流量。
  • 最小化文件 I/O 操作,尤其是在循环或频繁文件访问场景中。使用缓冲 I/O 操作来减少磁盘访问次数。例如:从文件中读取测试数据或从文件中读取配置或将结果推送到测试管理工具。
  • 学习编写优化的代码,包括死代码消除、循环优化、公共子表达式消除等。此外,分析不同算法的性能特征并为您的特定用例选择最合适的算法。
  • 保持测试工具和库是最新的,以受益于任何性能改进。
  • 在代码中及时释放资源,尤其是在资源受限的环境中,如移动设备或物联网设备测试。
  • 最小化后台进程、任务和线程,这些进程、任务和线程可能会提高 CPU 和内存利用率,从而增加功耗。
  • 调整日志记录和报告的详细程度,以避免不必要的资源消耗。

可以通过在自动化指南文档中记录所有上述内容、与质量工程师沟通并在代码审查过程中将它们作为检查领域来实现。