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

秒会selenium grid

今天在看 selenium grid 文档的时候,发现 selenium grid4 的设计还是不错的,想顺手体验一下,于是就发现了docker-selenium项目,可以快速的设置好 selenium grid 环境,非常简单方便。

然而后面准备用 python 去写个简单例子的时候,发现很难找到 python 代码的例子,好不容易找到 1 个却发现跑不起来,于是简单的看了下源码,找到了正确的打开方式,这里简单分享一下。

selenium grid 的使用场景

在我看来 grid 的使用场景有两个

  • 在不同的浏览器上并行跑用例,这比挨个在不同浏览器上跑要省不少时间
  • 启动多个节点在同一个浏览器上并行跑用例,同样也是节约了执行时间

快速安装好 selenium grid 环境

Selenium grid 有多种模式,比如 Standalone, Hub and Node,对于初次体验来说无脑用 Standalone 是不会有问题的。

传统的方式是使用 java 来运行 jar 包安装,不过 docker selenium 提供了更简单的方式,直接用 docker 跑镜像就好了。 比如下面的命令就启动了 1 个 firefox 的远程节点。

docker run -d -p 4444:4444 -p 7900:7900 --shm-size="2g" selenium/standalone-firefox:4.11.0-20230801

这里暴露了 2 个端口

  • 4444: hub 的端口,直接访问可以看到所有节点的信息
  • 7900: vnc 的端口,访问 http://localhost:7900/?autoconnect=1&resize=scale&password=secret 就可以看到远程节点上的浏览器运行情况,非常方便了

连接远程节点进行测试

代码很简单,我的运行环境是

如何实现线程安全的内存缓存

这两天正好看到一个用 go 实现的线程安全的内存缓存,实现代码非常简洁高效,不卖弄不烧脑,非常值得初学者拿来学习。

项目地址

项目地址在https://github.com/muesli/cache2go,目前已经有 1.8k 的 star。

如何使用

package main

import (
	"github.com/muesli/cache2go"
	"fmt"
	"time"
)

// Keys & values in cache2go can be of arbitrary types, e.g. a struct.
type myStruct struct {
	text     string
	moreData []byte
}

func main() {
	// Accessing a new cache table for the first time will create it.
	cache :=

	// We will put a new item in the cache. It will expire after
	// not being accessed via Value(key) for more than 5 seconds.
	val := myStruct{"This is a test!", []byte{}}
	cache.Add("someKey", 5*time.Second, &val)

	// Let's retrieve the item from the cache.
	res, err := cache.Value("someKey")
	if err == nil {
		fmt.Println("Found value in cache:", res.Data().(*myStruct).text)
	} else {
		fmt.Println("Error retrieving value from cache:", err)
	}

	// Wait for the item to expire in cache.
	time.Sleep(6 * time.Second)
	res, err = cache.Value("someKey")
	if err != nil {
		fmt.Println("Item is not cached (anymore).")
	}

	// Add another item that never expires.
	cache.Add("someKey", 0, &val)

	// cache2go supports a few handy callbacks and loading mechanisms.
	cache.SetAboutToDeleteItemCallback(func(e *cache2go.CacheItem) {
		fmt.Println("Deleting:", e.Key(), e.Data().(*myStruct).text, e.CreatedOn())
	})

	// Remove the item from the cache.
	cache.Delete("someKey")

	// And wipe the entire cache table.
	cache.Flush()
}

简单看一下核心 api

Slenium已死?

selenium 已死?其他的框架例如 playwright, cypress 当立?这是去年一个广泛讨论的话题。对于我来说这个观点很明显是偏颇的,因为

  • selenium 本身已经成为了 w3c 规范的一部分,现在市面上所有的浏览器都遵循这个规范
  • selenium 本来就不是一个纯测试工具,它是为自动化而生,除了浏览器测试之外,selenium 还有很广泛的用途,而 playwright/cypress 则更专精于测试领域
  • selenium 的 api 相当稳定,对于一些需要长期维护的项目来说这是非常有诱惑力的,而我去年用 playwright 做了一个项目,今年由于 api 升级,去年的代码基本上已经完全不可用了

所以对我来说,selenium 尽管已经徐娘半老,吸引力大不如前,但在某些场景下,selenium 仍然会是我的首选工具,就像是 vb/php 一样,尽管大家都已经看衰很多年了,但这些技术一直没有落幕退场。

selenium 官方可能也察觉到了这些广泛的讨论,昨天他们官网 blog 发了一篇文章,直接讨论 selenium 与其他工具的情感纠葛,上下文可能是不少人发博文比较 selenium 与其他工具,然后标题党一下,使得大家产生错觉:selenium 真的已经快死翘翘了。这篇内容专业简洁,适合给大家消除误解,原文地址:https://www.selenium.dev/blog/2024/selenium-vs-blog-posts/。

下面是全文翻译。

这篇博文讨论了那些比较 Selenium、Cypress 和 Playwright 的标题党文章。这些文章没有意义,也没有帮助。

作者:David Burns (@AutomatedTester) | 2024 年 1 月 9 日星期二

在博文中,关于自动化测试的标题党文章最容易的方式就是将 Selenium 与其他工具进行比较,并配以一个吸引人的标题,尤其是当它贬低现有工具时。

不幸的是,这可能会使人们对这些产品中的哪些功能可用产生困惑,尤其是当我们进行同类的过度类比时。

Selenium 一直是一个很好的浏览器自动化工具。对于该项目来说,幸运的是,它已经成为测试 Web 应用程序的首选工具近 20 年。该项目专注于构建越来越复杂的浏览器自动化的难点。项目的重点一直是稳定的 API 和可扩展性,以保证 Selenium 的运行。它没有关注人们如何进行测试,因为有非常好的测试框架可用,并且为 5 种不同的编程语言进行测试是一项非常重要的工程工作。

然而,这些博文中经常出现一些误解。

与 Playwright 和 Cypress 相比,设置浏览器和驱动程序太困难

过去确实如此,因为您需要下载驱动程序。对于 GeckoDriver 和 SafariDriver 来说,这并不太糟糕,因为它们可以优雅地处理浏览器升级。另一方面,对于基于 Chromium 的浏览器,您需要为每个新版本更新驱动程序。

AI大语言模型在自动化用例生成中的探索

最近读到这篇文章,原文在是https://drlee.io/implementing-ai-in-software-testing-creating-a-text-generation-model-for-test-automation-7294b26f93c4,里面涉及到一些基于ai进行自动化测试的探索,原文是这么说的:

将人工智能 (AI) 纳入软件测试可谓是游戏规则的改变者,能够显著提升效率和有效性。本文利用 OpenAI 的文本生成模型——尤其是 GPT-3.5-turbo 和 GPT-4-turbo-preview——在 Google Colab 中构建了一个文本生成模型,重点关注测试自动化用例。

看了一下,里面列举了 3 个测试用例。

The system shall allow users to securely login with a username and password.

这是用户登录的用例。

Ensure that the shopping cart allows users to add items, remove items, and proceed to checkout.

这是购物车的用例。

The weather API should return a JSON response with fields for temperature, humidity, and precipitation forecast for the next 5 days.

单元测试的最佳实践

看到一篇关于单元测试最佳实践的文章,简单翻译一下,很多都说到了点子上,不能赞同更多。

单元测试是对软件应用程序中各个单元或组件进行的软件测试。单元测试旨在验证每个软件单元的执行是否符合设计预期。单元测试可以确保代码质量,提高可维护性,方便重构,并提高开发速度。

当谈到最佳实践时,这里有一些应该遵循的:

  1. 为每个缺陷编写新测试:当你遇到一个缺陷时,编写一个暴露该缺陷的测试。这也称为回归测试。

  2. 保持测试的小而聚焦:一个单元测试应该限制在一个独立的函数或方法中。这使得当测试失败时更容易识别和修复问题。

  3. 隔离你的测试:确保每个测试都是相互独立的。这允许你单独运行每个测试,并以任意顺序运行。(划重点了)

  4. 按测试类型组织测试:你可以根据它们测试的对象类型或测试类型来组织测试。这使得查找和运行相关测试更容易。

  5. 每次测试一条代码路径:每个测试应该验证方法中的一条明确的代码路径。这使得理解被测试的内容以及测试可能失败的原因更容易。

  6. 避免在测试中加入逻辑:当你在测试中加入逻辑时,你有引入测试缺陷的风险。保持测试的简单。(重点)

  7. 避免在被测试的类中使用静态方法:静态方法不能在子类中重写,这使得它们难以测试。避免在你要测试的类中使用静态方法。

  8. 避免测试实现细节:你的测试应该关注代码的行为,而不是它的实现。如果测试实现细节,当你的代码行为保持不变时,测试仍可能中断。

  9. 首先为对应用影响最大的方法编写测试:将测试工作集中在对应用影响最大的方法上。这通常包括包含复杂逻辑或与外部资源交互的方法。

  10. 使用 AAA 模式:准备测试数据和测试环境(Arrange)、执行(Act)、断言(Assert)是编写单元测试的典型模式。单元测试方法的安排部分初始化对象和传递给被测试方法的数据值。执行部分调用带有 Arrange 参数的被测试方法。断言部分验证被测试方法的行为符合预期。(划重点)

原文如下:

𝗨𝗻𝗶𝘁 𝗧𝗲𝘀𝘁𝗶𝗻𝗴 𝗕𝗲𝘀𝘁 𝗣𝗿𝗮𝗰𝘁𝗶𝗰𝗲𝘀

Unit tests are software testing where individual units or components of a software application are tested. Unit testing aims to validate that each software unit performs as designed. Unit tests ensure code quality, and ease of maintenance, facilitates refactoring, and increase development speed.

When we talk about best practices, here is a list of that one should follow:

自己编写命令行工具来提高测试效率

测试同学在很多时候都是在沟通问题,复现问题和定位问题。

记得多年前刚入行的时候,我一直对复现问题不够重视,觉得自己是测试人员,主营业务应该是测试,复现问题应该是解决问题的人做的事情。后来尽管岗位分工越来越细,但是测试同学做的事情并没有越来越聚焦,反而整体的工作范围更加的宽泛,其实说白了就是专业性上有提升,但是专注度上的改善其实并不明显。

复现问题其实很多时候是测试人员的痛点,比如我们在测试过程中发现了一个问题,并发给了开发同学,然而,你可能会有下面的经历

  • 开发人员表示在自己的环境上是好的呀,这个不讨论,各种梗太多了
  • 开发人员表示看不懂 bug 上的描述,这点可以用截图和录屏来解决
  • 开发人员表示你帮我复现一下吧,这时候就比较痛苦了,因为复现本质上就是重复

测试的时候一再的重复自己是痛苦且低效的,如果问题是用户上报的,那么除去重复之外,还要加上阅读理解的过程,阅读用户的操作过程,理解他们做了些什么,以及猜测系统或者产品的表现。稍微归纳一下,发现复现基本上都是在做下面的一些事情

  1. 创建数据,可以是从 app 上,也可以是从网页上,甚至可以在 admin 后台去创建
  2. 收集或查询一些周边数据,比如数据库或者是被测的系统和产品上
  3. 结合数据和实际表现去判断产品或者系统的表现是不是正常

所以除了第 3 步之外,前 2 步都是非常适合用自动化脚本的方式去实现的。我就用 python 写了很多的辅助脚本去帮助我重现问题,下面是我的一些经验

用直接调用 api 的方式去创建/查询数据

因为现在大部分产品的管理端都是前后端分离的,所以管理端是可能有纯后台的 api 的;以及移动端 app 都是有 api 的,调用 api 创建各种测试数据其实是可以实现的,只要做好鉴权工作,调 api 去创建和查询的实现成本和维护成本都是可控的。

之前我是用直连数据库的方案去做查询和创建,不过后面逐渐废弃了这种做法,这是因为

  • 数据库表结构可能会发生变化,从而导致脚本执行失败,从而降低了脚本执行的稳定性以及提高了维护成本
  • 脚本需要去关注分库分表的实现,增加了实现难度,降低了长期的可维护性
  • 因为安全审计的关系,数据库的连接和密码会周期性的发生变化,增加了维护成本
  • 我们生产环境的数据库只有生产环境的机器才能访问,本地办公网没有办法访问,所以做线上问题复现的时候,直接连数据库就会显得有心无力了
  • 直接写数据库可能会绕过后端的数据校验,可能会写入脏数据

所以在做自动化工具的时候一般不推荐直接连数据库进行数据的生成和查询

用爬虫的方式去查询周边数据

很多时候我们需要获取一些周边团队的数据,有时候我们可以通过 api 调用来获取,但也会有获取不到的情况;另外有些祖传的系统可能前后端不分离,后端并没有提供 api 接口,这种情况下我一般用爬虫来做数据的收集。

爬虫的方式有很多好处,但对于一些系统来说可能存在开发难度高,运行效率低的情况,所以如果有更好的方式可以方便的拿到数据的话,爬虫的优先级可以低一点。

用缓存去提高执行效率

收集数据,特别是收集多个页面或者是多个系统的数据往往是比较慢的,这时候可以用 redis 等缓存数据库来保存一下结果,这样可以加速代码的执行。这里不推荐用数据去做,因为收集数据是一项相对来说比较高频且随意的工作,数据结构一般不会特别稳定,如果表结构频繁变更的话,那么开发和维护的成本都非常高。我一般是把数据直接序列化成 json 字符串,然后丢到 redis 的 sting 对象里,简单方便。

另外我的测试数据创建过程一半也是用爬虫加 api 的方式,缓存下来的数据可以帮助我进行更加复杂的测试数据创建工作。

编写 cli 工具,让测试过程更加工程化

有一段时间我积累了相当多的 python 脚本,在实际工作中调用脚本就成了一门学问,一些脚本要这样调,另一些可能要在其他脚本调用后才能调用,记忆的成本很高,文档化的价值又基本没有,后来我发现使用 cli 工具去整合脚本调用,写好每个命令的 help message,这样文档和调用编排都有了。