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

在软件开发和测试中为失败而设计

原文地址: https://medium.com/@peterdtitan/designing-for-failure-in-software-development-and-testing-9f3f2d0bbd7b

软件开发是一个复杂的过程,涉及到许多不同的技术、工具、方法和人员。在这个过程中,失败是不可避免的,甚至是必要的,因为它可以帮助我们发现错误、改进质量和提高效率。然而,并不是所有的失败都是有益的,有些失败可能会导致严重的后果,如数据丢失、系统崩溃、客户不满或法律责任。因此,我们需要在软件开发和测试中为失败而设计,即预见可能发生的失败情况,并采取相应的措施来防止、检测、恢复和学习。

为失败而设计的原则

为失败而设计并不意味着我们要故意制造失败,而是要在开发过程中考虑到失败的可能性,并采用一些原则来应对它们。以下是一些常见的为失败而设计的原则:

  • 容错:容错是指让系统能够在出现错误时继续正常运行,而不是崩溃或停止工作。容错可以通过多种方式实现,如使用异常处理、重试机制、备份系统、冗余组件等。
  • 隔离:隔离是指将系统分割成不同的模块或服务,使得一个模块或服务的失败不会影响其他模块或服务的正常运行。隔离可以通过多种方式实现,如使用微服务架构、消息队列、断路器模式等。
  • 降级:降级是指当系统无法提供完整的功能或性能时,能够提供一些基本的或替代的功能或性能,以满足用户的最低需求。降级可以通过多种方式实现,如使用缓存、静态页面、功能开关等。
  • 监控:监控是指收集和分析系统的运行状态、性能指标、错误日志等信息,以便及时发现和解决问题。监控可以通过多种方式实现,如使用日志系统、度量系统、报警系统等。
  • 反馈:反馈是指向用户或开发者提供有关系统状态或问题的信息,以便他们能够做出适当的响应或改进。反馈可以通过多种方式实现,如使用用户界面、通知系统、报告系统等。

为失败而设计的好处

为失败而设计可以带来许多好处,如:

  • 提高系统的可用性和可靠性:通过容错、隔离和降级等手段,可以使系统在出现错误时仍能保持运行,从而减少停机时间和损失。
  • 提高系统的可扩展性和可维护性:通过隔离和监控等手段,可以使系统更容易分布式部署和管理,从而适应不同的负载和环境。
  • 提高用户和开发者的满意度和信任度:通过监控和反馈等手段,可以使用户和开发者更清楚地了解系统的状态和问题,并及时得到解决方案或建议。

为失败而设计的挑战

为失败而设计也有一些挑战,比如增加系统的复杂度和成本:为了实现容错、隔离、降级等功能,需要增加额外的代码、配置、测试和部署

五款最值得日常使用的命令行应用

命令行应用很多时候可以提升我们的工作效率,这里给大家推荐 4 款常见好用的命令行应用,希望对大家有所帮助。

vim

大名鼎鼎的命令行编辑器,有时间的同学都可以尝试一下。

说起来比较惭愧,当初学习 vim 的原因有两个。第一个是十多年前的室友表示 vim 这种工具的学习成本底,因为学会以后键位几十年不变,学一次用终生,性价比极高,尽管入门的时候学习曲线非常的陡峭,甚至有点反直觉。第二个理由是学习 vim 可以让我比较方便的在线上环境改代码,是的,你没看错,很多年前我们用 php 的时候确实做过线上调试和改代码的极限操作。

如今随着运维的规范以及自动化发布的普及,线上改代码这种高危操作应该是被严令禁止了。不过使用 vim 仍然可以让你在任意机器 ssh 进远程服务器进行代码的编写和执行。配合上自定义的配置和第三方的插件,vim 也是日常代码编辑的一个不错选择。

另外很多编辑器都支持 vim 键位,比如 atom,vscode,这会让你在写代码的时候更有如鱼得水的感觉。

tmux

tmux 之前是运维同学的钟意之物,因为该工具可以

  • 分屏,将 1 个 terminal 分成多个部分
  • 独立运行 session,每个部分都是独立的会话,互不干涉
  • 快照,任意时刻退出 terminal,tmux 都会保存当前会话,下一次可以无缝恢复

命令行的典型使用方式是,打开一个终端窗口(terminal window,以下简称"窗口"),在里面输入命令。用户与计算机的这种临时的交互,称为一次"会话"(session) 。 会话的一个重要特点是,窗口与其中启动的进程是连在一起的。打开窗口,会话开始;关闭窗口,会话结束,会话内部的进程也会随之终止,不管有没有运行完。 一个典型的例子就是,SSH 登录远程计算机,打开一个远程窗口执行命令。这时,网络突然断线,再次登录的时候,是找不回上一次执行的命令的。因为上一次 SSH 会话已经终止了,里面的进程也随之消失了。 为了解决这个问题,会话与窗口可以"解绑":窗口关闭时,会话并不终止,而是继续运行,等到以后需要的时候,再让会话"绑定"其他窗口。

%E4%BA%94%E6%AC%BE%E6%9C%80%E5%80%BC%E5%BE%97%E6%97%A5%E5%B8%B8%E4%BD%BF%E7%94%A8%E7%9A%84%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%BA%94%E7%94%A8%20ec7a76e3ce004c3fbae198fe1c07aa7c/Untitled.png

Tmux 就是会话与窗口的"解绑"工具,将它们彻底分离。 (1)它允许在单个窗口中,同时访问多个会话。这对于同时运行多个命令行程序很有用。 (2) 它可以让新窗口"接入"已经存在的会话。 (3)它允许每个会话有多个连接窗口,因此可以多人实时共享会话。 (4)它还支持窗口任意的垂直和水平拆分。

tmux 对于我来说的典型用法就是

  • 把一个窗口分成几块,小的窗口运行 mysql,redis 之类的服务
  • 用一个窗口运行主服务,比如 python 的 flask 开发服务器
  • 用最大的窗口来运行 vim 编辑器,做代码的编辑

这样任意时刻我退出 ssh,上面的这些服务都会一直运行,下次我再 ssh 上去的话就可以无缝的继续工作了。

gemini在自动化测试中的潜力

昨天 google 发布了全新的 ai 模型 gemini,在视频里 google 演示了 gemini 近乎神奇的实时反馈能力,比如测试人员画一只鸭子,ai 可以立即识别,以及后面的一些互动小游戏中,gemini 展示出了出人意料的精准度和推理能力,这是令我印象最为深刻的多模态模型的实时演示了。

珠玉在前,gpt4 的文本类型的推理能力已经到达了一个相对可用的状态,基于该能力,我们也看到了一些比较有意思的自动化类型的项目,比如通过简化 html 输入大语言模型,让大语言模型去推断用户想要进行的操作,最终调用浏览器 api 进行自动化操作的项目,以及利用 llm 的解释器能力,一步一步推理并进行浏览器操作的项目,这些项目都非常有创意,也有一定的实用性,但是缺乏完善的多模态支持总让人觉得想象空间可能没有变得非常广阔。gemini 的出现,强大的推理能力,代码生成能力以及多模态能力的结合,也许会为自动化测试领域带来翻天覆地的变化。

UI 自动化

这是难度最高的自动化测试类型,比如浏览器和 app 的自动化,尽管概念出现的非常早,而且也发展了相当长的一段时间,但是 ui 自动化的普及度却相对来说不算太高,在资源和时间有限的情况下,ui 自动化测试往往是被大家优先降本增效的不二选择。

类型 gemini 的大语言模型的出现,可能会给 ui 自动化带来革命性的变化。今后,我们可以直接向 ai 输入一些描述相对明确的测试用例,当然,是用自然语言编写的,ai 可以推断出我们的测试意图,还原测试步骤,最终将测试步骤转化成测试代码,自动运行,通过运行中提供的截图和代码报错信息自动分析结果,自动迭代和修正代码错误,直到输出最终代码,基本上 ui 自动化是可以完美闭环的。

也就是说也许不久之后我们只需要写好手工测试用例就可以完成相对完美的自动化测试工作了,以后的测试人员可能不再需要每个用例都去执行一遍,执行的工作应该可以被 ai 和机器替代绝大部分。

顺便再考虑一下测试用例,如果 ai 的推断能力足够强的话,ai 应该可以写出大部分的测试用例,这时候测试人员只需要补充一些极端场景的用例就可以了。

那测试用例是从哪里来呢?可能是从产品文档里分析出来的,同样,如果模型精通多模态和推断的话,那么产品文档和产品设计图也是可以用 ai 去生成一大部分的,人工只要负责查缺补漏和提出修改意见就好了。同理,代码的话 ai 也能写个大部分,也许今后我们的工作方式会发生巨大的转变,从单纯的跟人合作变成跟人和 ai 一起协作,通用知识和能力将变得廉价,垂类的信息和领域知识可能会越来越值钱。

接口自动化

接口自动化可能不太需要多模态,基于模型的推理和生成能力,如果模型支持海量的上下文输入的话,那么我们是完全可以为接口自动化的用例定义规则,并教会模型如何应用这些规则生成测试用例。

举个例子,如果我们 curd 的接口遵循 restful 规则的话,ai 是完全有能力生成 restful 接口的测试用例的,毕竟 restful 的规则比较清晰,而且对于具体资源来说接口和用例的数量都是可以枚举的。

因此对于一些相对简单的接口,用 ai 去编写用例从直觉上来说是可行的,不过对于复杂接口,比如一些接口有很繁杂的前置依赖和操作步骤,ai 自动根据接口文档的话可能不太好去推断各种具体场景,这种情况下我们可能需要一种接近自然语言的简化版中间语言,这种语言描述一些接口的输入输出以及构造复杂的接口测试场景,ai 根据这种 DSL 去生成代码的话可能效率和准确性都要更高一点。

测试并不能改进产品质量

今天看到了有人给出了这样的观点,那就是测试并不能改进产品质量,觉得挺有意思的,所以把作者的原文翻译一下,希望对大家有所裨益。

在世界上有一种天经地义的看法,那就是在软件开发过程的早期预防问题会比在测试后期发现问题带来更高质量的产品,但那不是真的。

这是不真实的,但不是因为大多数人可能首先想到的原因。问题不在于尽早解决问题是个坏主意。这通常是一个非常好的主意。

问题是,测试自身不会导致更高质量的产品,在所有。问题预防和测试是不同的追求。测试不能预防问题,测试不能改进产品。

对我而已——一名教师和熟练测试的倡导者——这可能看起来很疯狂,但这是真的。称体重不会让你减肥。验血不会让你更健康。学校的标准化考试不会让孩子更聪明,当然也不会提高教育质量。

测试可以做的是通过质疑产品以对其进行评估来提高我们对可能摆在我们面前的事物的理解和意识。测试——通过体验、探索和试验了解产品来评估产品的过程——帮助我们意识到可能需要解决的问题。

在日常生活中,浴室秤上的某个特定读数可能会促使我们更加谨慎地进食,或者进行更多的锻炼。验血可能会促使医生开抗疟疾药物。这些标准化的学校考试可能会建议改变课程、教育资金或教师培训。但在有人采取行动之前,测试只会提高对现状的认识,而不是情况本身。

在软件开发中,除非有人解决测试帮助我们发现的问题,否则改进不会发生。当然,如果问题没有被发现,改进的可能性就会大大降低——这就是测试如此重要的原因。测试帮助我们了解我们拥有的产品,因此我们可以判断是否是我们想要的产品。在需要改进的地方,测试揭示了改进的必要性。

有些人认为测试要求我们操作产品,这是软件开发流程的一部分。确实这是是一种非常重要的测试,但也只是产品测试的一种方式而已。

将产品更广泛地理解为某人生产的东西会很有帮助。这意味着我们可以有单元,模块,原型等多种手段的测试。

尽管我们通常将其称为审查,但我们确实可以在产品开发之前就进行全方位的思考,在这种情况下,产品其实就是之前所有的讨论,设计和思考的产出物,测试其实在产品的研发的初期就开始了。

测试的结果是通过这些活动进行的评估和学习。这种学习可以应用于产品——但它是响应测试而发生的事情,而不是测试本身,这是可以改进产品。

正如测试不能改进产品一样,测试也不能防止问题。作为测试人员,我们有一个持久的信念,即我们被要求测试的任何东西都已经存在问题。也就是说,产品中的问题在我们遇到之前就已经存在。

那么,如果测试不能预防问题,那又有什么用呢?测试可以帮助我们意识到存在的问题,然后人们可以进行更改以防止这些问题进一步发展。

在开发早期尝试预防问题是一个好主意。如果尝试能够成功,我们就更有可能开发出高质量的产品。尽管如此,即使是高度规范的开发过程也可能会出现问题。至少有两种方法可以确定是否发生了这种情况。

第一种方法是全程测试产品。通过与一系列客户互动并了解他们所做的事情,确定我们开发的东西就是客户想要的。通过检查和讨论,测试那些最初模糊然后却越来越清晰的设计。

另一种方式是在代码开发的初级就做单元测试,然后在后续的构建步骤里进行集成测试,code review,静态代码扫描和自动化测试,从而找出组件自身的问题或者是配置的问题。这些形式的测试通常不是很深入——这是一件好事,因为深度测试可能需要时间、精力和准备,这可能会对开发人员造成更多负担。

在开发人员测试的同时,测试人员专注于并执行深度测试。深度测试的目标是测试少见的、隐藏的、微妙的、偶尔发生的、紧急的错误,这些错误可以通过手工测试或者探索性测试来发现。

如果您的问题预防和问题缓解策略是成功的,并且你的代码和产品是可测试的,那你不太可能在测试后期遇到一些显而易见的低级问题。如果您不需要测试和报告这些问题,那么后续的深度测试会相对更快、更容易。

如果您的问题预防和问题缓解策略不成功,全面和深入测试是找出问题的一种方法。您发现的问题可以解决;可以改进产品,并且可以在产品交付之前防止业务和客户出现问题。

当人们面临损失、伤害、不良情绪或价值降低的风险时,最好在为时已晚之前意识到问题,这就是测试的终极意义之所在。

测试本身既不能防止问题也不能改进产品。但是测试确实可以预测需要预防的问题,并且测试可以照亮产品需要改进的地方。

因为全文大部分是机翻,所以行文肯定有不流畅的地方,另外加上作者本身的笔法比较曲折,有一些点机器翻译可能难以驾驭,因此全文可能看起来观点比较分散,没有强一致性,不过大家可以简单的意会,忽略那些遣词造句里的机器感。

总而言之作者的观点其实并不离经叛道,而且还是有一定的道理的,比如癌症筛查并不能治疗癌症,不过如果你尽可能早的可以通过筛查确定自身的风险,提前治疗和预防,那么癌症很有可能将不会发生。测试也是如此,测的再多开发和产品不去修改,那么产品本身的质量也不会得到改进,但是如果我们频繁的提前的进行测试,那么我们就可以尽早的暴露问题,我们的开发和产品也就会有足够的时间去进行修复工作。这也是为什么我们总强调 bug 生命周期的原因,测试并不只是简单的提 bug,而是要尽早提 bug,并保证 bug 得到及时修复并得到验证。

原文地址:Testing Doesn’t Improve the Product (linkedin.com)

使用重试模式来提升自动化测试的稳定性

看到一篇非常好的关于在自动化测试中引入重试模式的文章,https://www.thegreenreport.blog/articles/enhancing-automation-reliability-with-retry-patterns/enhancing-automation-reliability-with-retry-patterns.html 忍不住跟大家分享一下。

自动化测试,特别是 ui 自动化的稳定性提升是非常困难的,出错了就重试这个简单的策略可以帮助我们处理一些难以预料的异常情景,从而提升测试的整体稳定性。

这篇文章的作者就分享了几种重试的设计模式,通俗易懂而且相当实用。

动态重试

async function dynamicRetry(actions, maxRetries) {
  let retryCount = 0;

  async function retryAction() {
    try {
      return await actions();
    } catch (error) {
      if (retryCount < maxRetries) {
        retryCount++;
        return retryAction();
      } else {
        throw error;
      }
    }
  }
  return await retryAction();
}

it("Testing the dynamic retry pattern", async () => {
  await demoPage.navigateToDemoPageTextBoxes();
  await browser.pause(1000);
  await dynamicRetry(async () => {
    await demoPage.fillFullName("John Doe");
    await demoPage.fillEmail("test@gmail.com");
    await demoPage.fillCurrentAddress("test address");
    await demoPage.clickFakeButton();
  }, 3);
});

该模式的核心是设置一个最大的重试次数,每次重试过后次数加一,直到达到阈值。

轮询重试

另一种常见的重试模式是使用超时和轮询。这种方法会重复检查一组操作是否成功完成或者是否达到了超时时间。

async function pollRetry(actions, timeout, pollInterval) {
  const startTime = Date.now();
  let lastError;

  while (Date.now() - startTime < timeout) {
    try {
      await actions();
      return;
    } catch (error) {
      lastError = error;
      await sleep(pollInterval);
    }
  }

  throw lastError;
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

it("Testing the poll retry pattern", async () => {
  await demoPage.navigateToDemoPageTextBoxes();
  await browser.pause(1000);
  await pollRetry(
    async () => {
      await demoPage.fillFullName("John Doe");
      await demoPage.fillEmail("test@gmail.com");
      await demoPage.fillCurrentAddress("test address");
      await demoPage.clickFakeButton();
    },
    20000,
    5000
  );
});

这个模式的核心是设置一个总的超时时间,如果在这个时间段内发生了异常,那么就每隔一段时间重试一下。

什么是向量数据库

对于向量数据库有疑问吗?以下是对这个数据库的简要解释。

什么是向量?

向量是一组数字的数组,表示点在空间中沿多个维度的位置。例如,向量{12, 13, 19, 8, 9}表示一个点在 5 个维度上的位置。向量可以几何地表示数据,使得数据点之间可以进行数学比较。

向量与嵌入有什么关系?

在机器学习中,嵌入是表示数据点(如单词、句子、图像等)的向量。嵌入将数据的语义含义编码为向量形式。它们由经过训练的神经网络生成,这些网络将数据点映射到有效的向量表示。这些嵌入将相关概念放置在向量空间中靠近的位置。

向量数据库如何工作?

向量数据库存储了各种数据点(如文档、图像、产品等)的嵌入,并将这些嵌入映射回其原始数据。数据库可以基于向量的相似性进行快速的相似性搜索。查询嵌入可以与存储的嵌入进行比较,最相似的向量可以几乎即时地被检索出来。这为语义搜索、推荐和其他人工智能应用提供了支持。

向量数据库在人工智能中的应用是什么?

向量数据库具有几个关键的人工智能应用:

  • 语义搜索 - 基于含义而不仅仅是关键词来检索信息
  • 推荐 - 提供与用户兴趣最相关的内容
  • 大型语言模型 - 理解上下文中的单词,用于生成和摘要
  • 异常检测 - 识别与正常情况相比的异常数据点
  • 图像分类 - 基于视觉属性对图像进行分类

通过促进快速的相似性比较,向量数据库使得机器学习模型能够在数据之间建立联系。这支持了使用原始数据无法实现的高级人工智能功能。向量空间充当算法构建理解的一种"记忆"。