<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>软件测试基础 on 重定向学院</title><link>https://itest2025.pages.dev/categories/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95%E5%9F%BA%E7%A1%80/</link><description>Recent content in 软件测试基础 on 重定向学院</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Fri, 14 Feb 2025 03:27:30 +0800</lastBuildDate><atom:link href="https://itest2025.pages.dev/categories/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95%E5%9F%BA%E7%A1%80/index.xml" rel="self" type="application/rss+xml"/><item><title>AI自动化测试又趟出一条新路了？Claude 模型可以直接操作电脑了</title><link>https://itest2025.pages.dev/ai%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%8F%88%E8%B6%9F%E5%87%BA%E4%B8%80%E6%9D%A1%E6%96%B0%E8%B7%AF%E4%BA%86claude_%E6%A8%A1%E5%9E%8B%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E6%93%8D%E4%BD%9C%E7%94%B5%E8%84%91%E4%BA%86_161/</link><pubDate>Fri, 14 Feb 2025 03:27:30 +0800</pubDate><guid>https://itest2025.pages.dev/ai%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%8F%88%E8%B6%9F%E5%87%BA%E4%B8%80%E6%9D%A1%E6%96%B0%E8%B7%AF%E4%BA%86claude_%E6%A8%A1%E5%9E%8B%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E6%93%8D%E4%BD%9C%E7%94%B5%E8%84%91%E4%BA%86_161/</guid><description>&lt;p>前几天 Claude 模型更新一个杀手级应用。&lt;/p>
&lt;p>这次最大的更新并不是新模型，而是让 AI 能够直接与计算机互动。&lt;/p>
&lt;p>Anthropic 推出了「computer use」功能：通过 API，让 Claude 像人一样操作电脑，能够查看屏幕、移动光标、点击按钮和输入文字。换句话说，Claude 现在可以使用标准的计算机工具和软件。这对于开发者来说是个福音，他们可以借此减少枯燥的重复性工作，甚至让 Claude 执行一些开放式任务。&lt;/p>
&lt;p>为了实现这一功能，Anthropic 通过 API 让 Claude 能够感知并操作电脑界面。开发者可以通过这个 API，将用户的指令（例如：「使用电脑上的数据并结合网上信息填写表格」）转化为计算机的操作步骤（如打开表格、浏览器，并自动填写数据）。&lt;/p>
&lt;p>目前，部分公司已经开始应用该功能。例如，Replit 正在利用 Claude 3.5 Sonnet 的计算机操作能力，为其智能体项目开发关键功能，用于应用评估。&lt;/p>
&lt;p>尽管如此，这种技术并非全新。在此之前，Asana、Canva、DoorDash 等公司已经在尝试用 AI 处理复杂、多步骤的任务。&lt;/p>
&lt;h3 id="现实中的挑战">现实中的挑战&lt;/h3>
&lt;p>尽管「computer use」功能颇具潜力，但目前仍处于测试阶段。官方也承认，操作速度较慢且容易出错。一些对人类来说非常简单的操作，如拖动、缩放和滚动，对 Claude 来说仍有难度。&lt;/p>
&lt;p>在功能演示时也出现了一些问题，比如 Claude 不小心中断了一次长时间屏幕录制，导致录制内容丢失；另一段时间，它开始浏览黄石国家公园的照片。&lt;/p>
&lt;p>由于 Claude 通过截图理解屏幕内容，它有时无法捕捉到屏幕上瞬时出现的动态元素或弹出窗口。&lt;/p>
&lt;p>Anthropic 希望通过提前发布测试版来获取开发者的反馈，并表示随着时间推移，这一功能将不断优化。&lt;/p>
&lt;p>Anthropic 的开发者关系负责人 Alex Albert 分享了一个趣事：在测试「computer use」功能时，团队决定让 Claude 通过 DoorDash 下单订餐。经过一番分析，Claude 最终成功订购了披萨。&lt;/p>
&lt;h3 id="computer-use功能限制">「computer use」功能限制：&lt;/h3>
&lt;ul>
&lt;li>无法创建社交媒体账户&lt;/li>
&lt;li>无法发送邮件或消息&lt;/li>
&lt;li>无法在社交媒体发布内容&lt;/li>
&lt;li>无法完成购物&lt;/li>
&lt;li>无法访问私人信息&lt;/li>
&lt;li>无法处理验证码&lt;/li>
&lt;li>无法生成或编辑图片&lt;/li>
&lt;li>无法拨打电话&lt;/li>
&lt;li>无法访问受限内容&lt;/li>
&lt;li>无法进行需要身份验证的操作&lt;/li>
&lt;/ul>
&lt;p>可以看出来当前 claude 的自动化能力比较有限，但表现出来的推理能力及思考能力还是非常让人印象深刻的。&lt;/p></description></item><item><title>如何去处理难以复现的bug</title><link>https://itest2025.pages.dev/%E5%A6%82%E4%BD%95%E5%8E%BB%E5%A4%84%E7%90%86%E9%9A%BE%E4%BB%A5%E5%A4%8D%E7%8E%B0%E7%9A%84bug_160/</link><pubDate>Fri, 14 Feb 2025 03:27:10 +0800</pubDate><guid>https://itest2025.pages.dev/%E5%A6%82%E4%BD%95%E5%8E%BB%E5%A4%84%E7%90%86%E9%9A%BE%E4%BB%A5%E5%A4%8D%E7%8E%B0%E7%9A%84bug_160/</guid><description>&lt;p>看到一篇讨论如何处理难以复现的 bug 的文章，这应该是面试里会被问到的问题，随手翻译了一下，大家可以酌情参考。&lt;/p>
&lt;h2 id="转瞬即逝什么是海森堡-bug">转瞬即逝:什么是海森堡 bug?&lt;/h2>
&lt;p>你是否遇到过一种似乎违背逻辑、难以复现的缺陷？&lt;/p>
&lt;p>如果你的答案是&amp;quot;是的&amp;quot;,请放心,你并不孤单。&lt;/p>
&lt;p>这类缺陷往往在看似随机的条件下发生,这意味着我们无法确定重现问题所需的具体步骤。通常,我们能得到的唯一信息可能是一些模糊的描述,比如&amp;quot;我在执行某个特定流程时遇到了这个问题,但之后就再也无法重现了。&amp;quot;&lt;/p>
&lt;p>正因如此,这些问题通常被称为&amp;quot;不可重现的缺陷&amp;quot;,或者就像我最近发现的,被称为&amp;quot;海森堡 bug&amp;quot;。海森堡 bug 的一个显著特征是,任何试图观察或调试问题的行为都可能改变应用程序代码的行为。仅仅是观察问题的行为就会无意中改变问题发生的条件。这一点我们将在下面详细讨论。&lt;/p>
&lt;h2 id="当密切关注反而适得其反观察者效应">当密切关注反而适得其反:观察者效应&lt;/h2>
&lt;p>&amp;ldquo;海森堡 bug&amp;quot;这个术语是对著名物理学家维尔纳·海森堡的一个俏皮双关语,他首次提出了量子力学中的观察者效应。&lt;/p>
&lt;p>&amp;ldquo;观察者效应(不要与测不准原理混淆)指的是观察某个情况或现象必然会改变它。观察者效应在物理学中尤为突出,因为观察和不确定性是现代量子力学的基本特征。观察者效应不仅存在于物理学,在社会学、心理学、语言学和计算机科学等领域也广为人知。&amp;rdquo; ——《观察者效应》,IEEE 出版物,K. Baclawski 等人。&lt;/p>
&lt;p>一个简单的例子就是检查轮胎气压。仅仅是将气压表连接到气门上,就几乎必然会损失一些空气。所以,单纯尝试读取轮胎气压的行为就会改变轮胎的实际气压。&lt;/p>
&lt;p>考虑到这一点,我们可以推断,仅仅是尝试重现缺陷的过程就可能足以改变代码的行为,使得缺陷不再出现。&lt;/p>
&lt;h2 id="海森堡-bug-的一个例子同步失败的故事">海森堡 bug 的一个例子:同步失败的故事&lt;/h2>
&lt;h3 id="同步缺陷是如何产生的">同步缺陷是如何产生的&lt;/h3>
&lt;p>想象我们需要测试一个使用多线程的应用程序。多线程是一种执行方法,允许在一个进程中创建多个线程。每个线程都独立执行,但与其他线程共享进程资源。构建这样的应用程序需要细致的编程,以避免可能出现的问题,如竞态条件和死锁。&lt;/p>
&lt;p>在应用程序代码中,开发人员创建了一个修改共享计数器变量的函数。理想情况下,开发人员会使用同步机制来确保每个线程在继续执行之前都达到与其他线程相关的已知操作点。然而,如果没有实现同步机制,两个线程就可能同时修改共享计数器。&lt;/p>
&lt;p>简单来说就是:&lt;/p>
&lt;p>我们有一个初始值为'0&amp;rsquo;的计数器变量。线程一将计数器变量更新为'1&amp;rsquo;,同时线程二将计数器变量更新为'2&amp;rsquo;。&lt;/p>
&lt;p>那么正确的值是多少？&lt;/p>
&lt;p>在这种情况下,根本无法确定,这很可能导致应用程序出现不可预测的行为。&lt;/p>
&lt;h3 id="观察者效应的实际表现">观察者效应的实际表现&lt;/h3>
&lt;p>调试这样的问题可能异常困难。通过添加打印语句、日志记录或使用调试器,线程的时序可能会发生足够大的变化,使得竞态条件的出现概率大大降低。这就给人一种缺陷消失的错觉。&lt;/p>
&lt;p>像这样的不可重现缺陷或&amp;quot;海森堡 bug&amp;quot;可能会让开发人员和测试人员头疼不已,因为它们经常出其不意地出现,又消失得无影无踪。这些缺陷的本质特征使得它们极难诊断、记录和修复,可能导致开发人员和测试人员之间的挫折感。&lt;/p>
&lt;h2 id="打破在我的机器上能用的论调">打破&amp;quot;在我的机器上能用&amp;hellip;&amp;ldquo;的论调&lt;/h2>
&lt;p>处理那些我们无法可靠重现的隐蔽缺陷可能是软件开发中最具挑战性的方面之一。这些问题不仅需要技术专长,还需要团队成员之间的紧密合作和清晰的沟通。所有团队成员都必须以开放的心态来处理这些问题,认识到仅仅因为问题在一个环境中没有出现,并不意味着它在另一个环境中就不存在。&lt;/p>
&lt;p>用随意的&amp;quot;在我的机器上能用&amp;quot;的心态来否定问题,这种做法会破坏寻找解决方案和提高项目整体质量所需的协作精神。相反,这些挑战应该被视为加深理解、加强合作和构建更具弹性系统的机会。&lt;/p>
&lt;h2 id="软件团队该怎么办">软件团队该怎么办？&lt;/h2>
&lt;p>不可重现的缺陷可能源于各种因素,包括罕见的时序条件、特定的硬件或软件配置,或软件内部的复杂交互。理解和修复这些缺陷需要敏锐的洞察力、系统的方法和大量的耐心。每次尝试调试问题都可能感觉像是徒劳无功,但耐心和沟通是关键。&lt;/p>
&lt;p>如果缺陷仍然无法重现,我们该怎么办呢？&lt;/p>
&lt;h3 id="作为团队理解缺陷的上下文">作为团队理解缺陷的上下文&lt;/h3>
&lt;p>&lt;code>&amp;quot;上下文:事情发生的情况,有助于你理解它。&amp;quot; - 牛津学习词典&lt;/code>&lt;/p>
&lt;p>所有提出的缺陷都需要对上下文有深入和广泛的理解。上下文包括各种因素,如软件运行的环境、发现缺陷时的具体条件,以及导致问题的操作序列。没有这些信息,就很难把握缺陷的全部影响,这可能会显著改变对其风险和影响的认知。&lt;/p>
&lt;p>以下是一些可能有助于确定是否应该继续调试不可重现缺陷的考虑因素：&lt;/p>
&lt;h4 id="评估潜在影响">评估潜在影响&lt;/h4>
&lt;ul>
&lt;li>评估缺陷的潜在严重性。如果缺陷可能导致数据丢失、安全漏洞或重大用户中断,即使很难重现也可能值得进一步调查。&lt;/li>
&lt;li>考虑缺陷可能发生的频率。一个罕见但灾难性的缺陷可能仍然值得深入调查,而一个罕见且轻微的问题可能不值得。&lt;/li>
&lt;/ul>
&lt;h4 id="评估继续调查的投资回报">评估继续调查的投资回报&lt;/h4>
&lt;ul>
&lt;li>评估尝试重现缺陷所消耗的时间和资源。如果付出的努力与潜在影响不成比例,可能就不值得继续追究。&lt;/li>
&lt;li>考虑如果团队专注于重现单个缺陷,可能会忽视或延迟哪些其他工作或改进。有时候,关注已知的、可重现的问题或新功能可能是更好的时间利用方式。&lt;/li>
&lt;/ul>
&lt;h4 id="收集所有可用信息">收集所有可用信息&lt;/h4>
&lt;ul>
&lt;li>确保缺陷详情得到记录并传达给团队。这应该包括预期与实际行为的对比、问题出现的位置、环境信息(如软件版本、操作系统、硬件和配置)、发生频率以及任何其他相关信息。&lt;/li>
&lt;li>检查是否有足够的日志、错误报告或遥测数据。如果信息太少或没有信息,可能很难取得进展。如上所述,这些缺陷并不总是能获得统计数据和信息,因此团队不应该仅仅依赖日志提供的信息。&lt;/li>
&lt;li>有时最终用户可以提供有助于重现问题的宝贵见解或模式。如果有持续的用户报告,可能值得进一步调查。&lt;/li>
&lt;li>向组织内外的其他人寻求建议。你可能不是唯一遇到这个问题的人。在网上研究,与组织内的其他团队交流,检查在线缺陷报告(例如 GitHub issues)。你可能会在第三方包和开源软件的代码库中找到关于这个问题的好文档以及如何重现它。&lt;/li>
&lt;/ul>
&lt;h4 id="监控系统并减轻缺陷的潜在影响">监控系统并减轻缺陷的潜在影响&lt;/h4>
&lt;ul>
&lt;li>是否可以实施一个变通方案或保护措施来最小化缺陷的潜在影响？例如,如果你在代码中使用了第三方包,而缺陷存在于这个包中,你能否使用一个完全不同的包来解决问题？这样就消除了进一步调查的需要。&lt;/li>
&lt;li>设置监控以捕获更详细的数据,以备缺陷再次出现时使用。这可能为未来的调查提供线索。&lt;/li>
&lt;li>考虑创建自动化脚本来监视特定的词或对象,并计划它们在一天中定期运行。这样做可能有助于深入了解导致缺陷发生的原因。它还可能突显出问题是否在特定时间发生。这些信息随后可以与后台触发的其他服务和操作(例如备份或防病毒更新)相关联。&lt;/li>
&lt;/ul>
&lt;h4 id="协调团队和利益相关者的优先级">协调团队和利益相关者的优先级&lt;/h4>
&lt;ul>
&lt;li>考虑如果缺陷后续在生产环境中出现是否会对用户或利益相关者体验造成损害。如果缺陷对用户或业务利益相关者来说是高优先级的,可能值得继续努力。&lt;/li>
&lt;li>确保团队充分了解情况,并与他们合作决定是否继续调查。有时一个全新的视角可能会带来不同。&lt;/li>
&lt;/ul>
&lt;h2 id="总结尝试找出海森堡-bug-的根本原因是否值得">总结:尝试找出海森堡 bug 的根本原因是否值得？&lt;/h2>
&lt;p>关于是否应该花更多时间调查海森堡 bug 的决策过程应该是一项协作努力,涉及所有相关利益相关者,包括开发人员、QA 工程师、产品经理,甚至在适当的情况下包括最终用户。这确保了各种观点都得到考虑,从而做出更明智和平衡的决定。此外,采用一种视每个缺陷(即使是那些难以重现的缺陷)为加强团队调试实践机会的心态,可以带来软件质量的长期改进。&lt;/p></description></item><item><title>测试领域还有发展空间吗？</title><link>https://itest2025.pages.dev/%E6%B5%8B%E8%AF%95%E9%A2%86%E5%9F%9F%E8%BF%98%E6%9C%89%E5%8F%91%E5%B1%95%E7%A9%BA%E9%97%B4%E5%90%97_159/</link><pubDate>Fri, 14 Feb 2025 03:26:49 +0800</pubDate><guid>https://itest2025.pages.dev/%E6%B5%8B%E8%AF%95%E9%A2%86%E5%9F%9F%E8%BF%98%E6%9C%89%E5%8F%91%E5%B1%95%E7%A9%BA%E9%97%B4%E5%90%97_159/</guid><description>&lt;p>今天看到一篇讨论测试职业发展的文章，&lt;a href="https://automationhacks.io/2024-10-12-is-there-growth-in-testing" target="_blank" rel="noopener noreffer ">原文&lt;/a>比较中肯，我结合自己的经历，稍微发展一下，供大家参考。&lt;/p>
&lt;h2 id="常见的困惑">常见的困惑&lt;/h2>
&lt;p>下面一些问题是测试人员的常见困惑。&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;我听说测试工作 10 年就到头了，我是不是应该转向开发或管理？&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;我的经理建议说，成为管理者是测试职业发展的必经之路&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;我感到困惑，不知道如何从 QA 自动化转型为 SDET&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;测试是不是一个死胡同工作？这个领域真的有职业发展前景吗？&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;h2 id="职业阶梯">职业阶梯&lt;/h2>
&lt;p>在国外，测试领域主要有 3 种角色。&lt;/p>
&lt;ul>
&lt;li>测试工程师&lt;/li>
&lt;li>测试开发工程师(SDET)&lt;/li>
&lt;li>工程经理(EM)&lt;/li>
&lt;/ul>
&lt;p>其中测试和测试开发被定义为个人贡献者。&lt;/p>
&lt;p>通常对个人贡献者有以下级别划分：&lt;/p>
&lt;ul>
&lt;li>实习生、新手、入门级(0-2 年)&lt;/li>
&lt;li>中级(2-6 年)&lt;/li>
&lt;li>高级(6-9 年)&lt;/li>
&lt;li>Staff/主管级(8-12 年)(可能有直接下属)&lt;/li>
&lt;li>架构师/首席(12 年以上)(可能有直接下属)&lt;/li>
&lt;li>&amp;hellip;更高级别如高级首席/架构师、杰出工程师(比较罕见)&lt;/li>
&lt;/ul>
&lt;p>工程经理或者技术经理大概包含下面的级别。&lt;/p>
&lt;ul>
&lt;li>工程经理(5-8 年)&lt;/li>
&lt;li>高级工程经理(8-12 年)&lt;/li>
&lt;li>总监(12-15 年)&lt;/li>
&lt;li>VP(副总裁)(15 年以上)&lt;/li>
&lt;li>SVP(高级副总裁)(15 年以上)&lt;/li>
&lt;li>GM(总经理)(15 年以上)&lt;/li>
&lt;/ul>
&lt;p>VP 及以上的职级通常只在大公司才有，但直到总监级别的职位却很常见。很多公司可能会根据职位需求和公司规模来设置职级上限。&lt;/p>
&lt;p>在国内情况也差不多，无非就是个人贡献者的天花板可能是技术专家，技术经理的潜力可能更大一点。&lt;/p>
&lt;h2 id="晋升方式">晋升方式&lt;/h2>
&lt;p>大致来说，一般的晋升方式如下：&lt;/p>
&lt;ul>
&lt;li>学习：在你的领域成为深度专家，确保你具备完成工作所需的技术能力，这可能涉及学习新技术、技术栈、编程语言或人际交往技能。&lt;/li>
&lt;li>出色执行：你应该清楚理解自己在组织中的角色，与主管沟通确保双方对此有共识，然后去执行、执行、再执行。持续完成一个又一个项目，始终把客户放在心上，做到最好。确保你是团队中最优秀的 5%，努力在各个方面创造价值，成为团队的力量倍增器。&lt;/li>
&lt;li>提前表现：关注你的下一级别职位的期望，并确保承担其中的一些职责。持续完成这些任务，展现出持续强劲的表现记录。绩效评估通常是滞后的，如果你已经在以更高级别的标准在工作，而且你的主管也不错，那么你很可能很快就会得到晋升。&lt;/li>
&lt;li>寻求指导与传授：找到公司内部、本地区或全球最优秀的人，跟随他们。向他们学习，吸收他们的智慧。寻找导师，与他们讨论想法，然后回去实践并展示实际价值。你也应该把所学传授给身边的人，帮助他们提升。&lt;/li>
&lt;/ul>
&lt;p>在国内，可能还包含&lt;/p>
&lt;ul>
&lt;li>运气。这里体现的是选择大于努力，加入一个业务好并且高速发展的团队，晋升的机会远远大于业务一般而且走下坡路的团队。&lt;/li>
&lt;li>创新能力。国内其实非常卷创新的，测试需要晋升可能需要周期性的“搞事情”，落地一些高概念或者新技术就可能给管理者留下深刻的印象。&lt;/li>
&lt;li>指标落地能力。能定指标，落地指标以及提升指标。其实就是换种方式去卷。&lt;/li>
&lt;/ul>
&lt;h2 id="拓展领域">拓展领域&lt;/h2>
&lt;p>打怪升级不是职业生涯的全部。&lt;/p>
&lt;p>你会发现，仅仅把薪酬、职称和职级作为成长的标准，最终可能会显得很肤浅。&lt;/p>
&lt;p>请不要误解我的意思，这些确实很重要，你需要在这方面做得好。&lt;/p>
&lt;p>✅ 你的生活和家庭都需要你这样做。我会鼓励你继续为此努力，确保你的付出得到应有的回报。&lt;/p>
&lt;p>❌ 但这并不是唯一重要的事。&lt;/p>
&lt;p>当人们说测试工作在 X 年后就到头了时，他们实际上是用职级阶梯作为唯一的成长指标，这种观点过于肤浅。&lt;/p></description></item><item><title>大厂的测试人员在用ai干点啥</title><link>https://itest2025.pages.dev/%E5%A4%A7%E5%8E%82%E7%9A%84%E6%B5%8B%E8%AF%95%E4%BA%BA%E5%91%98%E5%9C%A8%E7%94%A8ai%E5%B9%B2%E7%82%B9%E5%95%A5_158/</link><pubDate>Fri, 14 Feb 2025 03:26:38 +0800</pubDate><guid>https://itest2025.pages.dev/%E5%A4%A7%E5%8E%82%E7%9A%84%E6%B5%8B%E8%AF%95%E4%BA%BA%E5%91%98%E5%9C%A8%E7%94%A8ai%E5%B9%B2%E7%82%B9%E5%95%A5_158/</guid><description>&lt;p>上周末参加了可能是国内顶级的 AI 技术峰会，主要观摩了测试专场的内容，全程基本都是大厂和高校进行分享，应该可以部分代码国内的头部水平，感触良多，跟大家分享一下。&lt;/p>
&lt;p>可以看到大模型出现以来，很多测试同学都在探索 ai 在测试领域的应用，这一年多以来也有不少的落地项目和产出，总的来说还是让人颇受启发的。&lt;/p>
&lt;p>简单回顾一下，大家的发力点大概在下面几个方面。&lt;/p>
&lt;h2 id="接口测试">接口测试&lt;/h2>
&lt;p>ai 在接口测试中的应用大概有下面两种。&lt;/p>
&lt;ul>
&lt;li>通过大模型和 RAG 结合，使用模型的推理能力，让模型根据 api 文档自动生成用例和断言&lt;/li>
&lt;li>使用大语言模型生成接口测试的数据&lt;/li>
&lt;/ul>
&lt;p>讨论这个议题的同学很多，大家的实践也都比较深入了，特别是在跟现有系统的整合方面，有些公司可以做到在内部测试平台上直接一键生成各种接口的测试用例，并运行生产报告，在工程方面的探索还是值得称道的。&lt;/p>
&lt;p>不过当前的进展也有很多不完善的地方&lt;/p>
&lt;ul>
&lt;li>大家都没有很好的指标可以统计大模型生成用例的可用性以及准确性&lt;/li>
&lt;li>没有人真正的展示 ai 生成的断言是什么样子的，在分享后的交流里，有的讲师表示目前他们只实现了非常简单的不带业务逻辑的断言，比如响应状态码的断言&lt;/li>
&lt;/ul>
&lt;h2 id="ui-自动化测试">UI 自动化测试&lt;/h2>
&lt;p>似乎只有 1 位大厂的讲师分享了他们在 UI 自动化领域的探索，他们的当前的进展可能是&lt;/p>
&lt;ul>
&lt;li>通过 Agent 自动生成 ui 自动化用例，具体的实现细节也是先简化 dom，然后使用 agent 进行推断&lt;/li>
&lt;li>通过 Agent 实现 ui 自动化用例自动更新的能力，比如更新页面上发生变化的元素&lt;/li>
&lt;/ul>
&lt;p>一些我觉得不是很清楚的地方是&lt;/p>
&lt;ul>
&lt;li>项目似乎是进行时，并没有真正落地并大规模使用&lt;/li>
&lt;li>ui 自动化用例的更新范围似乎只是页面上发生变化的元素，如果系统流程发生了些许变化，大模型可能也是爱莫能助的&lt;/li>
&lt;/ul>
&lt;h2 id="模型效果评估">模型效果评估&lt;/h2>
&lt;p>使用指标对大模型的效果进行评估也是测试人员工作的一部分。&lt;/p>
&lt;p>这里测试人员需要&lt;/p>
&lt;ul>
&lt;li>准备测试数据集，也可以用 ai 生成测试数据集&lt;/li>
&lt;li>进行自动化验证并提炼模型效果指标&lt;/li>
&lt;/ul>
&lt;p>这一块我不是很懂，不过目前看来大家用的可能是业内统一的评测框架和指标，这块的天花板很高，有很大的深入研究的空间。&lt;/p>
&lt;h2 id="需求分类">需求分类&lt;/h2>
&lt;p>使用 Agent 和 RAG 对需求进行分类和打分，标识出需求风险的级别，高风险的需求需要进行人工测试，低风险的需求可以免测。&lt;/p>
&lt;p>这是整场分享最吸引我的议题。&lt;/p>
&lt;p>因为这个想法直接抓住了测试的核心问题，那就是需求问题。&lt;/p>
&lt;p>目前看来这个方案的召回率很高，准确性还有提升的空间。&lt;/p>
&lt;p>这个项目的局限性是&lt;/p>
&lt;ul>
&lt;li>模型对多模态的文档没有很好的处理办法，毕竟很多需求里图文并茂，不能识别图片和视频实在是比较遗憾&lt;/li>
&lt;/ul>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>总的看来很多大厂都在大模型与测试相结合方面积极的进行实践和落地，大家在工程化方面的探索还是非常积极主动的。&lt;/p>
&lt;p>不过目前看杀手级应用还是没有，最近正好 openai 和 claude ai 都在智能体上面重点发力。&lt;/p></description></item><item><title>测试用例设计中的契诃夫之枪原则</title><link>https://itest2025.pages.dev/%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B%E8%AE%BE%E8%AE%A1%E4%B8%AD%E7%9A%84%E5%A5%91%E8%AF%83%E5%A4%AB%E4%B9%8B%E6%9E%AA%E5%8E%9F%E5%88%99_157/</link><pubDate>Fri, 14 Feb 2025 03:26:26 +0800</pubDate><guid>https://itest2025.pages.dev/%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B%E8%AE%BE%E8%AE%A1%E4%B8%AD%E7%9A%84%E5%A5%91%E8%AF%83%E5%A4%AB%E4%B9%8B%E6%9E%AA%E5%8E%9F%E5%88%99_157/</guid><description>&lt;p>看到一篇跟用例数据准备相关的文章，觉得挺有道理的。我之前在设计用例数据的时候就会犯类似的错误，这篇文章其实说的非常在理。&lt;/p>
&lt;p>翻译了一下&lt;a href="https://solid-dry-kiss.hashnode.dev/chekhovs-gun-principle-for-testing" target="_blank" rel="noopener noreffer ">原文&lt;/a>，供大家参考。&lt;/p>
&lt;p>编写测试用例更像是讲故事而非纯技术工作，这种观点并不罕见。最近我在 The Bike Shed 播客中听到这个观点，而且在博客文章和会议演讲中也经常能看到类似的讨论。既然测试编写是一种讲故事的艺术，那么我们是否应该借鉴叙事原则来改进我们的测试呢？&lt;/p>
&lt;p>谈到讲故事的原则，首先想到的就是契诃夫之枪原则。这个原则是什么？用安东·契诃夫自己的话说（引自大英百科全书）：&lt;/p>
&lt;blockquote>
&lt;p>如果在舞台上放置了一支上膛的步枪，那么它就必须被开火。不要做出你不打算履行的承诺。&lt;/p>&lt;/blockquote>
&lt;p>大英百科全书还给出了如下定义：&lt;/p>
&lt;blockquote>
&lt;p>这是一条适用于戏剧、文学和其他叙事形式的原则，它强调故事中引入的每个元素都应该对情节发展必不可少。&lt;/p>&lt;/blockquote>
&lt;p>那么这个原则如何应用到我们的测试编写中呢？想象一下你正在为电商系统编写测试用例。你有不同类别的产品，这些类别对买家有一些限制条件。最明显的例子就是不能向未满十八岁的人销售酒类产品。在我们的例子中，这类人群不能将这些产品添加到购物车。&lt;/p>
&lt;p>让我们看一个测试代码示例（我使用的是 Elixir，但这个原则适用于大多数编程语言）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-elixir" data-lang="elixir">&lt;span class="line">&lt;span class="cl">&lt;span class="n">test&lt;/span> &lt;span class="s2">&amp;#34;don&amp;#39;t allow adding products to cart when age constraint is not met&amp;#34;&lt;/span> &lt;span class="k">do&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">buyer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">%&lt;/span>&lt;span class="nc">Person&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;John Smith&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">age&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">17&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">country&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ss">:uk&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">registered_on&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="sx">~U[2023-09-16T18:17:22Z]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">category&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">%&lt;/span>&lt;span class="nc">Category&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Alcohol&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">external_id&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">3242&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">constraints&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">%&lt;/span>&lt;span class="nc">AgeConstraint&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="ss">min&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">18&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">product&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">%&lt;/span>&lt;span class="nc">Product&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Triple Hazy IPA&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">category&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">category&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">sku&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;TRI-557&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="ss">added_at&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="sx">~U[2022-01-01T12:16:54Z]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cart&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nc">Cart&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">init&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">buyer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">assert&lt;/span> &lt;span class="nc">Cart&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cart&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">product&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">quantity&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="ss">:error&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ss">:constraint_violated&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个测试本身并不算糟糕，但它在多处违反了契诃夫之枪原则。在我们测试的&amp;quot;准备&amp;quot;阶段引入的每个标量都是契诃夫意义上的&amp;quot;枪&amp;quot;。它们都被放在了舞台上，读者可能会期待它们都会&amp;quot;开火&amp;quot;。这里我们有 10 个标量，相当于舞台上放了 11 把枪。什么是&amp;quot;开火&amp;quot;？就是当我们把这个值改成其他值时，测试应该失败。让我们检查一下这些标量：&lt;/p>
&lt;ul>
&lt;li>&lt;code>name: &amp;quot;John Smith&amp;quot;&lt;/code>: 无论改成什么，测试都不会失败&lt;/li>
&lt;li>&lt;code>age: 17&lt;/code>: 如果改成 18 或 22，测试会失败&lt;/li>
&lt;li>&lt;code>country: :uk&lt;/code>: 不会失败（除非我们实现了基于国家的限制，但目前没有）&lt;/li>
&lt;li>&lt;code>registered_on: &amp;lt;date&amp;gt;&lt;/code>: 无关紧要&lt;/li>
&lt;li>&lt;code>name: &amp;quot;Alcohol&amp;quot;&lt;/code>: 无关紧要&lt;/li>
&lt;li>&lt;code>external_id: 3242&lt;/code>: 这是什么？无关紧要&lt;/li>
&lt;li>&lt;code>min: 18&lt;/code>: 改成 15 会导致测试失败&lt;/li>
&lt;li>产品中剩余的 name、sku 和添加日期都不会影响测试&lt;/li>
&lt;li>&lt;code>quantity: 2&lt;/code>: 同样无关紧要&lt;/li>
&lt;/ul>
&lt;p>总结一下，我们的 11 个&amp;quot;枪&amp;quot;中只有两个会&amp;quot;开火&amp;quot;，约 18%。其余的都是纯粹的干扰，如果读者试图理解测试的动态性，这些都会让他们误入歧途。&lt;/p></description></item><item><title>测试工程师被解雇后，接下来该怎么办？</title><link>https://itest2025.pages.dev/%E6%B5%8B%E8%AF%95%E5%B7%A5%E7%A8%8B%E5%B8%88%E8%A2%AB%E8%A7%A3%E9%9B%87%E5%90%8E%E6%8E%A5%E4%B8%8B%E6%9D%A5%E8%AF%A5%E6%80%8E%E4%B9%88%E5%8A%9E_156/</link><pubDate>Fri, 14 Feb 2025 03:26:10 +0800</pubDate><guid>https://itest2025.pages.dev/%E6%B5%8B%E8%AF%95%E5%B7%A5%E7%A8%8B%E5%B8%88%E8%A2%AB%E8%A7%A3%E9%9B%87%E5%90%8E%E6%8E%A5%E4%B8%8B%E6%9D%A5%E8%AF%A5%E6%80%8E%E4%B9%88%E5%8A%9E_156/</guid><description>&lt;h2 id="写在前面">写在前面&lt;/h2>
&lt;p>前几天富途大裁员的消息相信让很多同学都感到压力倍增。&lt;/p>
&lt;p>如今互联网领域的裁员潮并没有收敛的趋势，大有人人自危的感觉。&lt;/p>
&lt;p>今天正好看到了一篇跟裁员相关的文章，翻译了一下，希望可以给大家提供一些思路。&lt;/p>
&lt;p>原文在这里: &lt;a href="https://filiphric.com/laid-off-as-a-tester-what-now" target="_blank" rel="noopener noreffer ">https://filiphric.com/laid-off-as-a-tester-what-now&lt;/a>&lt;/p>
&lt;h2 id="正文">正文&lt;/h2>
&lt;p>如果你在 QA（质量保证）领域工作已经几年了，你可能已经注意到这个行业正在迅速变化。不幸的是，这也意味着裁员浪潮正在席卷整个行业，无论是初级还是高级测试人员都不能幸免。&lt;/p>
&lt;p>最近，我遇到了一个在 Reddit 上发帖的测试工程师，他在手动测试岗位上已经工作了 15 年，而他所在的公司正经历一些变革。对未来的担忧已经蔓延，我想和大家分享一些看法，因为说实话，这种情况其实并不罕见。&lt;/p>
&lt;h2 id="跳出公司的舒适圈">跳出公司的舒适圈&lt;/h2>
&lt;p>作为测试人员，我们往往会深入了解公司的专业领域。但当我们不再为这家公司工作时，很难将这些专业知识应用到其他地方。我们常常将自身价值与所在公司绑定，一旦环境改变，就会感到迷茫。然而，事实上我们还是很有价值的！&lt;/p>
&lt;p>想想看 - 测试人员通常是全能型人才：&lt;/p>
&lt;ul>
&lt;li>他们负责发布管理&lt;/li>
&lt;li>他们能发现 bug&lt;/li>
&lt;li>他们拥有技术知识&lt;/li>
&lt;li>他们可能懂得编程&lt;/li>
&lt;li>他们擅长编写文档&lt;/li>
&lt;/ul>
&lt;p>我认为记录下你的知识，并真正理解自己的优势、专长和经验是很重要的。&lt;/p>
&lt;p>因为关键在于 - 在不同的公司中，你已有的技能可能会以不同的方式呈现！可能会有一家公司正在寻找你恰好具备的技能，只是他们没有将其称为&amp;quot;测试&amp;quot;岗位。这可能是产品负责人、Scrum 大师、发布经理、技术文档撰写员、开发体验工程师或其他角色。&lt;/p>
&lt;h2 id="自动化学习永远不会太晚">自动化学习永远不会太晚&lt;/h2>
&lt;p>如果你一直在做手动测试，还没有接触过测试自动化，但觉得现在是时候开始了 - 我强烈推荐查看 Applitools 的测试自动化大学。那里有大量免费资源，你可以学习各种测试自动化。不要觉得自己错过了机会 - 学习永远不会太晚！&lt;/p>
&lt;h2 id="关于人工智能">关于人工智能&lt;/h2>
&lt;p>我知道听到人工智能这个词，一些人可能会翻白眼（相信我，我懂！），因为网上关于这个话题的噪音太多了。要分辨哪些是有价值的信息确实很困难。&lt;/p>
&lt;p>尽管所有关于 AI 的讨论可能令人感到无所适从，但现实是：AI 已经来了，而且不会离开。许多公司正在转型，如果你想保持竞争力，就需要至少掌握一些相关知识。&lt;/p>
&lt;p>获取 AI 知识并不意味着你必须成为一名&amp;quot;提示工程师&amp;quot;。相反，想想如何运用你现有的专业知识并用 AI 来增强它。例如，在做测试自动化时，你可以：&lt;/p>
&lt;ul>
&lt;li>用 AI 提高工作效率&lt;/li>
&lt;li>尝试使测试自动化更加稳定&lt;/li>
&lt;li>使用 AI 加速学习&lt;/li>
&lt;/ul>
&lt;p>我个人最近几个月一直在使用 Cursor，它大大提升了我的学习效率。当它生成我不理解的代码时，我可以直接选中那段代码并要求解释。&lt;/p>
&lt;p>你可以将 AI 视为不会替代你技能，而是帮助你增强技能的工具。&lt;/p>
&lt;h2 id="展示你的工作">展示你的工作&lt;/h2>
&lt;p>在当前的就业市场中，让你的工作可见是非常重要的。即使你刚开始接触写代码，我也建议创建一个 GitHub 账号，上传你的代码仓库，展示你正在做的项目。&lt;/p>
&lt;p>虽然我已经很久没有面试了，所以请对我的建议保持警惕。但每当我看到一个展示自己工作的 GitHub 账号的候选人，他们都会立即获得加分（需要说明的是，我从未是唯一的决策者）。这不仅让我的工作更轻松，因为我可以清楚地了解候选人的技术水平，更重要的是，这表明他们愿意学习和尝试。&lt;/p>
&lt;h2 id="开始写博客">开始写博客&lt;/h2>
&lt;p>另一个绝佳的职业发展技巧是写博客并分享你的学习经历。正如 Angie Jones 最近提到的，这可能会为你打开很多机会之门。我就是一个活生生的例子。&lt;/p></description></item><item><title>为什么测试人员找工作越来越难了</title><link>https://itest2025.pages.dev/%E4%B8%BA%E4%BB%80%E4%B9%88%E6%B5%8B%E8%AF%95%E4%BA%BA%E5%91%98%E6%89%BE%E5%B7%A5%E4%BD%9C%E8%B6%8A%E6%9D%A5%E8%B6%8A%E9%9A%BE%E4%BA%86_155/</link><pubDate>Fri, 14 Feb 2025 03:22:45 +0800</pubDate><guid>https://itest2025.pages.dev/%E4%B8%BA%E4%BB%80%E4%B9%88%E6%B5%8B%E8%AF%95%E4%BA%BA%E5%91%98%E6%89%BE%E5%B7%A5%E4%BD%9C%E8%B6%8A%E6%9D%A5%E8%B6%8A%E9%9A%BE%E4%BA%86_155/</guid><description>&lt;p>最近在招聘，3 个测试岗位，1 个 sre 岗位，进展不是十分顺利。&lt;/p>
&lt;p>3 个测试岗位里 2 个是正式编制，1 个是外包岗位，sre 岗位是正编。&lt;/p>
&lt;p>招不到人的原因其实并不是因为简历不够，相反简历数据还是很可观的。&lt;/p>
&lt;p>核心原因还是因为现在招聘的要求比之前行业红火的时候高了很多。&lt;/p>
&lt;p>现在就职的公司大概属于三四流公司的水平，比上不足比下有余的类型，反正跟大厂比差距还是很大的。&lt;/p>
&lt;p>但哪怕是这种非头部公司，对新晋人员的要求还是有一定门槛的。&lt;/p>
&lt;p>对于正式编制来说，需要满足&lt;/p>
&lt;ul>
&lt;li>40 岁以下&lt;/li>
&lt;li>一本学历以上&lt;/li>
&lt;/ul>
&lt;p>外包编制相对没有那么严格，只要是正规的全职日本科就好。&lt;/p>
&lt;p>对年龄和学历的硬性要求就导致了尽管收到的简历很多，但真正可以通过筛选并进入面试流程的候选人寥寥无几。&lt;/p>
&lt;p>相反的，静下心来想一想，从另一方面推测，今年测试人员找工作其实应该非常困难。&lt;/p>
&lt;p>原因大概以下几点。&lt;/p>
&lt;h2 id="市场不景气">市场不景气&lt;/h2>
&lt;p>各种公司裁员倒闭的消息层出不穷，市场上真正的需求其实非常疲软，真正放出来的岗位其实非常有限。&lt;/p>
&lt;h2 id="企业要求水涨船高">企业要求水涨船高&lt;/h2>
&lt;p>三四流公司都要求一本学历了，这其实是行业从业人员渐进式换血的体现。&lt;/p>
&lt;p>之前行业欣欣向荣，英雄不问出处，旺盛的市场需求就造成了很多学历一般专业不对口的候选人可以进入软件测试行业。&lt;/p>
&lt;p>但现在市场相对饱和，大浪淘沙加上挤出效应，原先条件一般的从业人员慢慢被市场淘汰，客观上完成了一轮又一轮的洗牌。&lt;/p>
&lt;h2 id="测试行业停滞不前">测试行业停滞不前&lt;/h2>
&lt;p>测试行业这么多年来的变化其实相对有限，无论是云计算时期，音视频时期还是 ai 阶段，测试人员的工作性质和范围基本变化不大。&lt;/p>
&lt;p>整个行业停滞不前已经很多年了，缺少颠覆性的变化造成了行业本身新需求不足。&lt;/p>
&lt;p>另外行业十分稳定也造成了测试人员同质化相对严重，好的背景自然而然就成为了求职的关键。&lt;/p>
&lt;h2 id="近期建议">近期建议&lt;/h2>
&lt;p>既然寒冬已至，那么还是要有应对的套路的。&lt;/p>
&lt;ul>
&lt;li>有工作的同学相对要保持稳健一些，毕竟跳槽之后新东家忽然一夕之间倒闭关门也是有可能的&lt;/li>
&lt;li>没有工作的同学心态平和一些，多锻炼身体，减少不必要的开销，先苟住，静待春暖花开&lt;/li>
&lt;/ul>
&lt;h2 id="最后">最后&lt;/h2>
&lt;p>顺便发个招聘信息。&lt;/p>
&lt;ul>
&lt;li>正编测试，base 重庆/深圳，功能测试为主，希望有一定的工程化能力，20k 左右，oa 或 hr 项目，统招本科，一本，2 人&lt;/li>
&lt;li>外包测试，base 深圳，功能测试为主，oa 项目，1 人&lt;/li>
&lt;li>SRE 工程师，base 深圳，1 人，统招本科，一本&lt;/li>
&lt;/ul>
&lt;p>感兴趣的同学麻烦加下面的群哈。&lt;/p></description></item><item><title>第二春？令人惊喜的selenium项目selenium base</title><link>https://itest2025.pages.dev/%E7%AC%AC%E4%BA%8C%E6%98%A5%E4%BB%A4%E4%BA%BA%E6%83%8A%E5%96%9C%E7%9A%84selenium%E9%A1%B9%E7%9B%AEselenium_base_154/</link><pubDate>Fri, 14 Feb 2025 03:22:02 +0800</pubDate><guid>https://itest2025.pages.dev/%E7%AC%AC%E4%BA%8C%E6%98%A5%E4%BB%A4%E4%BA%BA%E6%83%8A%E5%96%9C%E7%9A%84selenium%E9%A1%B9%E7%9B%AEselenium_base_154/</guid><description>&lt;p>最近发现 github 月热门项目里有个老面孔 selenium base 在短期内获得了比较大的关注。&lt;/p>
&lt;p>这个项目存在的时间应该有好多年了，我记得当初似乎也写过文章去介绍。&lt;/p>
&lt;p>本以为这就是一个普通的结合 pytest 和 selenium 封装的测试框架，不料几年过去项目的发展似乎渐入佳境。&lt;/p>
&lt;p>这次最让我眼前一亮的功能是 selenim base 支持&lt;strong>绕过 Cloudflare 的访问校验&lt;/strong>。&lt;/p>
&lt;h2 id="新的爬虫利器">新的爬虫利器？&lt;/h2>
&lt;p>用 selenium 写过爬虫的同学可能都会对 Cloudflare 的访问校验感到头痛。&lt;/p>
&lt;p>简单来说，在你访问目标站点的时候，cloudflare 会自动校验此次访问是不是来自不明的 ip 或者设备，如果是用脚本去访问该站点的话，cloudflare 会直接进行拦截，不展示网页的内容。&lt;/p>
&lt;p>不过 selenium base 却用几行代码打破了这一桎梏。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">seleniumbase&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">SB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">SB&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">uc&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">test&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">locale_code&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;en&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">sb&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://gitlab.com/users/sign_in&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sb&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">activate_cdp_mode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sb&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">uc_gui_click_captcha&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sb&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>用什么工具写爬虫其实无关紧要，爬虫进入深水区的时候往往需要跟反爬策略做各种对抗。&lt;/p>
&lt;p>由于 selenium 本身使用了真实的浏览器进行网页访问，自带光环，可以绕过很多的反爬策略。&lt;/p>
&lt;p>但是 cloudflare 的前置拦截却一直没有稳定的解决方案，selenium base 提供了绕过校验的便利，看上去非常利好爬虫的发挥。&lt;/p>
&lt;h2 id="其他有意思的特性">其他有意思的特性&lt;/h2>
&lt;h3 id="支持录制">支持录制&lt;/h3>
&lt;p>文档在这里。https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/recorder_mode.md&lt;/p>
&lt;p>因为 selenium base 暴露出来的 api 比较有限，所以录制出的代码可用性相对较高。&lt;/p>
&lt;h3 id="可以把用例转换成-markdown-的表格模式">可以把用例转换成 markdown 的表格模式&lt;/h3>
&lt;p>这里的路子跟 robot framework 是反着来的。&lt;/p>
&lt;p>把代码转成了更容易阅读的表格，对用例评审来说还是很有用的。&lt;/p>
&lt;p>&lt;a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/case_plans.md" target="_blank" rel="noopener noreffer ">https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/case_plans.md&lt;/a>&lt;/p>
&lt;h3 id="智能等待">智能等待&lt;/h3>
&lt;p>selenium base 的 api 很多都是操作类型的，比如 click,type 之类，在进行操作时 selenium base 会进行智能等待，从而提升用例的稳定性。&lt;/p></description></item><item><title>看起来有点用但实际用处有限的ai测试工具coTestPilot.ai</title><link>https://itest2025.pages.dev/%E7%9C%8B%E8%B5%B7%E6%9D%A5%E6%9C%89%E7%82%B9%E7%94%A8%E4%BD%86%E5%AE%9E%E9%99%85%E7%94%A8%E5%A4%84%E6%9C%89%E9%99%90%E7%9A%84ai%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7cotestpilotai_153/</link><pubDate>Fri, 14 Feb 2025 03:21:46 +0800</pubDate><guid>https://itest2025.pages.dev/%E7%9C%8B%E8%B5%B7%E6%9D%A5%E6%9C%89%E7%82%B9%E7%94%A8%E4%BD%86%E5%AE%9E%E9%99%85%E7%94%A8%E5%A4%84%E6%9C%89%E9%99%90%E7%9A%84ai%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7cotestpilotai_153/</guid><description>&lt;p>最近看到一款新发布的 AI 自动化测试工具 coTestPilot.ai，项目主页在&lt;a href="https://github.com/jarbon/coTestPilot/tree/main" target="_blank" rel="noopener noreffer ">这里&lt;/a>。&lt;/p>
&lt;p>这是由 Checkie.AI 的测试专家开发的开源扩展，为自动化测试带来了额外的 AI 功能。该项目旨在通过简单集成现有的 Playwright 和 Selenium 测试，使每个自动化工程师都能享受到 AI 给测试带来的便捷。&lt;/p>
&lt;h2 id="什么是-cotestpilot">什么是 coTestPilot？&lt;/h2>
&lt;p>coTestPilot 是 checkie.ai 和 testers.ai 上可用的 AI 测试 agent 的轻量级版本。它通过利用 GPT-4 Vision 分析网页，为 Playwright 和 Selenium 扩展了自动化测试和缺陷检测的 AI 能力，以识别潜在问题、不一致和可用性问题。&lt;/p>
&lt;p>最棒的是，只需添加一个函数调用，就可以为现有的测试自动化添加 AI 驱动的检查。&lt;/p>
&lt;h2 id="主要特点">主要特点&lt;/h2>
&lt;ul>
&lt;li>多样化测试角色：内置多个测试 agent 配置文件，包括 UI/UX 专家、无障碍专家、安全测试人员等&lt;/li>
&lt;li>可定制检查：轻松添加自定义测试规则和专门的提示&lt;/li>
&lt;li>全面分析：识别视觉错误、内容不一致和功能问题&lt;/li>
&lt;li>详细报告：生成包含屏幕截图和详细问题描述的 HTML 报告&lt;/li>
&lt;li>速率限制和重试逻辑：内置 API 速率限制保护&lt;/li>
&lt;/ul>
&lt;h2 id="为什么使用-ai-测试-agent">为什么使用 AI 测试 agent？&lt;/h2>
&lt;p>传统的自动化测试擅长检查特定的预定义场景，但常常会忽略人工测试人员会立即发现的意外问题。coTestPilot 通过为自动化测试套件添加一个 AI 驱动的&amp;quot;额外视角&amp;quot;来解决这个问题。
AI agent 可以识别如下问题：&lt;/p>
&lt;ul>
&lt;li>元素错位和视觉缺陷&lt;/li>
&lt;li>内容不一致和拼写错误&lt;/li>
&lt;li>无障碍问题&lt;/li>
&lt;li>基本安全问题&lt;/li>
&lt;li>性能预警&lt;/li>
&lt;li>用户体验问题&lt;/li>
&lt;/ul>
&lt;h2 id="入门指南">入门指南&lt;/h2>
&lt;p>以 selenium 的版本为例。&lt;/p></description></item><item><title>Playwright v1.50.5发布了</title><link>https://itest2025.pages.dev/playwright_v1505%E5%8F%91%E5%B8%83%E4%BA%86_152/</link><pubDate>Fri, 14 Feb 2025 03:17:47 +0800</pubDate><guid>https://itest2025.pages.dev/playwright_v1505%E5%8F%91%E5%B8%83%E4%BA%86_152/</guid><description>&lt;p>Playwright v1.50.0 引入了多项值得关注的功能和改进，旨在提升测试体验。以下是这些新增功能的详细概述，并附有示例以说明其使用方法。&lt;/p>
&lt;h2 id="runner-增强">Runner 增强&lt;/h2>
&lt;h3 id="1-步骤超时配置">1. 步骤超时配置&lt;/h3>
&lt;p>现在可以通过 timeout 选项为单个测试步骤指定最大运行时间。如果某个步骤的运行时间超过此限制，测试将失败。&lt;/p>
&lt;p>示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;example test&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">page&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">test&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">step&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;step with timeout&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 此步骤必须在 1000 毫秒内完成
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">page&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">click&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;#some-button&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nx">timeout&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">1000&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-跳过测试步骤">2. 跳过测试步骤&lt;/h3>
&lt;p>新增的&lt;code>test.step.skip()&lt;/code>方法允许跳过特定的测试步骤，这在某些条件未满足或功能尚未实现的情况下非常有用。&lt;/p>
&lt;p>示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;some test&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">page&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">test&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">step&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;before running step&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 正常步骤
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">test&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">step&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">skip&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;not yet ready&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 此步骤将被跳过
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">test&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">step&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;after running step&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 即使上一步被跳过，此步骤仍会运行
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-aria-快照存储于单独文件">3. ARIA 快照存储于单独文件&lt;/h3>
&lt;p>&lt;code>expect(locator).toMatchAriaSnapshot()&lt;/code>方法已扩展，允许将 ARIA 快照存储在单独的 YAML 文件中，便于更好地组织和版本控制。&lt;/p>
&lt;p>示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;ARIA snapshot test&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">page&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">page&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">goto&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://example.com&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">page&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">locator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;body&amp;#34;&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="nx">toMatchAriaSnapshot&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在此示例中，ARIA 快照将存储在对应的 YAML 文件中。&lt;/p></description></item><item><title>postman替代工具:2024版</title><link>https://itest2025.pages.dev/postman-alternative-2024_151/</link><pubDate>Fri, 13 Sep 2024 02:02:30 +0800</pubDate><guid>https://itest2025.pages.dev/postman-alternative-2024_151/</guid><description>&lt;p>又又又看到有人发帖抱怨说 postman 太慢了，想找一款可以替代 postman 的工具。这个需求基本上每隔几个月都会被提出来一次。&lt;/p>
&lt;p>这里顺手总结一下原帖中提到的解决方案，再加上自己的亲身体验，给大家推荐一些 postman 的替代工具。&lt;/p>
&lt;p>反正我坚持认为，postman 只要不登录，速度快点，那么它还是用起来最顺手的 http 请求工具了。&lt;/p>
&lt;h2 id="rapidapi">RapidAPI&lt;/h2>
&lt;p>mac 用户强推了，当年的版本叫 paw，我是花了不少钱买的。现在完全免费了，妥妥的背刺。&lt;/p>
&lt;p>&lt;a href="https://paw.cloud/" target="_blank" rel="noopener noreffer ">https://paw.cloud/&lt;/a>&lt;/p>
&lt;h2 id="bruno">Bruno&lt;/h2>
&lt;p>开源的轻量 API 测试工具，类似 Postman。&lt;/p>
&lt;p>下载地址: &lt;a href="https://www.usebruno.com/" target="_blank" rel="noopener noreffer ">https://www.usebruno.com/&lt;/a>。&lt;/p>
&lt;p>主打一个开源以及个人用户不要钱。&lt;/p>
&lt;h2 id="thunder-client">Thunder Client&lt;/h2>
&lt;p>这个是 vscode 的扩展，很轻量，而且免费，用起来不如 postman 顺手。&lt;/p>
&lt;h2 id="rest-client">Rest Client&lt;/h2>
&lt;p>同样也是 vscode 的扩展，用着还行。&lt;/p>
&lt;h2 id="hoppscotch">Hoppscotch&lt;/h2>
&lt;p>今天才之前 Hoppscotch 的前身是之前我们关注过的 postwoman。&lt;/p>
&lt;p>工具地址：https://hoppscotch.io/&lt;/p>
&lt;p>建议直接用 chrome 的扩展，跟当年的 postman 一样。&lt;/p>
&lt;h2 id="insomnia">Insomnia&lt;/h2>
&lt;p>另一个类似 Postman 的 API 工具。&lt;/p>
&lt;p>我记得当年是完全开源免费的，现在要收钱了，不过免费的版本完全够用了。&lt;/p>
&lt;p>&lt;a href="https://insomnia.rest/" target="_blank" rel="noopener noreffer ">https://insomnia.rest/&lt;/a>&lt;/p>
&lt;h2 id="yaak">Yaak&lt;/h2>
&lt;p>这个似乎就是 Insomnia 的原作者新写的工具，目前免费。&lt;/p>
&lt;p>&lt;a href="https://yaak.app/" target="_blank" rel="noopener noreffer ">https://yaak.app/&lt;/a>&lt;/p>
&lt;h2 id="jmeter">Jmeter&lt;/h2>
&lt;p>属于啥都能干，但是体验没有 postman 好了，完全免费，怎么都香。&lt;/p></description></item><item><title>测试人员需要成为devops工程师吗</title><link>https://itest2025.pages.dev/do-testers-need-to-be-devops-engineers_150/</link><pubDate>Fri, 13 Sep 2024 01:22:18 +0800</pubDate><guid>https://itest2025.pages.dev/do-testers-need-to-be-devops-engineers_150/</guid><description>&lt;p>这确实是个非常值得讨论的话题。&lt;/p>
&lt;p>测试人员究竟是不是需要成为 devops 工程师，我的答案是不需要，但又需要。&lt;/p>
&lt;p>测试有时候在开发团队会不被开发同事所重视，甚至有时候会背上效率低下的骂名，我认为其中一个很重要的原因是：测试的工程化程度不高。&lt;/p>
&lt;p>想想开发的一些核心行为&lt;/p>
&lt;ul>
&lt;li>写设计文档和方案：这个工程化程度不算太高，但是如果是写接口文档，那么很多工具都支持从代码生成文档，这个是可以工程化的；&lt;/li>
&lt;li>搭建本地开发环境和测试环境：可以工程化。&lt;/li>
&lt;li>写代码：本身代码就是工程化的一环。&lt;/li>
&lt;li>构建发布产物：可以工程化。&lt;/li>
&lt;li>发布：可以工程化。&lt;/li>
&lt;li>监控和告警：可以工程化。&lt;/li>
&lt;/ul>
&lt;p>再看测试的核心行为&lt;/p>
&lt;ul>
&lt;li>用例编写：很难工程化；&lt;/li>
&lt;li>用例执行：手工执行用例很难工程化；&lt;/li>
&lt;li>测试数据准备和清理：可以工程化；&lt;/li>
&lt;li>提出 bug 并进行跟踪：很难工程化；&lt;/li>
&lt;/ul>
&lt;p>我们再看看 devops 的概念，DevOps 将软件开发（Dev）和 IT 运维（Ops）的工作进行整合和自动化，旨在提升并缩短系统开发生命周期。&lt;/p>
&lt;p>自动化是 DevOps 中的重要部分。软件程序员和架构师应使用“适应性函数”（fitness functions）来确保他们的软件保持在预期状态。&lt;/p>
&lt;p>简单来说 devops 就是运维工程化，而工程化最好的体现就是运维的自动化。&lt;/p>
&lt;h2 id="测试需要工程化">测试需要工程化&lt;/h2>
&lt;p>测试同学其实可以不用成为专职的 devops 工程师，毕竟这是另一个研发角色，负责的内容和范围也不太一样。&lt;/p>
&lt;p>但是测试同学确实是可以借鉴 devops 文化中的工程化思想，让测试变得更具工程化一些。&lt;/p>
&lt;p>然而我们看到，核心的一些测试行为确实很难进行工程化，这样一来，我们就需要扩大我们的工作半径，将&lt;strong>一切可以提升质量的行为&lt;/strong>都纳入到我们的工作职责中来，而这一系列的行为里，有一部分是可以进行工程化的。&lt;/p>
&lt;p>最典型的可以进行工程化质量实践就是构建 ci/cd 流水线。&lt;/p>
&lt;p>测试同学可以通过构建 ci/cd 流水线进行自动化的准出测试，如果对代码库的 precommit hook 进行工程化定制的话，还可以对准入进行控制。&lt;/p>
&lt;p>流水线的输入是代码，输出也是代码，其中执行的各种任务也都是代码实现的，所以如果测试同学想要构建一条对质量有所裨益的流水线，那么下面的一些技能还是需要具备的。&lt;/p>
&lt;ul>
&lt;li>一定的代码能力&lt;/li>
&lt;li>一定的工具整合能力，ci/cd 的工具很多，有时候我们不需要开发，只需要接入和定制就好&lt;/li>
&lt;li>一定的质量规划能力，加什么准入和准出？如何保证各种检查不会拖慢流水线的执行？&lt;/li>
&lt;/ul>
&lt;h2 id="小结">小结&lt;/h2>
&lt;p>简单来说，会代码，有工程化思想，有构建高质量产出物的工具和思路，这类的测试同学应该还是会受欢迎的。&lt;/p>
&lt;p>但不要忘了，测试同学的核心任务还是质量保障，ci/cd 只是质量保障的一种实践，而不是质量管理的全部。&lt;/p>
&lt;h2 id="其他人的观点">其他人的观点&lt;/h2>
&lt;p>最近看到了一篇文章对测试人员要不要成为 devops 人员进行了讨论，这里顺便放上来，供大家参考。&lt;/p>
&lt;p>原文地址: &lt;a href="https://visible-quality.blogspot.com/2024/09/do-testers-need-to-be-devops-engineers.html" target="_blank" rel="noopener noreffer ">https://visible-quality.blogspot.com/2024/09/do-testers-need-to-be-devops-engineers.html&lt;/a>&lt;/p>
&lt;p>在我职业生涯的某个阶段，我逐渐专注于理解测试环境。这一切始于观察子系统之间的关联，以及识别哪些数据可以兼容。在保险行业，进行有效测试没有其他选择，尤其是当时一个新的 IBM 大型机测试环境的搭建就耗费了 100 万和一年的时间（我也经历过）。我已经不太记得那时用的货币是芬兰马克还是欧元了，只记得那种对项目的巨大责任感，因为我需要确保这个耗资百万的项目能够顺利进行。那段经历让我从此更加重视测试环境，不仅对工作站、服务器和版本有了更清晰的分类，处理这些事情也变成了日常工作的一部分。&lt;/p></description></item><item><title>关于生成式AI在测试行为中的思考</title><link>https://itest2025.pages.dev/talk-about-llm-in-testing_149/</link><pubDate>Mon, 12 Aug 2024 23:33:13 +0800</pubDate><guid>https://itest2025.pages.dev/talk-about-llm-in-testing_149/</guid><description>&lt;p>看到了一篇讨论测试和 AI 的文章，有些观点确实是非常正确的，顺手翻译了一下，供大家参考。&lt;/p>
&lt;p>我自己读了两遍，发现翻译的版本措辞不够通顺，还是直接给个省流版本好了。&lt;/p>
&lt;h2 id="省流">省流&lt;/h2>
&lt;ul>
&lt;li>AI 生成的代码开发可能不理解其中的原理，这就为代码变更埋下了隐患，毕竟代码等于是抄的，改抄过来的东西往往不容易&lt;/li>
&lt;li>AI 每次生成的代码可能风格都不一样，这样用的越多，代码风格百花齐放的可能性就越大，这样在团队协同的时候会带来麻烦&lt;/li>
&lt;li>AI 生成的代码不好维护，集成进现有代码的难度其实很大&lt;/li>
&lt;li>AI 写用例很菜，一些异常情况可能考虑不到&lt;/li>
&lt;li>AI 生成的测试用例是没有测试模型指导的，所以随机性很大，这就导致了用例对功能的覆盖率可能不会很高&lt;/li>
&lt;li>AI 生成的代码可能引入过时的依赖库和解决方案，从而让团队背上了技术债&lt;/li>
&lt;li>泛化型的 AI 在代码和测试用例生成时可能不会给出最正确的决策，这很好理解，毕竟有些训练数据本身可能质量不高，这就造成了生成的内容质量低下&lt;/li>
&lt;li>招聘问题。是招会写代码的人还是招会用 ai 的人？&lt;/li>
&lt;/ul>
&lt;p>随着生成式 AI 及其对各行各业的影响成为热门话题,我想探讨一下它将如何影响软件测试,主要基于使用这类 AI 可能带来的风险。了解这些新工具的潜在风险,有助于我们获得相关信息,并思考将其融入团队时需要采取的测试策略。&lt;/p>
&lt;h2 id="代码质量不如亲自编写">代码质量不如亲自编写&lt;/h2>
&lt;p>生成式 AI 通过分析互联网上的内容来根据提示创建代码,这意味着你会继承其他开发者的编码风格和特点。这可能导致我们难以理解为我们生成的代码,从而增加维护和继承的难度。另一个风险是我们可能会想当然地认为 AI 按照我们的要求执行,而不去验证(因为代码难以理解)。&lt;/p>
&lt;p>这不仅影响产品代码,也会影响测试代码的生成。为应对这个问题,我们可以对代码进行全面的静态分析和重构,以提高其可读性。&lt;/p>
&lt;h2 id="编码习惯和风格不一致">编码习惯和风格不一致&lt;/h2>
&lt;p>与上述情况类似,AI 会采用各种程序员的风格和习惯。这意味着每次要求生成代码时,可能会得到不同的实现方式或编码风格。这同样会影响代码的可维护性和可继承性,缺乏一致性也使问题更难被发现。代码所有权也可能成为问题,因为难以确定是团队中谁的代码。在代码集成方面,不同的编码风格可能难以协调,函数行为可能相互矛盾。&lt;/p>
&lt;p>解决方案仍然是进行更多的静态分析和代码重构,以及对函数行为进行基于代码的测试,以确保代码的可理解性和正确性。&lt;/p>
&lt;h2 id="更高的维护成本">更高的维护成本&lt;/h2>
&lt;p>生成的代码可能难以与现有代码很好地融合,因此我们可能不得不替换而非添加代码。这可能导致现有的代码测试失效,增加代码行为偏离预期的风险。替换现有代码还意味着必须重新测试现有功能,特别是如果我们失去了贴近代码的测试,这将增加测试所需的时间。&lt;/p>
&lt;p>对代码(或生成的测试)缺乏理解意味着每次修改代码库时都需要花更多时间进行静态分析。一种应对方法是为测试建立独立的代码库,这样对代码的修改就不会影响测试。测试人员还必须人工审查生成的测试,确保它们与当前的代码库相符。&lt;/p>
&lt;h2 id="生成的代码质量如同实习生水平">生成的代码质量如同实习生水平&lt;/h2>
&lt;p>生成式 AI 创建的软件代码或自动化测试可能只达到实习生的水平。它会完成你明确要求的内容,但可能忽视细节或异常情况。这存在测试覆盖不全的风险,也可能无法发现更深层次的问题。任何不确定性都可能导致 AI 做出与你意图不符的假设,从而影响功能表现。&lt;/p>
&lt;p>应对这种情况的方法是确保继续进行探索性测试和人工测试。测试人员不仅要审查生成的测试,还要继续测试 AI 已涉及功能的准确性。我们还必须善于减少不确定性,并以不留任何假设空间的方式记录所有要求,以便输入到 AI 中(不要认为它会有常识并知道失败应该导致错误)。&lt;/p>
&lt;h2 id="缺乏测试模型">缺乏测试模型&lt;/h2>
&lt;p>由于你可能使用简短的提示而非详细的测试用例,你可能对测试覆盖率缺乏心理预期。如果过度依赖低代码或无代码测试工具,我们可能对代码/软件的行为缺乏深入理解。这可能影响软件的可维护性,并可能导致遗漏或错误测试边缘情况和功能。&lt;/p>
&lt;p>为应对这些风险,我们需要制定计划来探索代码和功能,以了解其工作原理。这种理解可以用于改进生成的自动化测试,并指导我们知道应该测试什么以及如何测试。&lt;/p>
&lt;h2 id="引入不良依赖项导致技术债务">引入不良依赖项导致技术债务&lt;/h2>
&lt;p>生成式 AI 可能会引入过时的库,这可能导致代码不受支持或无法正常工作。尝试更新生成的代码可能会失败,或者如果 AI 引入了我们不知道会发生变化的库,就存在代码突然停止工作而我们不知原因的风险。AI 使用的库可能存在安全漏洞,如后门或恶意代码,我们必须诊断和修复这些问题。&lt;/p>
&lt;p>我们必须确保团队分析所使用的库,并在生成的代码中记录这些库,以便检查其有效性。还需要对所有生成的代码进行静态分析,以测试是否存在漏洞和安全缺陷(包括对 AI 创建的功能进行渗透测试)。&lt;/p>
&lt;h2 id="ai-可能做出不直观的决策">AI 可能做出不直观的决策&lt;/h2>
&lt;p>所使用的 AI 可能会在创建代码、测试内容或测试方法上做出糟糕的决定。如果代码不直观,我们可能无法从代码中推断 AI 所做的假设。这带来了无法理解或维护代码和测试的风险,以及得到无用的测试或遗漏关键内容的风险。&lt;/p></description></item><item><title>跟线上事故相关的几个指标</title><link>https://itest2025.pages.dev/incident-metrics-that-matter_148/</link><pubDate>Mon, 12 Aug 2024 23:32:56 +0800</pubDate><guid>https://itest2025.pages.dev/incident-metrics-that-matter_148/</guid><description>&lt;p>最近业界比较流行面向指标的开发，据我所知在一些大厂，所有的开发过程指标和结果指标都要被明确定义出来并进行统计。&lt;/p>
&lt;p>这里无意去罗列各种过程指标和结果指标，只想稍微分享一下跟线上事故相关的几个指标。&lt;/p>
&lt;h2 id="事故量度指标">事故量度指标&lt;/h2>
&lt;p>事故量度指标帮助我们评估整个系统的表现。它们通过事故数量和时间频率,反映了系统的可靠性。然而,事故量度指标并不能让我们知晓我们对事故预案的准备程度。&lt;/p>
&lt;h3 id="-告警转化率alert-to-incident-ratio">✅ 告警转化率(Alert-to-Incident Ratio)&lt;/h3>
&lt;p>这个指标也被称为告警转换率，它对团队的健康状况至关重要。虽然我们强调快速响应每一个告警的重要性，但过多的告警可能会适得其反。当告警数量过多时，团队成员可能会产生&amp;quot;告警疲劳&amp;quot;，这不仅会影响团队的整体效率，还可能削弱我们的可靠性策略。&lt;/p>
&lt;p>长期处于高压状态下的团队成员容易感到精疲力竭，当真正严重的事故发生时，他们可能无法以最佳状态应对。这就是为什么我们需要密切关注告警转换率的原因。&lt;/p>
&lt;p>如果我们发现大量告警最终并未演变成实际事故，那就意味着我们需要重新审视告警机制了。找到合适的平衡点并非易事，这需要我们不断调整和优化可观测性工具链。正因如此，持续监控和分析告警转换率变得尤为重要，它能帮助我们及时发现问题，并做出必要的调整。&lt;/p>
&lt;h3 id="-单位时间事故数incidents-over-time">👻 单位时间事故数(Incidents Over Time)&lt;/h3>
&lt;p>这个指标简单地统计了特定时期内的事故发生次数。虽然看似直观，但单独使用时往往缺乏深度洞察。有趣的是，这个数据常常引起 SRE 团队之外人员的关注，但我们要警惕它可能变成一个华而不实的指标。&lt;/p>
&lt;p>需要注意的是，某个月份或季度的事故数量并不能直接反映系统的可靠性。这些数字的波动可能与业务的季节性特点有关，或者受到一些我们无法掌控的外部因素影响。&lt;/p>
&lt;p>不过，跟踪事故数量的变化趋势确实有其价值。它能帮助我们评估当前的值班安排是否合理，以及是否需要增加人手支持。为了保护团队不被过度劳累，很多公司都会设定每个值班轮次最多处理 2 起事故的上限。这种做法既能确保及时响应，又能避免团队成员疲惫不堪。&lt;/p>
&lt;h3 id="-平均故障间隔时间mtbf">✅ 平均故障间隔时间(MTBF)&lt;/h3>
&lt;p>Mean Time Between Failures&lt;/p>
&lt;p>MTBF（平均故障间隔时间）是一个重要的可靠性指标，它计算的是系统或某个组件在两次故障之间的平均时间。想象一下工厂里的安全记分牌，上面记录着&amp;quot;已安全运行天数&amp;quot;，MTBF 的概念与之类似。这个指标之所以能够很好地反映系统可靠性，是因为它直观地展示了系统出现故障的频率。&lt;/p>
&lt;p>MTBF 的应用非常广泛。通过分析这个指标，我们可以：&lt;/p>
&lt;ol>
&lt;li>对系统未来的可靠性进行预测&lt;/li>
&lt;li>制定更加合理的预防性维护计划&lt;/li>
&lt;li>比较不同组件的可靠性表现&lt;/li>
&lt;/ol>
&lt;p>特别值得注意的是，当我们发现某些组件的 MTBF 明显低于其他部分时，这就为我们提供了强有力的数据支持。利用这些数据，我们可以说服相关方投入资源，对这些问题频发的组件进行深入分析和改进。这种数据驱动的决策方法，能够帮助我们更加精准地提升系统的整体可靠性。&lt;/p>
&lt;h2 id="事故响应指标">事故响应指标&lt;/h2>
&lt;p>事故响应指标是衡量我们处理事故能力的重要标尺。通过这些指标，我们可以清晰地看到团队在应对事故时的表现，同时也能发现我们的工具链中可能存在的不足之处。这些洞察为我们持续改进事故响应流程提供了方向。&lt;/p>
&lt;h3 id="-平均确认时间mtta">👻 平均确认时间（MTTA）&lt;/h3>
&lt;p>Mean Time To Acknowledge&lt;/p>
&lt;p>想知道你的团队对告警的反应有多快吗？平均确认时间（MTTA）就是答案。这个指标精确地测量了从系统发出告警到团队成员确认接收之间的时间。&lt;/p>
&lt;p>虽然 MTTA 是个重要指标，但它更多地反映了你的值班系统的设置是否合理。如果你发现 MTTA 高于预期，那可能就需要重新审视你的值班轮换安排和升级策略了。&lt;/p>
&lt;p>不过，使用 MTTA 时要小心，因为它容易被值班工程师&amp;quot;钻空子&amp;quot;。如果你为 MTTA 设定了具体目标，工程师们可能会为了达标而快速确认告警，但这并不能保证他们会有效地处理整个事故直到解决。换句话说，快速确认不等于高效解决。&lt;/p>
&lt;p>因此，在使用 MTTA 时，我们需要全面考虑，将其与其他指标结合使用，以获得更全面的事故响应效率评估。&lt;/p>
&lt;h3 id="-平均解决时间mttr">✅ 平均解决时间（MTTR）&lt;/h3>
&lt;p>Mean Time To Resolve&lt;/p>
&lt;p>在所有事故响应指标中，MTTR 无疑是当之无愧的明星。它代表平均解决、恢复或修复时间。几乎所有推销 SRE 相关工具的人都会强调 MTTR 的重要性，因为从一线事故指挥到高层管理，每个人都关心一个核心问题：我们能多快解决问题？&lt;/p></description></item><item><title>充分发挥 Selenium 4 的潜力</title><link>https://itest2025.pages.dev/new-selenium-4-features_147/</link><pubDate>Mon, 12 Aug 2024 23:32:36 +0800</pubDate><guid>https://itest2025.pages.dev/new-selenium-4-features_147/</guid><description>&lt;p>selenium 4 的新功能，不是非常惊喜，不过确实比较实用了。&lt;/p>
&lt;p>在瞬息万变的网络自动化领域,Selenium 一直是主要参与者。随着 Selenium 4 的发布,其功能得到进一步增强,引入了前沿特性,简化了测试流程并提高了效率。Chrome DevTools 和 BiDi API 的集成不仅增强了 selenium 的技术能力,还为管理自动化项目的经理们带来了战略价值。&lt;/p>
&lt;h2 id="chrome-devtools-协议深入浏览器自动化">Chrome DevTools 协议:深入浏览器自动化&lt;/h2>
&lt;p>Selenium 4 与 &lt;strong>Chrome DevTools 协议&lt;/strong> (&lt;strong>CDP&lt;/strong>) 的集成为自动化测试人员开启了无限可能。通过直接与浏览器底层协议交互,CDP 实现了全面的网络和性能监控、控制台日志访问以及高级调试能力。&lt;/p>
&lt;p>想象一下,你需要测试应用在不同网络条件下的表现。利用 CDP,你可以模拟各种网络速度,分析应用如何处理缓慢或不稳定的连接。这一功能确保你的网络应用具有韧性,能够为各种网络条件下的用户提供最佳性能。&lt;/p>
&lt;p>&lt;strong>CDP 还支持以下强大功能:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>模拟网络条件&lt;/strong>: 模拟离线模式、慢速网络等情况,测试应用的适应能力&lt;/li>
&lt;li>&lt;strong>访问控制台日志&lt;/strong>: 直接捕获和分析控制台日志,便于调试和验证 JavaScript 错误&lt;/li>
&lt;li>&lt;strong>性能指标&lt;/strong>: 收集详细的性能指标,识别瓶颈并优化加载时间&lt;/li>
&lt;li>&lt;strong>安全测试&lt;/strong>: 监控和操作 cookie,追踪混合内容等安全问题,验证 HTTPS 配置&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>启用网络拦截的示例代码:&lt;/p>&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">ChromeDevToolsExample&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">static&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">main&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">setProperty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;webdriver.chrome.driver&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;path/to/chromedriver&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">ChromeOptions&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">options&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ChromeOptions&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">ChromeDriver&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ChromeDriver&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">options&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">DevTools&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">devTools&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getDevTools&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">devTools&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">createSession&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// 启用网络&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">devTools&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Network&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">enable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Optional&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">empty&lt;/span>&lt;span class="p">(),&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Optional&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">empty&lt;/span>&lt;span class="p">(),&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Optional&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">empty&lt;/span>&lt;span class="p">()));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// 添加网络请求监听器&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">devTools&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">addListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Network&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">requestWillBeSent&lt;/span>&lt;span class="p">(),&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;请求 URL: &amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">request&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">getRequest&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="na">getUrl&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">});&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;https://www.example.com&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">quit&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在这个例子中,我们启用了网络拦截来捕获浏览器发出的所有网络请求。这对于验证是否发出了正确的网络请求以及调试资源加载问题特别有用。&lt;/p></description></item><item><title>7大开源 CI/CD 工具 2024版本</title><link>https://itest2025.pages.dev/must-have-ci-cd-tools-2024_146/</link><pubDate>Mon, 12 Aug 2024 23:26:43 +0800</pubDate><guid>https://itest2025.pages.dev/must-have-ci-cd-tools-2024_146/</guid><description>&lt;p>现在很多测试岗位需要测试同学有 CI/CD 流水线的搭建经验，这里给大家推荐一下常用的 7 大 CI/CD 工具，覆盖了代码管理，ci/cd 集成，静态代码扫描，容器安全扫描，测试用例管理，性能测试等方向，值得收藏。&lt;/p>
&lt;h2 id="sonarqube-代码质量的守护者">&lt;a href="https://github.com/SonarSource/sonarqube" target="_blank" rel="noopener noreffer ">SonarQube&lt;/a>: 代码质量的守护者&lt;/h2>
&lt;p>SonarQube 是一款卓越的代码质量和静态分析工具。&lt;/p>
&lt;p>它通过发现 bug、漏洞和代码质量隐患,帮助开发者维护高水平的代码标准。SonarQube 凭借其直观的界面和全面的报告,确保您的代码库保持整洁和易于维护。&lt;/p>
&lt;p>我采访过的许多专家都表示,我会喜欢上 SonarQube,因为它能帮助改进代码并推广良好的编码习惯。&lt;/p>
&lt;p>SonarQube 的优点:&lt;/p>
&lt;ul>
&lt;li>静态代码分析&lt;/li>
&lt;li>检测 bug 和漏洞&lt;/li>
&lt;li>追踪代码质量隐患&lt;/li>
&lt;li>生成全面的质量报告&lt;/li>
&lt;/ul>
&lt;p>目前在 GitHub 上拥有 8.8k 星标。&lt;/p>
&lt;h2 id="gitlab-一体化-devops-平台">&lt;a href="https://about.gitlab.com/" target="_blank" rel="noopener noreffer ">GitLab&lt;/a>: 一体化 DevOps 平台&lt;/h2>
&lt;p>GitLab 不仅仅是版本控制工具。&lt;/p>
&lt;p>我交流过的专家认为,它是一个完整的 DevOps 平台,包含 CI/CD 流水线,为众多团队提供一站式解决方案。此外,GitLab 的集成方式简化了工作流程,增强了开发和运维团队之间的协作。&lt;/p>
&lt;p>GitLab 的优点:&lt;/p>
&lt;ul>
&lt;li>集成版本控制&lt;/li>
&lt;li>内置 CI/CD 流水线&lt;/li>
&lt;li>协作开发环境&lt;/li>
&lt;li>精简的 DevOps 工作流程&lt;/li>
&lt;/ul>
&lt;p>目前在 GitHub 上拥有 5.1k 星标。&lt;/p>
&lt;h2 id="gitleaks-保护密码信息">&lt;a href="https://github.com/gitleaks/gitleaks" target="_blank" rel="noopener noreffer ">Gitleaks&lt;/a>: 保护密码信息&lt;/h2>
&lt;p>在 DevOps 中,安全至关重要,Gitleaks 可以扫描和检测代码中的硬编码密码。这个工具有助于防止 API 密钥和密码等敏感信息意外泄露,提高整体安全水平。&lt;/p></description></item><item><title>如何优雅的测试cli应用</title><link>https://itest2025.pages.dev/how-to-test-a-cli-application_145/</link><pubDate>Sat, 03 Aug 2024 07:31:27 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-test-a-cli-application_145/</guid><description>&lt;p>之前写过一篇文章讨论如何去做 hello world 的单元测试，当时想到的一个方法是将 hello world 抽象成 1 个函数，这个函数返回 hello world 字符串，然后测试这个函数的返回值就好了，代码大概如下所示。&lt;/p>
&lt;pre tabindex="0">&lt;code>def hello_world():
 return &amp;#34;hello world&amp;#34;
&lt;/code>&lt;/pre>&lt;p>前几天看到 rust cli 手册里的一个做法，比上面的方案更加的合理，这里忍不住分享一下。&lt;/p>
&lt;h2 id="更优雅的解决方案">更优雅的解决方案&lt;/h2>
&lt;p>测试功能有两种互补的方法:测试构建完整应用程序的小单元,这些称为&amp;quot;单元测试&amp;quot;。还有从&amp;quot;外部&amp;quot;测试最终应用程序,称为&amp;quot;黑盒测试&amp;quot;或&amp;quot;集成测试&amp;quot;。让我们从第一种开始。&lt;/p>
&lt;p>为了弄清楚我们应该测试什么,让我们看看我们的程序功能是什么。主要来说,grrs 应该打印出与给定模式匹配的行。因此,让我们为此编写单元测试:我们要确保我们最重要的逻辑能正常工作,并且我们要以一种不依赖于周围任何设置代码(例如处理 CLI 参数)的方式来做到这一点。&lt;/p>
&lt;p>回到我们对 grrs 的第一个实现,我们在 main 函数中添加了这段代码:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">for&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">lines&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="fm">println!&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="si">{}&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>遗憾的是,这不太容易测试。首先,它在 main 函数中,所以我们不能轻易调用它。这可以通过将这段代码移到一个函数中来轻松解决:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="k">fn&lt;/span> &lt;span class="nf">find_matches&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>: &lt;span class="kp">&amp;amp;&lt;/span>&lt;span class="kt">str&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pattern&lt;/span>: &lt;span class="kp">&amp;amp;&lt;/span>&lt;span class="kt">str&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">for&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">in&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">lines&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="fm">println!&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="si">{}&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>现在我们可以在测试中调用这个函数,看看它的输出是什么:&lt;/p></description></item><item><title>没想到测试知识库竟然还真有人访问</title><link>https://itest2025.pages.dev/the-great-wiki-content_144/</link><pubDate>Thu, 01 Aug 2024 23:36:13 +0800</pubDate><guid>https://itest2025.pages.dev/the-great-wiki-content_144/</guid><description>&lt;p>之前翻译了一版我认为是史上最全的测试 wiki，并把内容作为测试知识库放到了 itest.info，也就是重定向学院的网站上面。&lt;/p>
&lt;p>一段时间过去了，前几天我偶然看了一下百度统计里的结果，发现这些 wiki 内容竟然已经有自然流量了，尽管翻译的效果不是很好，但有访问就是好事，可以重点去优先那些大家都在搜索的页面了。&lt;/p>
&lt;h2 id="热门内容">热门内容&lt;/h2>
&lt;p>我稍微统计了一下，热门的测试知识点有下面几个。&lt;/p>
&lt;h3 id="测试规范测试规格说明书">测试规范(测试规格说明书)&lt;/h3>
&lt;p>可能是这个条目的英文把大家给迷惑了，英文叫做 test specification，直接翻译过来就是测试规范。&lt;/p>
&lt;p>大家在工作中遇到一些质量管理的问题时总会想着去学一下别人是怎么做的，有没有好的管理流程和质量规范。&lt;/p>
&lt;p>但这个条目的测试规范不是我们所理解的在测试里的准入和准出标准，或者跟流程相关的内容，这里的测试规范实际上是测试规格说明书。它包含下面这些内容。&lt;/p>
&lt;ul>
&lt;li>测试范围：明确定义需要测试和不需要测试的内容，确保测试的重点和效率。&lt;/li>
&lt;li>测试目标：概述测试的目标和目的，提供方向和成功标准。&lt;/li>
&lt;li>测试标准：指定通过/不通过的标准，包括进入和退出条件。&lt;/li>
&lt;li>测试环境：描述进行测试时的硬件、软件、网络配置和其他条件。&lt;/li>
&lt;li>测试用例：详细描述每个测试，包括步骤、预期结果和测试数据。&lt;/li>
&lt;li>可追溯矩阵：将测试用例与需求关联，确保覆盖和责任。&lt;/li>
&lt;li>测试交付物：列出测试过程的输出，如报告、日志和缺陷摘要。&lt;/li>
&lt;li>资源规划：识别测试所需的人员、工具和其他资源。&lt;/li>
&lt;li>进度和估算：提供测试准备、执行和评估的时间表。&lt;/li>
&lt;li>风险分析：评估测试过程中潜在的风险，并制定缓解策略。&lt;/li>
&lt;li>假设和依赖：记录进行测试所需的前提条件或假设。&lt;/li>
&lt;/ul>
&lt;p>这更像是描述测试管理理念的终极文档。包含了测试范围，测试规划，测试环境和测试产出等，是个纲领性质的东西。&lt;/p>
&lt;p>如果大家有搭建测试团队或者团队管理的需求，可以参考这个条目的内容，我已经重新翻译过了，尽量做到英文内容的忠实还原。&lt;/p>
&lt;p>地址: &lt;a href="https://www.itest.info/wiki/test-specification" target="_blank" rel="noopener noreffer ">https://www.itest.info/wiki/test-specification&lt;/a>&lt;/p>
&lt;h3 id="故障注入测试">故障注入测试&lt;/h3>
&lt;p>故障注入测试(FIT)是一种测试人员故意向系统引入错误,以评估其健壮性和错误处理能力的方法。这种技术通过模拟故障来观察系统在意外情况下的表现,确保它能够优雅地处理和恢复故障。&lt;/p>
&lt;p>这个条目也是大家热搜的内容，这是因为近年来故障注入的好处越来越被更多人知晓。&lt;/p>
&lt;p>故障注入测试的主要好处包括:&lt;/p>
&lt;p>• 增强系统稳定性:通过故意引入故障,可以在不利条件下测试系统,确保它能优雅地处理意外情况。&lt;/p>
&lt;p>• 提高容错能力:它验证了故障处理机制的有效性,从而打造出更具弹性的软件。&lt;/p>
&lt;p>• 系统加固:让系统暴露于故障中有助于识别并加强薄弱环节,降低生产环境中出现故障的可能性。&lt;/p>
&lt;p>• 提升可靠性:通过确认系统在故障条件下的正确行为,整体可靠性得到提高。&lt;/p>
&lt;p>• 改善风险管理:有助于识别潜在风险及其影响,从而制定更好的缓解策略。&lt;/p>
&lt;p>• 主动发现问题:故障注入测试能发现常规测试可能无法发现的隐藏 bug。&lt;/p>
&lt;p>• 验证监控和告警:确保监控系统能按预期检测并提醒故障。&lt;/p>
&lt;p>• 符合行业标准:某些行业要求验证容错能力,这可以通过故障注入来实现。&lt;/p>
&lt;p>• 节省成本:早期发现故障可以节省软件开发生命周期中与停机时间和后期修复 bug 相关的成本。&lt;/p>
&lt;p>• 洞察系统行为:它提供了对系统在压力下行为的深入理解,可以指导未来的开发和测试工作。&lt;/p>
&lt;p>该条目除了解释了故障注入的原理和概念，还推荐了一些常用工具。&lt;/p>
&lt;p>地址: &lt;a href="https://www.itest.info/wiki/fault-injection-testing" target="_blank" rel="noopener noreffer ">https://www.itest.info/wiki/fault-injection-testing&lt;/a>&lt;/p>
&lt;h3 id="基准测试">基准测试&lt;/h3>
&lt;p>这是在性能测试中经常引入的概念。&lt;/p>
&lt;p>我们可以在某个时间点以当时系统的性能作为基准，然后每次发布之前都去做一次性能测试，从而对比系统的性能有没有劣化，如果有的话则要进行修改和优化。&lt;/p>
&lt;p>地址: &lt;a href="https://www.itest.info/wiki/baseline-testing" target="_blank" rel="noopener noreffer ">https://www.itest.info/wiki/baseline-testing&lt;/a>&lt;/p>
&lt;h3 id="背靠背测试">背靠背测试&lt;/h3>
&lt;p>这个我确实不知道，之前也没听说过。&lt;/p>
&lt;p>背靠背测试（Back-to-back testing）是一种软件测试方法，它涉及到比较两个不同版本（通常是现有系统）的输出，以验证它们在一系列测试用例下的行为是否相同。这种测试方法在将遗留系统迁移到新平台或重构代码时特别有用，以确保新系统的行为与旧系统一致，同时避免引入回归错误。&lt;/p></description></item><item><title>我终于肝完了playwright的中文站</title><link>https://itest2025.pages.dev/i-finished-the-playwright-site_143/</link><pubDate>Thu, 18 Jul 2024 12:48:10 +0800</pubDate><guid>https://itest2025.pages.dev/i-finished-the-playwright-site_143/</guid><description>&lt;p>经过两个星期的努力，我终于肝完了 playwright 中文站的主要内容。&lt;/p>
&lt;p>站点地址 &lt;a href="playwright.itest.info" rel="">playwright.itest.info&lt;/a>&lt;/p>
&lt;p>后面我会把精力放到 playwright 的原创教程上去，预计也会在这两周发布吧。&lt;/p>
&lt;p>这次技术选型的时候遇到了一些问题，肝到快结束才发现 zola 并不支持中文索引的能力，也就是说搜索功能就没办法实现了。&lt;/p>
&lt;p>所以我搞了个全部教程的 archive 页面，大家需要搜索的话可以可以在全部教程页面进行搜索。&lt;/p>
&lt;p>目前全站应该更新了 90 篇左右的资料，涉及到 playwright 的基础，框架设计，api 测试以及各种最佳实践，技术含量还是不错的。&lt;/p>
&lt;p>在整理和翻译教程的过程中，我自己对 playwright 也有了比较深入的了解，对目前正在写的 playwright 教程的帮助很大，一些实现和配置目前基本上手到擒来，所以建站的过程也是一个非常好的学习的过程，付出和收获是对等的。&lt;/p>
&lt;p>另外一些文章翻译的质量不是很高，比如有一篇关于 playwright 视觉测试的教程，英文内容本来是很好的，不过翻译过来之后就显得牛头不对马嘴，我自己理解起来都觉得费劲，所以下周我决定抽时间把这篇教程本地化一下，做一个高质量的视频教程。&lt;/p>
&lt;p>因为写了很多爬虫的关系，站点的内容应该会持续更新下去的，后面有机会的话看能不能把之前 selenium 的内容也整理出来，做一个 selenium 的中文教程站，毕竟现在对静态网站的制作也是非常得心应手了。&lt;/p>
&lt;h2 id="推荐内容">推荐内容&lt;/h2>
&lt;p>我精选了一些我认为比较好的内容，我把教程的名字放在这里，感兴趣的内容大家自行去所有教程的存档页面搜索就可以了。&lt;/p>
&lt;ul>
&lt;li>如何让 playwright 运行的更快: 非常实用，特别是你的自动化代码足够多的话&lt;/li>
&lt;li>如何使用 Playwright 监控 JavaScript 控制台日志和异常: 监听页面的&lt;code>pageerror&lt;/code>事件，对实时前端页面监控很有意义&lt;/li>
&lt;li>挖掘 playwright 的隐藏宝藏: 各种最佳实践，量大管饱&lt;/li>
&lt;li>全面的基于 Playwright 的 ui 自动化测试，使用页面对象模型的模块化框架: 用 playwright+po 实现测试框架&lt;/li>
&lt;li>Playwright: 如何正确使用 fixture 构建页面对象: 使用 fixture 来组织 po，这个实践的工程化水平很高，推荐&lt;/li>
&lt;li>使用 playwrigth api 测试: 用 playwright 做 api 测试，这一系列教程确实写的很好&lt;/li>
&lt;li>如何使用组件来重构 playwright 的代码: 在元素之上，page 之下还可以抽象 1 个组件层，代码思想很好&lt;/li>
&lt;/ul>
&lt;h2 id="最后">最后&lt;/h2>
&lt;p>创作内容和维护内容不易，希望这个站点对大家会有所帮助。&lt;/p></description></item><item><title>我肝了一个playwright的教程及资讯站</title><link>https://itest2025.pages.dev/i-build-a-brand-new-playwright-tutorial-site_142/</link><pubDate>Fri, 12 Jul 2024 01:26:34 +0800</pubDate><guid>https://itest2025.pages.dev/i-build-a-brand-new-playwright-tutorial-site_142/</guid><description>&lt;p>我花了一周的时间肝了个 playwright 的教程站，域名是 playwright.itest.info，事情的起因还要从 1 个油管视频说起。&lt;/p>
&lt;p>我从那个视频上了解到，1 个做 AI 硬件的公司把 playwright 的自动化操作功能跟 AI 硬件结合了起来，融了 2000 万美金。&lt;/p>
&lt;p>&lt;strong>这让我大受震撼。&lt;/strong>&lt;/p>
&lt;p>首先我没想到这么高大上的产品竟然用的是如此朴实无华的技术，更让我感到惊讶的是这家公司选择的工具是 playwright，而不是自动化届的老大哥 selenium。毕竟如果从纯技术的角度上考虑，selenium 是在原生浏览器上跑的，它的兼容性其实更好。&lt;/p>
&lt;p>看来 playwright 确实是有它独到之处的。&lt;/p>
&lt;p>这也让我陷入了思考，可能大部分人学习 ui 自动化工具的初衷不是测试，而是赚钱。&lt;/p>
&lt;p>毫无疑问，在 2024 年的今天，playwright 是最有钱景的自动化测试工具，当然了，是金钱的钱。&lt;/p>
&lt;p>不过我发现 playwright 的中文资料非常有限，所以我和虫师创建了这个站点，希望可以一直维护下去吧。&lt;/p>
&lt;h2 id="关于目前的进度">关于目前的进度&lt;/h2>
&lt;p>目前上线的版本有大概 24 篇左右的原创加翻译的资料。&lt;/p>
&lt;p>我写了个爬虫去关注 playwright 的最新进展，也爬了 120 多篇非常优秀的英文教程。后面我会持续更新，这样一来资料会更加丰富一些。&lt;/p>
&lt;p>除了翻译，我目前主要的精力是放在了原创教程上。在 playwright 可以找到的资料里，大部分的内容都是和自动化测试相关的，这些内容很好，不过我想如果我的教程把关注的重点放在爬虫和自动操作上，那么是不是受众会更广泛一些。&lt;/p>
&lt;p>爬虫可以获取数据，在 AI 大行其道的今天，数据是各种大语言模型的基础。&lt;/p>
&lt;p>自动化操作可以提升工作效率，在提效这个赛道上，永远是有商机可以挖掘的。&lt;/p>
&lt;p>所以我开始肝一套 playwright 的中文教程，可以写的素材相当的丰富，既然要写很多，那么不如就写一本书吧。&lt;/p>
&lt;p>目前书籍的进度大概是这样的，typescript 版本应该完成了有 50%了，等肝完 ts 的版本，再花点时间把 python 版本结束掉。&lt;/p>
&lt;p>关于网站内容的质量，翻译的文章，我尽量翻译的通俗易通，然而水平毕竟有限，一些翻译的资料中会出现比较神奇的表达，希望大家可以多多指正。&lt;/p>
&lt;p>原创的文章我会尽量做到深入浅出，让大家容易理解。&lt;/p>
&lt;p>另外 playwright 的 api 变化很快，之前已经过期的内容我这边会人肉过滤掉，尽量保证呈现出的内容是最新的，一些翻译文章里我会加入自己的看法，明目张胆的夹带私货了。&lt;/p>
&lt;p>结束掉文本资料的部分后，我会根据教程的内容录制一套 playwright 的视频教程，也会把重点放在爬虫和自动化操作上。&lt;/p></description></item><item><title>我怎么可能在每次迭代中测试所有内容？</title><link>https://itest2025.pages.dev/how-can-i-possibly-test-all-the-stuff-every-iteration_141/</link><pubDate>Fri, 05 Jul 2024 02:38:51 +0800</pubDate><guid>https://itest2025.pages.dev/how-can-i-possibly-test-all-the-stuff-every-iteration_141/</guid><description>&lt;p>这是一个常见的经典问题，答案见仁见智，下面是一种比较普遍的观点。&lt;/p>
&lt;p>这取决于你对“所有内容”的定义。&lt;/p>
&lt;p>你可能指的是对整个应用程序进行全面的回归测试，包括所有功能、负面路径、边缘情况等。你说得对——除了一些非常小的产品外，你不可能在每次迭代中做所有这些事情。&lt;/p>
&lt;p>但事实是：你不应该在每次迭代中进行这种全面的测试，你不应该觉得有必要这样做，也不应该被要求这样做。&lt;/p>
&lt;p>迭代是敏捷软件开发中的概念——在软件长期开发并在最后一次性交付的模式中，“迭代”这个词没有意义。&lt;/p>
&lt;p>迭代开发软件的全部意义在于引入小而有价值的变更并将其交付给用户。由于变更很小，产品的大部分与之前相同，因此不需要再次进行全面验证。假定它在过去已经被测试过了。&lt;/p>
&lt;p>但也许变更的范围比看起来更广，或者它们在程序的其他部分引入了意想不到的后果。&lt;/p>
&lt;p>这些都是合理的担忧。然而，开发人员引入的大多数变更并不影响整个系统，你不应该默认认为它们可能会这样。&lt;/p>
&lt;p>值得补充的是，如果团队经常发现自己处于变更波及整个系统并需要全面测试的情况，那就是存在更深层次问题需要解决的迹象。&lt;/p>
&lt;p>&lt;strong>这可能意味着系统架构有问题，团队应该认真考虑偿还技术债务。&lt;/strong> 或者也许软件更像是一个原型、概念验证，而不是解决方案。在这种情况下，在团队更好地理解需求和利益相关者可以批准一种方法之前，可能不需要全面测试。许多测试人员在这种环境中工作时感到困难。他们将自己的角色视为可能存在缺陷的软件与用户之间的最后一道防线。他们对用户可能得到未经全面测试和验证的软件感到不安。&lt;/p>
&lt;p>如果你是其中之一，你需要理解这种风险是经过计算的，也是重视短迭代和频繁交付的软件开发模式固有的。这些想法相互强化。产品没有经过全面测试，这意味着可能存在用户遇到前未被发现的错误、失误和问题。但正因为产品没有经过全面测试，它可以更快地发布。当团队了解到软件中的问题时，他们可以修复并进行新的发布。&lt;/p>
&lt;p>如果这个论点不能说服你，我建议你尝试一下。承认你个人并不认同这种方法，但如果这是团队想做的，就不要阻止它。在几次发布中保留你的保留意见，看看会发生什么。你可能会发现产品的质量实际上并没有明显低于以前。如果结果显示质量确实下降了，那么你将有坚实的经验数据向团队展示。大多数人会同意这些结果是不理想的，需要某种改变。&lt;/p>
&lt;p>&lt;strong>最后，你不应该被要求在每次迭代中进行全面测试。&lt;/strong>&lt;/p>
&lt;p>就我个人而言，我在这方面没有太多经验——我从未在试图这样做的团队中工作过。虽然我不能说在这种情况下什么方法可能有效，但我猜想首先要做的是理解这种要求背后的立场。也许需要教导利益相关者迭代的权衡；或者也许团队需要采用更谨慎的软件开发模式，以牺牲速度为代价。&lt;/p>
&lt;p>我还没有提到自动化，它对以上所有观点都有贡献。作为测试人员，你的工作应该得到自动化的支持，至少每天运行一次，并提供关于产品状态的最新可靠信息。虽然自动化有限制，不能检查所有内容，但它可以检查一些行为。&lt;/p>
&lt;p>你应该了解在你的项目中自动化能做什么和不能做什么，这样你就可以对哪些功能和路径不需要花费太多时间做出明智的决定。你应该对自动化结果有信心。当有人要求你反复执行特定测试时，你可以将其视为讨论花些时间改进自动化的机会。&lt;/p>
&lt;p>在这篇文章的开头，我说过答案取决于“所有内容”的含义。它可能意味着全面测试，这是我到目前为止关注的重点。但它也可能意味着迭代期间发生变化的所有内容。&lt;/p>
&lt;p>确实会发生开发人员完成了如此多的工作，以至于团队中的测试人员应接不暇的情况。不久前我就遇到过这种情况，对我们有效的方法是推动整个团队的所有权。大多数开发任务不需要专门的测试人员来测试或验证——它们只需要由没有编写代码的人来测试。在我们的案例中，我们所要做的就是在站会上表明问题，团队其他成员很快就想出了解决方案——一些开发人员应该停止接受新工作，而是专注于测试已经完成的内容。为每个任务明确列出完成的定义和验收标准肯定会有所帮助。&lt;/p>
&lt;p>然而，处理这个问题的最好方法是主动避免它。总的来说，作为测试人员，尽量尽快测试变更。不要等到迭代结束。如果团队实践基于 PR 的工作流程，你可以尝试在代码合并之前测试创建的开发版本。让 CI 系统为每个 PR 构建工件会有所帮助，但另一种选择是在你自己的机器上设置构建环境。对于特别大的功能，在代码编写之前给开发人员反馈，并鼓励早期分享代码和想法。每日站会（如果有的话）是表达这种对话愿望的好机会。你也可以考虑主动联系开发人员并私下讨论。&lt;/p>
&lt;p>可能会发生尽管使用了上述所有建议，测试人员在冲刺结束时仍然常常工作过载的情况。根据我的经验，这种情况更有可能发生在试图严格遵循僵化的 Scrum 结构的团队中，迭代周期为一到两周。由于工作在冲刺开始时分配，会有一段时间开发人员专注于他们的工作，而测试人员没有太多事情可做。这种动态在冲刺结束时会发生变化。在这种情况下，我强烈建议在回顾会议上提出这个问题。大多数人会同意这是一个长期不可持续的系统性问题。像往常一样，对于自组织团队来说，由团队自己想出解决方案。只要记住监控情况，保留数据，如果没有明显改善，就再次提出这个话题。&lt;/p></description></item><item><title>人人都要会的工具之--curl</title><link>https://itest2025.pages.dev/you-should-learn-curl_140/</link><pubDate>Sun, 30 Jun 2024 13:41:44 +0800</pubDate><guid>https://itest2025.pages.dev/you-should-learn-curl_140/</guid><description>&lt;p>想象一下，你需要在 CI/CD 流水线中进行一些 API 调用，以进行身份验证、获取数据、发送简单请求或从网络 URI 下载文件。&lt;/p>
&lt;p>你会选择哪些工具？&lt;/p>
&lt;p>答案可能是使用像 Postman 这样的著名 API 测试工具。&lt;/p>
&lt;p>不过一般情况下只需要用 curl 就够了。而且很多年之前 curl 就已经跨平台了，我记得 windows 上也是可以使用的。&lt;/p>
&lt;p>所以我认为，写一篇关于 CURL 的文章是值得的。&lt;/p>
&lt;p>我将这篇文章的范围缩小到 HTTP/HTTPS，因为写一篇关于 curl 全部功能的文章会花费好几天。你很快就会明白我为什么这么说。&lt;/p>
&lt;h3 id="curl-是做什么的">curl 是做什么的？&lt;/h3>
&lt;p>Curl，全称为“Client URL”，是一种命令行工具，用于通过 URL 传输数据。curl 还用于汽车、电视机、路由器、打印机、音频设备、手机、平板电脑、医疗设备、机顶盒、电脑游戏、媒体播放器等。&lt;/p>
&lt;p>Curl 是超过二百亿个软件应用程序中的互联网传输引擎。&lt;/p>
&lt;p>几乎每个使用互联网的人每天都会使用 Curl。&lt;/p>
&lt;p>你能想象它支持多少协议吗？&lt;/p>
&lt;pre tabindex="0">&lt;code>DICT、FILE、FTP、FTPS、GOPHER、GOPHERS、HTTP、HTTPS、IMAP、IMAPS、LDAP、LDAPS、MQTT、POP3、POP3S、RTMP、RTMPS、RTSP、SCP、SFTP、SMB、SMBS、SMTP、SMTPS、TELNET、TFTP、WS 和 WSS。curl 支持 TLS 证书、HTTP POST、HTTP PUT、FTP 上传、基于 HTTP 表单的上传、代理（SOCKS4、SOCKS5、HTTP 和 HTTPS）、HTTP/2、HTTP/3、cookie、用户+密码认证（Basic、Plain、Digest、CRAM-MD5、SCRAM-SHA、NTLM、Negotiate、Kerberos、Bearer tokens 和 AWS Sigv4）、文件传输恢复、代理隧道、HSTS、Alt-Svc、unix 域套接字、HTTP 压缩（gzip、brotli 和 zstd）、etags、并行传输、DNS-over-HTTPS，等等。
&lt;/code>&lt;/pre>&lt;h3 id="为什么选择-curl因为它简单无处不在且灵活">为什么选择 Curl？因为它简单、无处不在且灵活！&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>简单易用&lt;/strong>：Curl 就像 API 测试的瑞士军刀。它简单、直接，不需要编程博士学位就能使用。只需输入几个命令，按下回车键，瞧，你就像专业人士一样发送或测试 API！&lt;/li>
&lt;li>&lt;strong>随处可用&lt;/strong>：无论你是在 Mac、PC 还是 Linux 机器上，Curl 都能为你服务。不需要为不同平台使用不同的工具，Curl 与每个人都相处得很好。&lt;/li>
&lt;/ul>
&lt;h3 id="让我们开始发送-api-请求吧">让我们开始发送 API 请求吧&lt;/h3>
&lt;p>首先确保你已经在电脑上安装了 Curl。别担心，这就像下载一个应用程序或使用包管理器一样简单。安装完成后，你就准备好了！&lt;/p></description></item><item><title>进行测试前必须问的三个基本问题</title><link>https://itest2025.pages.dev/three-questions-you-should-ask-before-start-testing_138/</link><pubDate>Sun, 30 Jun 2024 13:31:43 +0800</pubDate><guid>https://itest2025.pages.dev/three-questions-you-should-ask-before-start-testing_138/</guid><description>&lt;p>对于测试同学来说，为了详细了解背景，你必须在合适的时间向合适的人提出合适的问题。如果没有背景信息，你可能无法设计出有效的测试场景。&lt;/p>
&lt;p>在没有有效测试场景的情况下，你可能无法为团队提供有价值的反馈，而这是质量保证人员（QA）所期望的。最终，团队将对测试过程失去信心，产品负责人可能会对软件质量提出质疑。因此，提问不仅对深入了解背景至关重要，还对确保交付的软件符合每个利益相关者的期望至关重要。&lt;/p>
&lt;p>我的测试方法总是从向参与决策过程的不同人员提问开始。这使我能够深入了解变更的原因。&lt;/p>
&lt;p>我向技术主管和架构师提问，以了解他们如何规划代码变更以适应新的业务需求。&lt;/p>
&lt;p>我向产品负责人提问，了解计划中的变更将如何解决用户的问题。这让我了解客户面临的问题。&lt;/p>
&lt;p>在这篇文章里，我将分享在开始测试之前你应该问的这些基本问题。&lt;/p>
&lt;h2 id="目录">目录&lt;/h2>
&lt;ul>
&lt;li>在测试之前 QA 应问的基本问题
&lt;ul>
&lt;li>什么变了？&lt;/li>
&lt;li>为什么变了？&lt;/li>
&lt;li>影响在哪里？&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="什么变了">什么变了？&lt;/h3>
&lt;p>你必须首先问产品负责人：计划做哪些变更？为了详细了解，可以进一步拆分为更小的问题，比如：&lt;/p>
&lt;ul>
&lt;li>具体修改了哪些组件？（例如数据库、后端、前端或其他系统服务）&lt;/li>
&lt;li>哪些改动影响了系统的状态路径？&lt;/li>
&lt;li>是否引入了任何新的终端用户功能？&lt;/li>
&lt;li>这些变更为终端用户解决了什么问题？&lt;/li>
&lt;/ul>
&lt;h3 id="为什么变了">为什么变了？&lt;/h3>
&lt;p>同样重要的是了解变更背后的理由。你应该能够理解客户面临的问题，这些问题促成了这一决策。如果不了解用户的痛点，作为 QA，你无法确保变更是否有效地解决了这些问题。为了进一步拆分这个问题，你可以问以下跟进问题：&lt;/p>
&lt;ul>
&lt;li>在此次变更之前，利益相关者具体遇到了哪些问题或痛点？&lt;/li>
&lt;li>实施此变更预期带来的好处或改进是什么？&lt;/li>
&lt;/ul>
&lt;h3 id="影响在哪里">影响在哪里？&lt;/h3>
&lt;p>“影响在哪里？”是有效测试计划中最重要的问题之一。你应该尽早识别受影响的区域。通过定位受影响的区域，你可以设计出全面覆盖这些变更的测试场景。&lt;/p>
&lt;p>总之，作为 QA，你应该具备设计有效测试场景所需的所有知识。你可以根据你的系统和情况调整这些问题，但你必须尽早得到这些问题的答案，以确保你的测试高效且有效。&lt;/p></description></item><item><title>2024年最值得学习的自动化测试工具</title><link>https://itest2025.pages.dev/best-ui-automation-tool-you-can-learn-in-2024_137/</link><pubDate>Sun, 30 Jun 2024 13:31:24 +0800</pubDate><guid>https://itest2025.pages.dev/best-ui-automation-tool-you-can-learn-in-2024_137/</guid><description>&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="https://img.ethanhan.cc/file/e8bea6744d9a0e2895ad1.png"
 data-srcset="https://img.ethanhan.cc/file/e8bea6744d9a0e2895ad1.png, https://img.ethanhan.cc/file/e8bea6744d9a0e2895ad1.png 1.5x, https://img.ethanhan.cc/file/e8bea6744d9a0e2895ad1.png 2x"
 data-sizes="auto"
 alt="https://img.ethanhan.cc/file/e8bea6744d9a0e2895ad1.png"
 title="https://img.ethanhan.cc/file/e8bea6744d9a0e2895ad1.png" />&lt;/p>
&lt;p>这是一张 主流的 3 大自动化测试工具每周 npm 下载量的统计对比。&lt;/p>
&lt;p>npm 是 nodejs 的依赖管理工具，所以这张图可以看出在 javascript 这门语言领域，每种工具使用人数的变化情况。&lt;/p>
&lt;p>看图说话，从趋势图上看，3 大工具在 2024 年的命运大相径庭。&lt;/p>
&lt;ul>
&lt;li>cypress 增速明显放缓，考虑到 cypress 只有 javascript(typescript)版本，所以可以比较确定的是，cypress 的用户数已经停滞不前了;&lt;/li>
&lt;li>selenium 的用户数开始退坡，考虑到 selenium 有 java 和 python 的版本，我们只能说，在前端领域用 selenium 的人变少了；&lt;/li>
&lt;li>playwright 的用户数急剧增加，特别是在 2023 年的下半年，曲线陡然上升，可能新用户和从 selenium 转投过来的用户带来了这次爆发式的增长；&lt;/li>
&lt;/ul>
&lt;p>结合上次我做的一个关于如何使用 playwright 狂赚美刀的视频，结论很明显了。&lt;/p>
&lt;p>2024 年最值得学习的自动化测试框架就是 playwright 了。&lt;/p>
&lt;h3 id="为什么是-playwright">为什么是 playwright&lt;/h3>
&lt;ul>
&lt;li>首先 playwright 有个好爹，微软出品，如果项目不被砍的话，更新和维护的力度是可以得到保证的；&lt;/li>
&lt;li>其次 playwright 的入门比 selenium 其实是要简单不少的，适合初学者；&lt;/li>
&lt;li>另外 playwright 的多种特性保证了初学者可以用相对简单的代码写出可以稳定运行的测试用例，做过自动化测试的同学肯定知道，对于用例来说，稳定运行意味着什么；&lt;/li>
&lt;li>最后，也就是最为重要的一点就是，大部分的在其他测试层面，比如单元测试或者接口测试，搞不定的用例或者操作，都可以用 ui 自动化工具来实现。也就是重剑无锋，大巧不工。这就意味着，除了测试，ui 自动化工具的应用范围其实非常广，学的好真的可以赚钱。还是我上次视频里提到的 ai 设备之耻——rabbit R1 的例子，既然 ai 现阶段没办法实现自动打车或者定外卖的功能，那就让 playwright 去实现吧；&lt;/li>
&lt;/ul>
&lt;h3 id="如何学习">如何学习&lt;/h3>
&lt;ul>
&lt;li>首先建议初学者使用 typescript 的版本，原因很简单，ts 版本配合编辑器会带来完备的代码提示，对初学者来说很可能带来极大的帮助；&lt;/li>
&lt;li>建议使用默认的带 testsuit 也就是测试套件的版本进行学习，因为这个版本自带断言，测试的目的就是为了各种断言；&lt;/li>
&lt;li>官方文档是最好的学习资料，因为官方文档更新非常及时，毕竟 playwright 还在密集开发中，经常隔一段时间就会带来一些新特性；&lt;/li>
&lt;/ul>
&lt;h3 id="开坑">开坑&lt;/h3>
&lt;p>一转眼就下半年了，准备开个 playwright 教程的坑，其实之前一直想做的，但无奈 playwright 更新太快，所以就这么搁置起来了。&lt;/p></description></item><item><title>当今爆火的RPA其实就是自动化测试</title><link>https://itest2025.pages.dev/rpa-is-the-future_136/</link><pubDate>Fri, 31 May 2024 00:46:06 +0800</pubDate><guid>https://itest2025.pages.dev/rpa-is-the-future_136/</guid><description>&lt;p>最近有机会看到了 RPA 在实际工作中的重度应用，深刻感受到了自动化的强大实力，以后的应用前景时完全可期的。&lt;/p>
&lt;h3 id="rpa-robotic-process-automation-简介">RPA (Robotic Process Automation) 简介&lt;/h3>
&lt;p>Robotic Process Automation (RPA) 是一种技术，使用软件机器人（或称“机器人”）来模拟人类在数字系统中执行的任务。RPA 可以自动执行高度重复性和规则驱动的任务，这些任务通常需要人类操作来完成。RPA 通过与现有系统和应用程序进行交互，无需对底层系统进行改动即可实现自动化。&lt;/p>
&lt;p>本质上讲 RPA 就是自动化脚本，好的 RPA 脚本可以结合页面自动化，桌面自动化（操作桌面级的应用，比如微信电脑版）以及接口自动化，用自动化去替代人类操作。与自动化用例不同的是 RPA 里面的断言不是必须的，自动化用例的目的是验证，也就是实现断言；RPA 的目的是高效率低错误率的完成工作，比如给微信群群发消息之类的。从本质上讲，RPA 就是一种自动化测试，一种理论上可以省略断言的自动化测试。但实际上如果想要 RPA 脚本长时间稳定运行，断言也是必不可少的，当断言条件不满足时，RPA 脚本应该进行无损的容错处理，这样程序才会更加健壮。&lt;/p>
&lt;p>我们可以把 RPA 看成是综合性的自动化测试，也就是设备端的自动化。比如下面这个 RPA 场景：&lt;/p>
&lt;ul>
&lt;li>第一步调用 api 自动获取一些最新的资讯，用 ai 做改写。这是接口自动化；&lt;/li>
&lt;li>在网页端把改写后的资讯发布到某资讯网站。这是网页自动化；&lt;/li>
&lt;li>在桌面端把新发布的资讯自动发送到微信群里。这是桌面应用的自动化；&lt;/li>
&lt;li>最后调用设备农场的手机，在所有的手机上给这篇资讯点赞。这是手机自动化；&lt;/li>
&lt;/ul>
&lt;p>因为测试金字塔的关系，我们会将自动化测试分为 ui，接口以及单元测试，这些测试之间天然隔绝，相对独立，所以想象的空间其实不大，只是代替人工进行验证而已。其实这是没问题的，毕竟每种测试代表的主体不一样，分开使得测试用例变得更加容易编写和维护。&lt;/p>
&lt;p>但 RPA 的主体是机器人，他的目标就是代替人类办公过程中的大量的重复性操作，多种自动化技术和 ai 技术的结合才可以达到这种效果，这么一想其实结论很明显了，RPA 就是自动化测试，是所有种类自动化测试的终极合体形态（有点中二了）。&lt;/p>
&lt;h3 id="rpa-的真实应用案例">RPA 的真实应用案例&lt;/h3>
&lt;h4 id="我看到的实际例子">我看到的实际例子&lt;/h4>
&lt;p>我看到的实际应用其实是在电商上，有朋友开了一家跨境电商的公司，我去那边拜访的时候正好遇到他在研究国内某家 RPA 厂商(最近融资了 1 亿美金)的方案。因为电商的重复性劳动是非常多的，所以 RPA 的应用场景相当的广阔。最震撼的例子是退货退款的例子。&lt;/p>
&lt;p>一些商家平时的销量比较大，但众口难调，出的单越多，退货的比例就越高。&lt;/p>
&lt;p>每个退货单其实逻辑上不需要审核，只要退就好了，但是流程上还是要系统在系统上做一些操作的，因为处理有时效性，需要在短时间内快速解决掉，用人工做的话长时间下来工人会感到疲惫，另外操作的效率也相对较低。这时候 RPA 就可以很好的满足长时间高效率低错误率的重复性工作，据说一到两天可以处理几万个订单，这其中节省的人力真的是非常可观的。&lt;/p>
&lt;p>另外还有很多跨境电商公司用 rpa 做批量上下商品等操作，也是非常杀手级的应用。&lt;/p>
&lt;p>其他的 RPA 应用领域如下。&lt;/p>
&lt;h4 id="1-银行与金融">1. 银行与金融&lt;/h4>
&lt;p>应用场景: 贷款处理、账户开立、反洗钱监控、客户服务。
案例: 某大型银行采用 RPA 自动化贷款处理过程，包括贷款申请数据的验证和审批，从而将处理时间从几天缩短到几小时。此举不仅提高了效率，还减少了人工处理中的错误。&lt;/p></description></item><item><title>活久见!性能测试工具Gatling支持javascript了</title><link>https://itest2025.pages.dev/gatling-support-javascript_135/</link><pubDate>Fri, 31 May 2024 00:44:33 +0800</pubDate><guid>https://itest2025.pages.dev/gatling-support-javascript_135/</guid><description>&lt;p>很久之前给大家介绍过一款性能测试工具 Gatling。该工具有开源版本和企业版本，一般情况下我们只关注开源版本，毕竟企业版还是非常贵的。之前的开源版本中，测试脚本是用 Scala 写的，这门语言有点高阶，写起来不顺手，不过 2024 年的今天，情况有所改变。&lt;/p>
&lt;p>Gatling 团队 5 月 23 日宣布，他们已为 Gatling 负载测试新增了 JavaScript 和 TypeScript SDK。这个新的 JavaScript SDK 是 npm 库中第一个企业级负载测试工具，使更多的开发者能够使用他们熟悉的编程语言进行 Gatling 负载测试。&lt;/p>
&lt;p>自从 Gatling 推出以来，它一直是一个基于 Java 虚拟机（JVM）的开发工具，专注于负载测试。现在，开发者可以使用 Gatling JavaScript SDK 编写负载测试，并将测试编译在 JVM 上运行。这结合了脚本语言的灵活性和多线程等必要的性能特性。&lt;/p>
&lt;p>最重要的是，使用 Gatling 不再局限于 Java 开发者。通过 npm 安装命令设置项目，熟悉的语法使 JavaScript 开发者可以轻松上手，同时拥有 Gatling 引擎的强大功能来运行负载测试。&lt;/p>
&lt;h3 id="为什么-gatling-团队开发了-javascript-和-typescript-sdk">为什么 Gatling 团队开发了 JavaScript 和 TypeScript SDK？&lt;/h3>
&lt;p>JavaScript 生态系统的惊人发展是不可否认的。最初，它只是一个前端脚本语言。随着 NodeJS 和 TypeScript 的出现，它现在成为构建现代 Web 应用程序和 API 的全栈、类型安全的语言。根据某些估计，JavaScript 是 98%财富 500 强企业技术栈的一部分。&lt;/p>
&lt;h3 id="java-和-javascript-的历史">Java 和 JavaScript 的历史&lt;/h3>
&lt;p>Java 和 JavaScript 的历史从一开始就紧密相连。两种语言都是在硅谷开发并于 1995 年发布的。JavaScript 最初被称为 Mocha，但 Netscape 将其改名为 JavaScript，以利用 Java 日益增长的人气。&lt;/p></description></item><item><title>chatgpt 4o 制定的软件测试学习计划强到离谱</title><link>https://itest2025.pages.dev/chatgpt-4o-is-good-at-making-software-testing-study-plans_134/</link><pubDate>Fri, 17 May 2024 02:15:33 +0800</pubDate><guid>https://itest2025.pages.dev/chatgpt-4o-is-good-at-making-software-testing-study-plans_134/</guid><description>&lt;p>chatgpt4o 已经发布几天了，近期关于这个模型的各种测试评测层出不穷，不过可以看到该模型在学习教育领域潜力巨大，可以教初中生学数学，教老外学英语，教学方式新颖独特，比普通老师教的都好。&lt;/p>
&lt;p>今天正好被灰度到了 gpt4o，发现其知识库是 2023 年的，比之前的 3.5 要新不少，正好可以问一下跟测试同学自我提升相关的问题。&lt;/p>
&lt;h2 id="第一个问题-请帮我制定一个-2024-年软件测试人员的技术提升计划">第一个问题: 请帮我制定一个 2024 年软件测试人员的技术提升计划&lt;/h2>
&lt;p>制定一个 2024 年软件测试人员的技术提升计划需要考虑当前的技术趋势、行业需求以及个人的职业目标。以下是一个详细的技术提升计划：&lt;/p>
&lt;h3 id="第一季度基础夯实与工具掌握">第一季度：基础夯实与工具掌握&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>巩固基础知识&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>复习软件测试基础理论：测试生命周期、测试类型（如单元测试、集成测试、系统测试、验收测试等）。&lt;/li>
&lt;li>学习和掌握测试用例设计方法：等价类划分、边界值分析、因果图、状态转换测试等。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>工具使用&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>熟练掌握常用的测试管理工具：JIRA、TestRail、Zephyr 等。&lt;/li>
&lt;li>学习和使用版本控制工具：Git。&lt;/li>
&lt;li>学习自动化测试工具：Selenium、Appium（针对 Web 和移动应用）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>编程技能&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>学习一门编程语言，如 Python 或 Java，掌握基础语法和编程逻辑。&lt;/li>
&lt;li>编写简单的测试脚本，进行单元测试和简单的自动化测试。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="第二季度深入自动化测试与持续集成">第二季度：深入自动化测试与持续集成&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>自动化测试进阶&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>学习和掌握高级的 Selenium 用法：页面对象模型（POM）、数据驱动测试（DDT）。&lt;/li>
&lt;li>掌握移动应用测试工具 Appium 的进阶技巧。&lt;/li>
&lt;li>学习 API 测试工具：Postman、RestAssured，并掌握 API 测试的基本方法。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>持续集成/持续交付（CI/CD）&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>学习 CI/CD 基础知识和概念。&lt;/li>
&lt;li>掌握 CI/CD 工具：Jenkins、GitLab CI、CircleCI 等。&lt;/li>
&lt;li>实践在项目中配置自动化测试与 CI/CD 管道。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>测试框架&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>学习和使用测试框架：JUnit、TestNG（针对 Java），PyTest（针对 Python）。&lt;/li>
&lt;li>设计和实现端到端测试框架。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="第三季度性能测试与安全测试">第三季度：性能测试与安全测试&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>性能测试&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>学习性能测试的基础理论和方法。&lt;/li>
&lt;li>掌握性能测试工具：JMeter、LoadRunner。&lt;/li>
&lt;li>进行性能测试脚本编写和性能分析。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>安全测试&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>学习和掌握基本的安全测试知识：OWASP Top 10、常见的安全漏洞（如 SQL 注入、XSS 等）。&lt;/li>
&lt;li>使用安全测试工具：Burp Suite、OWASP ZAP。&lt;/li>
&lt;li>进行安全测试实践，识别和修复常见的安全问题。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="第四季度云测试与新技术探索">第四季度：云测试与新技术探索&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>云测试&lt;/strong>&lt;/p></description></item><item><title>AI自动化探索之gpt4与playwright</title><link>https://itest2025.pages.dev/gpt4-with-playwright_133/</link><pubDate>Fri, 17 May 2024 02:15:09 +0800</pubDate><guid>https://itest2025.pages.dev/gpt4-with-playwright_133/</guid><description>&lt;p>之前介绍过一个使用 chatgpt4 分析 dom，然后生成 puppeteer 代码进行自动化的测试工具 Taxy AI。今天发现有人推荐了一个使用 chatgpt4 生成 playwright 代码的测试工具&lt;a href="https://github.com/mayt/BrowserGPT" target="_blank" rel="noopener noreffer ">BrowserGPT&lt;/a>，稍微看了一下，原理比较简单，比较适合我们去研究一下，顺便打开思路。&lt;/p>
&lt;h3 id="演示动画">演示动画&lt;/h3>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif"
 data-srcset="https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif, https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif 1.5x, https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif 2x"
 data-sizes="auto"
 alt="https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif"
 title="https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif" />&lt;/p>
&lt;h3 id="具体使用">具体使用&lt;/h3>
&lt;p>因为我没有 chatgpt4 的 key，所以没办法直接上手使用，只能通过文档去猜测一下具体用法。&lt;/p>
&lt;p>BrowserGPT 设置了 openai 的 key 以及 start url 之后就可以在命令行里运行了，大致的使用方式是输入一些自然语言，然后 BrowserGPT 执行 AI 生成的 playwright 代码，实现自然语言自动化的功能。&lt;/p>
&lt;pre tabindex="0">&lt;code>go to hn
click on the abc article
&lt;/code>&lt;/pre>&lt;p>比如上面的一些描述就实现了去 hacknews 网站点击 abc 这篇文章的功能。&lt;/p>
&lt;h3 id="原理分析">原理分析&lt;/h3>
&lt;p>大致看了一下，执行的流程是这样的，代码在&lt;a href="https://github.com/mayt/BrowserGPT/blob/master/index.js" target="_blank" rel="noopener noreffer ">这里&lt;/a>&lt;/p>
&lt;ul>
&lt;li>获取初始化的 url，打开 chrome 浏览器，跳转到这个 url&lt;/li>
&lt;li>在命令行里启动 prompt，也就是给用户一个输入的 ui&lt;/li>
&lt;li>初始化 openai 的 api&lt;/li>
&lt;li>写个死循环，每次用户输入之后调用&lt;code>doAction&lt;/code>函数&lt;/li>
&lt;li>在&lt;code>doAction&lt;/code>函数里简化当前页面的 dom 元素&lt;/li>
&lt;li>将简化过的 dom 元素传给 chatgpt，让 gpt 根据 playwright 的示例生成代码&lt;/li>
&lt;li>执行 chatgpt 生成的代码&lt;/li>
&lt;/ul>
&lt;p>这里最有意思的部分是&lt;code>doAction&lt;/code>函数&lt;/p></description></item><item><title>破防了，我用本地运行的ai写代码，感觉它比我厉害多了</title><link>https://itest2025.pages.dev/you-must-try-llama3_132/</link><pubDate>Fri, 17 May 2024 02:09:53 +0800</pubDate><guid>https://itest2025.pages.dev/you-must-try-llama3_132/</guid><description>&lt;p>事情是这样的，前几天 facebook 发布了最新的&lt;a href="https://ai.meta.com/blog/meta-llama-3/" target="_blank" rel="noopener noreffer ">llama3 模型&lt;/a>，我第一时间就用&lt;a href="https://ollama.com/library/llama3" target="_blank" rel="noopener noreffer ">ollama&lt;/a>下载运行了一下，8b 模型的 size 很小，只有 4.6G 的样子，在我 16g 显存的台式机上，速度相当快，快到有点恍惚，现在的本地模型这么强了吗？&lt;/p>
&lt;p>本着判断本地模型唯一的标准就是让它做中英文翻译的原则，我让 llama3 翻译了几个英文句子，不意外，基本言不达意，完全不可用，然后就简单了，给它贴上不可用的标签，就在我的模型库里待着好了。&lt;/p>
&lt;p>然而几天后事情来了个 180 度大反转，我发现大家的测试结果都非常的正向，直呼这应该是目前为止最强的本地运行大模型了，有人测试了 70b 的版本，甚至把这个版本跟 gpt4 相提并论。&lt;/p>
&lt;p>这不科学呀，明明这玩意连英译中都搞不好，怎么可能评价这么高呢？&lt;/p>
&lt;p>本着试试不要钱的态度，我随便问了它几个跟自动化相关的问题，结果确实让人惊讶。&lt;/p>
&lt;p>首先 llama3 应该 95%的训练材料都是英文的( over 5% of the Llama 3 pretraining dataset consists of high-quality non-English data that covers over 30 languages)，所以中文不好无可厚非，跟其交互的时候最好用英文提问。于是我用蹩脚的英文问了它第一个问题。&lt;/p>
&lt;h3 id="第一个问题">第一个问题&lt;/h3>
&lt;p>Open a browser using selenium and go to &lt;a href="https://www.itest.info" target="_blank" rel="noopener noreffer ">www.itest.info&lt;/a>, then write a pytest assert to verify the page title should equal to 重定向科技, please give me the entile python code&lt;/p></description></item><item><title>用起来，在提交代码时实现自动化验证</title><link>https://itest2025.pages.dev/pre-commit-hook_131/</link><pubDate>Fri, 17 May 2024 02:09:39 +0800</pubDate><guid>https://itest2025.pages.dev/pre-commit-hook_131/</guid><description>&lt;p>不怕开发写 bug，就怕开发带私货，这应该是很多小伙伴都经历过的职场噩梦。目前看来一个比较好的方案就是在开发提交的时候进行一些自动化的检查，让夹带私货这件事情成本提高，从而减少这种情况的发生。&lt;/p>
&lt;h3 id="1-我们可以在代码提交时进行哪些检查">1. 我们可以在代码提交时进行哪些检查&lt;/h3>
&lt;ul>
&lt;li>Linters：Linters 是分析源代码以找到编程问题、语法错误和其他潜在问题的工具。流行的例子包括 pylint、flake8 和 pyflakes。&lt;/li>
&lt;li>代码格式化器：代码格式化器自动化根据预定义的样式约定格式化源代码的过程。示例包括 black、autopep8 和 yapf。&lt;/li>
&lt;li>自动化测试：自动化测试，如单元和集成测试，对于确保代码的功能性和质量至关重要。框架如 unittest、pytest 和 nose 允许以自动化方式编写和运行测试。&lt;/li>
&lt;li>静态代码分析：静态分析工具如 bandit（用于检测安全问题）、mypy（用于静态类型检查）和 prospector（集成了几个静态分析器）可以在不需要运行代码的情况下识别潜在问题。这些工具可以帮助找到安全漏洞、逻辑错误和可能的性能改进。&lt;/li>
&lt;/ul>
&lt;p>好的，那么……我们如何在我们的项目中应用这些测试/分析呢？别担心，这就是 Pre-commit 的用武之地。&lt;/p>
&lt;h3 id="2-pre-commit">2. Pre-commit&lt;/h3>
&lt;p>pre-commit 是一个用于管理和维护多语言 pre-commit 钩子的框架。我们称之为“钩子”的是一个脚本或一组命令，它们会自动执行。但是，我们什么时候要执行这些测试呢？&lt;/p>
&lt;p>这个工具允许我们配置一组质量测试，在运行 git 命令（commit、push 等）之前自动执行。这在某种意义上非常强大，因为每次我们修改 git 仓库时，我们都可以确保添加的代码遵循我们设定的质量规则。&lt;/p>
&lt;h4 id="21-pre-commit-安装">2.1 Pre-commit: 安装&lt;/h4>
&lt;p>要安装 pre-commit，首先执行以下命令行：&lt;/p>
&lt;pre tabindex="0">&lt;code>pip install pre-commit
&lt;/code>&lt;/pre>&lt;h4 id="22-pre-commit-仓库结构">2.2 Pre-commit: 仓库结构&lt;/h4>
&lt;p>为了配置 pre-commit，我们需要 2 个文件，这些文件必须位于根文件夹中。这些文件是.pre-commit-config.yaml 和 pyproject.toml。&lt;/p>
&lt;pre tabindex="0">&lt;code>project/
├── src/
├── .pre-commit-config.yaml
└── pyproject.toml
&lt;/code>&lt;/pre>&lt;p>.pre-commit-config.yaml：必需。配置 QA 流水线。它包含要在执行 git 命令之前执行的每个钩子。
pyproject.toml：可选。配置文件，由打包工具以及其他工具（如 linters、类型检查器等）使用。它允许更具体的钩子配置。&lt;/p>
&lt;h4 id="23-pre-commit-qa-流水线">2.3 Pre-commit: QA 流水线&lt;/h4>
&lt;p>现在，让我们看看如何修改这些文件。首先，我们将看到一个简单的.pre-commit-config.yaml 配置示例：&lt;/p></description></item><item><title>我把这个最好的test wiki全部翻译了一遍 💪</title><link>https://itest2025.pages.dev/i-translated-the-best-test-wiki_129/</link><pubDate>Sat, 30 Mar 2024 03:46:30 +0800</pubDate><guid>https://itest2025.pages.dev/i-translated-the-best-test-wiki_129/</guid><description>&lt;p>之前给大家分享过这个测试 wiki，&lt;a href="https://ray.run/wiki" target="_blank" rel="noopener noreffer ">https://ray.run/wiki&lt;/a>&lt;/p>
&lt;p>个人觉得还是非常全面好用的，不过因为是英文的，而且里面的内容不够直观，所以我直接翻译了一遍。最后的效果看&lt;a href="https://itest2025.pages.dev/wiki" rel="">这里&lt;/a>，一共翻译了 224 个主题，好在是全程自动化，效率还是比较可观的。&lt;/p>
&lt;h3 id="具体做法">具体做法&lt;/h3>
&lt;p>首先我写了个爬虫把所有的英文内容都爬了下来，这一步不难，毕竟熟练工，三下五除二就搞定了。然后就是调用&lt;a href="https://openai.com/blog/openai-api" target="_blank" rel="noopener noreffer ">openai&lt;/a>或者是&lt;a href="https://ai.google.dev" target="_blank" rel="noopener noreffer ">gemini 的 api&lt;/a>做全文翻译，但是前者太贵，后者封我号，没办法，只能去打&lt;a href="https://www.deepl.com/en/translator" target="_blank" rel="noopener noreffer ">deepl&lt;/a>的主意。&lt;/p>
&lt;p>不过 deeplx 的翻译接口经常触发限频，没办法了只能另辟蹊径。&lt;/p>
&lt;p>无奈之下只能在本地部署了个&lt;a href="https://huggingface.co/baichuan-inc/Baichuan2-7B-Chat-4bits" target="_blank" rel="noopener noreffer ">百川 2 7b&lt;/a>模型，调用百川 2 的 api 进行翻译，当然了，翻译时候尝试了很多策略，浪费了大量时间，最终才出来一个相对可用的版本。过程中发现了一些问题&lt;/p>
&lt;ul>
&lt;li>有时候显存不足导致翻译结果为空&lt;/li>
&lt;li>如果 baichuan2 翻译不了，那么它会直接返回原文，就会出现中英文混杂的情况&lt;/li>
&lt;/ul>
&lt;p>不过简单来说，翻译的结果凑合可用，而且我在 wiki 的详情页也给出了英文原文，也方便大家对照。&lt;/p>
&lt;p>爬虫和翻译完成之后就是在 itest 上加个 wiki 模块，把爬下的内容导入到数据库之后，调整一下样式就可以上线了。&lt;/p>
&lt;p>总的来说翻译很麻烦，毕竟 openai 和 gemini 的 api 都对我关上了大门，不过好在中文模型可以一战，这才做成了这个勉强可用的版本。&lt;/p>
&lt;h3 id="未来计划">未来计划&lt;/h3>
&lt;ul>
&lt;li>有精力的话完善一下翻译&lt;/li>
&lt;li>增加 wiki 搜索功能&lt;/li>
&lt;/ul></description></item><item><title>每个项目都能用上吧:使用Playwright进行图片比较测试</title><link>https://itest2025.pages.dev/use-playwright-do-comparison-test_128/</link><pubDate>Fri, 29 Mar 2024 00:58:09 +0800</pubDate><guid>https://itest2025.pages.dev/use-playwright-do-comparison-test_128/</guid><description>&lt;p>Playwright 做为新一代的浏览器 ui 自动化测试工具，有很多有意思的功能，其中截图对比就是其中一个。&lt;/p>
&lt;p>本质上讲 playwright 的图片对比测试其实就是&lt;strong>基准测试&lt;/strong>，其流程大致如下：&lt;/p>
&lt;ul>
&lt;li>先运行一遍测试用例，把代码里定义的需要截图的地方都截图，这些截图就是以后测试的&lt;strong>基准&lt;/strong>了，后面所有的比较都是针对于这次截图进行的&lt;/li>
&lt;li>第一遍运行测试用例时候用例会报错，因为之前没有任何截图，不过没关系，再运行一次就可以了&lt;/li>
&lt;li>任何时候都可以再次运行用例，此时会拿本次运行时的截图跟第一次保存的截图做像素级的比较，一旦像素有差异用例就会失败，否则用例通过&lt;/li>
&lt;/ul>
&lt;p>下面简单介绍一下如何从零开始使用 playwright 进行截图对比测试。&lt;/p>
&lt;h3 id="安装-playwright">安装 playwright&lt;/h3>
&lt;p>安装 playwright 之前，大家需要安装 nodejs，这一步可以自行搜索。&lt;/p>
&lt;p>这里推荐大家使用&lt;a href="https://www.typescriptlang.org/" target="_blank" rel="noopener noreffer ">Typescript&lt;/a>进行代码的编写。主要是因为在入门阶段我们基本上不会接触到复杂的 typescript 语法结构，难度其实跟写 javascript 差不多，心智上的压力其实不算大。最重要的一点是，使用 typescript+vscode+playwright 这套微软组合（三者都是微软出品的），可以在编辑器里非常容易的得到稳定的代码提示，毕竟 ts 可以算得上是静态语言，对于初学者来说代码提示有时候是非常重要的。&lt;/p>
&lt;p>因为国内的网络问题，整个安装过程需要用到&lt;a href="npm.taobao.org" rel="">淘宝 npm 源&lt;/a>。&lt;/p>
&lt;p>打开&lt;strong>命令行&lt;/strong>，敲入下面的命令。不知道怎么使用命令行的同学可以自行搜索弄清楚命令行是怎么回事。&lt;/p>
&lt;pre tabindex="0">&lt;code>npm install -D @playwright/test --registry=https://registry.npmmirror.com
npm install -D typescript --registry=https://registry.npmmirror.com
npx playwright install

mkdir -p tests
touch tests/itest.info.spec.ts
&lt;/code>&lt;/pre>&lt;p>第 1 行是安装 playwright 及测试套件；第 2 行是安装 typescript，第 3 行是安装 playwright 需要调用的浏览器。最后两行则创建了 tests 文件夹并创建了名为&lt;code>itest.info.spec.ts&lt;/code>的文件。&lt;/p>
&lt;h3 id="第一个用例">第一个用例&lt;/h3>
&lt;p>在&lt;code>itest.info.spec.ts&lt;/code>中输入下面的内容。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">test&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">expect&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;@playwright/test&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Itest.info homepage&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">({&lt;/span> &lt;span class="nx">page&lt;/span> &lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="nx">page&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kr">goto&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;http://www.itest.info&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="nx">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">page&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">toHaveScreenshot&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>上面代码的意思是跳转到重定向学院的首页www.itest.info，然后截图，如果截图跟之前保存的像素级别一致，那么用例通过，否则用例失败。&lt;/p></description></item><item><title>我克隆了自己的声音：人人都能训练模型的时代到了</title><link>https://itest2025.pages.dev/i-cloned-my-own-voice_127/</link><pubDate>Fri, 29 Mar 2024 00:57:14 +0800</pubDate><guid>https://itest2025.pages.dev/i-cloned-my-own-voice_127/</guid><description>&lt;p>之前就看到有人推荐&lt;a href="https://github.com/RVC-Boss/GPT-SoVITS" target="_blank" rel="noopener noreffer ">GPT-SoVITS&lt;/a>项目，据说只要提供很少的声音素材就可以克隆一个人的声音，不仅训练时间短，而且中文的效果非常好，最近正好有点时间，稍微弄了一下，大家可以听一下效果。&lt;/p>
&lt;p>只能说是神似，毕竟准备的训练数据比较一般，加上是 mac 上做的训练，据说本身效果就会差点，buff 叠满了，效果一般般也正常，下次准备在一台有显卡的机器上再试一次。&lt;/p>
&lt;h3 id="通过克隆自己声音来体验训练模型的过程">通过克隆自己声音来体验训练模型的过程&lt;/h3>
&lt;p>不过尽管结果不太尽如人意，我还是推荐大家有时间也去训练个模型，把自己的声音克隆一下。因为这样可以简简单单的了解一下模型训练的过程，比如 GPT-SoVITS 从训练模型到推理 tts 的过程大致可以分为&lt;/p>
&lt;ul>
&lt;li>数据集处理：准备足够的训练数据，首先需要提取人声，然后去除混响，获得比较纯净的高质量人声；接下来还要切分音频并给音频降噪，然后再是给音频打标并校对标注，这一步实际上就是把人声对应的文本给记录下来，因为用了声音转文字的引擎，所以工作量其实还好；&lt;/li>
&lt;li>训练过程：这一步先输出 logs，我也不知道有啥用；然后开始微调训练，可以自定义训练的轮数和其他一些参数，终于感受到什么叫做训练时的调参了；&lt;/li>
&lt;li>推理过程：这一步需要先给出一个 5 秒左右的参考音频，然后把你要合成的文本输入进去，推理一段时间之后就可以生成音频了；&lt;/li>
&lt;/ul>
&lt;p>自己体验训练的过程还是非常有意思的，毕竟隔行如隔山，如果大家想往 ai 行业发展的话，有训练模型的经历应该是对求职有帮助的。&lt;/p>
&lt;h3 id="可以克隆其他人的声音">可以克隆其他人的声音&lt;/h3>
&lt;p>一些公共人物或者虚拟人物的声音其实想对来说比自己的声音更好克隆，因为他们的声音素材相对较多，而且声音质量比自己录制的要高不少。我们可以克隆一些声音来自娱自乐，比如有时间的话我可以克隆一下虫师的声音，然后跟我的声音剪辑在一起来做一个播客。大家也可以天马行空，关公战秦琼式的混搭，不过要注意一些法律上的风险。&lt;/p>
&lt;h3 id="人人都能训练自己的模型">人人都能训练自己的模型&lt;/h3>
&lt;p>这个项目最大的意义我觉得是它大大拉低了个人训练模型的门槛，原则上只要提供十几秒的声音就可以进行训练和克隆，而且训练过程非常迅速（至少在我的 macbook pro 上如此），让小白可以非常低成本的训练模型，因为之前的各种模型要么是对数据要求非常苛刻（海量数据），要么是对训练的机器要求很高（比如 A100 显卡），普通人很难触及到训练这一层面。不过有理由相信，随着科学炼丹的发展，后面将会有越来越多的项目提供更多模态的低门槛模型训练能力吧。&lt;/p></description></item><item><title>AI码农来了，AI会让程序员和测试人员都失业吗</title><link>https://itest2025.pages.dev/ai%E7%A0%81%E5%86%9C%E6%9D%A5%E4%BA%86ai%E4%BC%9A%E8%AE%A9%E7%A8%8B%E5%BA%8F%E5%91%98%E5%92%8C%E6%B5%8B%E8%AF%95%E4%BA%BA%E5%91%98%E9%83%BD%E5%A4%B1%E4%B8%9A%E5%90%97_126/</link><pubDate>Mon, 18 Mar 2024 05:28:57 +0800</pubDate><guid>https://itest2025.pages.dev/ai%E7%A0%81%E5%86%9C%E6%9D%A5%E4%BA%86ai%E4%BC%9A%E8%AE%A9%E7%A8%8B%E5%BA%8F%E5%91%98%E5%92%8C%E6%B5%8B%E8%AF%95%E4%BA%BA%E5%91%98%E9%83%BD%E5%A4%B1%E4%B8%9A%E5%90%97_126/</guid><description>&lt;p>前几天初创公司 Cognition AI ，2 个月前才刚成立一夜之间在 X 卷起了风暴，就因为他们一条浏览量上千万的推文：&lt;/p>
&lt;blockquote>
&lt;p>今天，我们很高兴地向大家介绍，第一位 AI 软件工程师 Devin。&lt;/p>&lt;/blockquote>
&lt;p>我们来看看这位 AI 程序员能做什么吧。&lt;/p>
&lt;ul>
&lt;li>自主查阅 api 文档，学习新技术，自主写代码&lt;/li>
&lt;li>面对一个需要不时维护的包含不同算法的开源存储库，Devin 拔剑四顾，帮忙查找和修复错误，也就是会改 bug&lt;/li>
&lt;li>去自由职业者网站上接私活，并顺利完成任务&lt;/li>
&lt;/ul>
&lt;p>看上去这位程序员可以完成普通开发者的工作任务，是不是有这样一种可能：&lt;strong>让 AI 诞生的职业，会因为 AI 而失业&lt;/strong>，典型的教会徒弟，饿死师傅？&lt;/p>
&lt;p>目前已经有人在惊呼：ai 已经可以替代程序员了，结合之前英伟达 ceo 黄仁勋的&lt;em>学代码无用论&lt;/em>的观点，看来业界颠覆程序员的野心真是昭然若揭。&lt;/p>
&lt;p>不过事实真的是这样吗？至少在目前这个阶段，我觉得我们&lt;strong>大可不必过分担心&lt;/strong>ai 取代程序员和测试人员的事情，理由有如下几点&lt;/p>
&lt;h3 id="一切都只是-demo">一切都只是 demo&lt;/h3>
&lt;p>目前 Cognition AI 只是放出了 demo 而已，距离真正的生产环境级别的应用，目前应该还是有一定的距离的。还记得之前 google 放出的多模态人工智能模型演示吗，全程充满了科幻感和不真实感，让人觉得未来已来。然后后续的事实是 google 承认演示 demo 有剪辑的成分，声誉一落千丈。&lt;/p>
&lt;p>另外 Cognition AI 正处在融资的关键阶段，这时候放出 demo，我只能说是懂的都懂。&lt;/p>
&lt;p>在经历了之前超导的全民沸腾和迅速反转之后，没有实锤的结论我们可以先观望一段时间再说。&lt;/p>
&lt;h3 id="ai-还不能从事特别复杂的工作">ai 还不能从事特别复杂的工作&lt;/h3>
&lt;p>Devin 应该是可以替代程序员和测试人员完成一些创造力有限的重复性工作，对于需要深度思考的工作，ai 的表现只是一般，比如 Devin 在改 github issue 方面可以达到惊人的 13.86%，在一众 ai 竞品中瑶瑶领先，然而还有 86.14%左右的 bug 是 Devin 所不能解决的，换句话说，Devin 目前解决不了大部分的有深度的问题。&lt;/p></description></item><item><title>我希望在职业生涯开始时就知道的关于软件测试和成长的事情</title><link>https://itest2025.pages.dev/what-i-wish-i-knew-about-software-testing-and-growth_125/</link><pubDate>Sun, 10 Mar 2024 06:03:08 +0800</pubDate><guid>https://itest2025.pages.dev/what-i-wish-i-knew-about-software-testing-and-growth_125/</guid><description>&lt;p>翻译了一篇&lt;a href="https://automationhacks.io/2024-02-25-what-i-wish-i-knew-about-software-testing-and-growth" target="_blank" rel="noopener noreffer ">文章&lt;/a>，原文是https://automationhacks.io/2024-02-25-what-i-wish-i-knew-about-software-testing-and-growth，感觉还是很有道理的。&lt;/p>
&lt;p>大家好&lt;/p>
&lt;p>我一直在反思一些与社区中一些同行和新兴测试人员和工程师进行的有关测试发展的对话。虽然要解开这些话题需要许多博客文章和演讲,但我想列出一个无序的技能、方法、习惯和成长想法随机清单,这些都是我希望在测试职业生涯开始时就知道的,以求成熟。&lt;/p>
&lt;h2 id="我现在如何看待测试">我现在如何看待测试?🤔&lt;/h2>
&lt;p>测试是一个深层次和多方面的学科,有很多东西要学习,并且有令人难以置信的力量为客户带来愉悦的成果。如果做得好,它可以使整个公司快速行动并自信地交付产品。测试的最终目标是有助于构建令人满意的优质产品和服务。&lt;/p>
&lt;h2 id="技能-">技能 🤹&lt;/h2>
&lt;ul>
&lt;li>学习盲打&lt;/li>
&lt;li>学习思维导图&lt;/li>
&lt;li>学习如何评估风险并系统地增加不同层次的测试覆盖范围&lt;/li>
&lt;li>学习测试的基础知识,然后不要停止,继续前进。&lt;/li>
&lt;li>在会议和讨论中发言(不要害羞)&lt;/li>
&lt;li>提出那些&amp;quot;愚蠢&amp;quot;的问题,始终保持好奇心&lt;/li>
&lt;li>学习多种语言,包括静态和动态编程语言,以及更多语言&lt;/li>
&lt;li>学习编码和阅读大量代码&lt;/li>
&lt;li>学习设计高效的 CI/CD 流水线&lt;/li>
&lt;li>测试金字塔也表明你可以学习不同层次的测试&lt;/li>
&lt;li>不要只关注 UI 或后端。把系统视为一个整体。&lt;/li>
&lt;li>专注于客户及其 UI/UX 接触点。这些绝不能中断&lt;/li>
&lt;li>学习工具或框架的基本 API,然后不断深入&lt;/li>
&lt;li>学习如何在 LeetCode 上解决问题,并了解数据结构和系统设计&lt;/li>
&lt;li>学会如何进行向上管理&lt;/li>
&lt;li>学习如何建立自己的人际网络&lt;/li>
&lt;/ul>
&lt;h2 id="方法和态度-">方法和态度 🙂&lt;/h2>
&lt;ul>
&lt;li>从任何地方开始&lt;/li>
&lt;li>做一个喜欢探索的人&lt;/li>
&lt;li>谦逊踏实。认识到总有成长的空间&lt;/li>
&lt;li>与新朋友交朋友,帮助他们成长,你也一起成长。&lt;/li>
&lt;li>在团队中鼓励心理安全感。&lt;/li>
&lt;li>寻找一位导师,让他们教授你一系列主题。然后再找另一位。不断成长。&lt;/li>
&lt;li>不要将自己局限于一个领域(Web、移动、后端、数据、性能、安全、CI) - 混合各种事物!&lt;/li>
&lt;li>质量是每个人的责任,但需要有人为之效力&lt;/li>
&lt;li>如果在某个环境中你没有在学习,要么改变自己,要么改变环境。&lt;/li>
&lt;li>利用互联网和广阔开放的测试社区的力量。你会学习得更好更快&lt;/li>
&lt;li>当你遇到问题时不要放弃;要舒服地说&amp;quot;我还不知道这个&amp;hellip;&amp;hellip;&amp;quot;&lt;/li>
&lt;li>学习某些东西,教给其他人,然后专注于其他更好的事物。&lt;/li>
&lt;li>不要成为一个孤立的单点故障&lt;/li>
&lt;li>阅读文档是你最好的朋友&lt;/li>
&lt;li>你不需要等待一门课程或班级来教授你什么。尽可能自学&lt;/li>
&lt;li>清晰的写作即清晰的思维&lt;/li>
&lt;li>不要只关注头衔,而是要发展技能。你不等于你的头衔&lt;/li>
&lt;li>始终为团队做出正面贡献,专注于成果&lt;/li>
&lt;li>不要将自己局限于测试员的单一角色,而要成为一名通才软件工程师&lt;/li>
&lt;/ul>
&lt;h2 id="习惯-">习惯 ⌨️&lt;/h2>
&lt;ul>
&lt;li>建立笔记系统。它会使你更快成长。&lt;/li>
&lt;li>安排一致和有目的的学习时间&lt;/li>
&lt;li>定期总结和叙述你的工作&lt;/li>
&lt;li>养成阅读书籍和总结的习惯&lt;/li>
&lt;li>在做日常杂务时听播客。让你的杂务变得有趣!&lt;/li>
&lt;li>阅读博客和时事通讯,向同行学习&lt;/li>
&lt;li>在会议上提议演讲,作为学习的一种方式&lt;/li>
&lt;li>采用开源,成为社区的一部分,尽自己所能贡献。&lt;/li>
&lt;/ul>
&lt;h2 id="成长-">成长 🌱&lt;/h2>
&lt;ul>
&lt;li>以上所有以及&amp;hellip;&amp;hellip;&lt;/li>
&lt;li>尽可能在公开场合构建、学习和分享&lt;/li>
&lt;li>制定个人路线图和 OKR,并评估自己的进展&lt;/li>
&lt;li>维护个人技术品牌&lt;/li>
&lt;li>探索在不同的工作环境中工作,如服务、产品、初创公司、成长型公司和大型科技公司,以获得广阔的视野&lt;/li>
&lt;/ul>
&lt;p>我遗漏了什么吗?请在评论中告诉我&lt;/p></description></item><item><title>推荐1个很有用的测试wiki</title><link>https://itest2025.pages.dev/a-great-test-wiki_114/</link><pubDate>Sat, 09 Mar 2024 09:04:26 +0800</pubDate><guid>https://itest2025.pages.dev/a-great-test-wiki_114/</guid><description>&lt;p>&lt;a href="https://ray.run/wiki" target="_blank" rel="noopener noreffer ">https://ray.run/wiki&lt;/a>&lt;/p>
&lt;p>这个网站应该是我见过最全面的关于测试领域的知识整理了。稍微写代码数了一下，应该有 200 多个主要知识点，每个下面都有很多的常见问题。知识点的解释其实没啥突出的，不过常见问题对于面试来说就非常的顺手了。&lt;/p>
&lt;p>稍微列举 1 个有意思的主题。&lt;/p>
&lt;blockquote>
&lt;p>Definition of Acceptance Test Driven Development&lt;/p>&lt;/blockquote>
&lt;p>什么是 ATDD?&lt;/p>
&lt;p>说实话，这个概念我也是第一次见到。&lt;/p>
&lt;blockquote>
&lt;p>What are the key steps involved in ATDD?&lt;/p>&lt;/blockquote>
&lt;p>ATDD 的关键步骤有哪些？&lt;/p>
&lt;p>The key steps involved in ATDD are:&lt;/p>
&lt;ul>
&lt;li>Collaboration among developers, testers, and business stakeholders to define acceptance criteria.&lt;/li>
&lt;li>Creation of acceptance tests before the development starts, based on the agreed-upon criteria.&lt;/li>
&lt;li>Development of the feature or user story, guided by the acceptance tests.&lt;/li>
&lt;li>Continuous Integration to ensure that code changes are automatically tested against the acceptance tests.&lt;/li>
&lt;li>Refinement of the acceptance tests as necessary, to address changes in requirements or understanding.&lt;/li>
&lt;li>Test Execution to validate that the software meets the agreed-upon acceptance criteria.&lt;/li>
&lt;li>Review and Feedback from stakeholders to confirm that the acceptance tests cover the desired functionality and behavior.&lt;/li>
&lt;li>Iteration through these steps as needed until the feature meets the acceptance criteria.&lt;/li>
&lt;/ul>
&lt;p>Acceptance tests are typically automated to facilitate frequent execution and regression testing. The tests are written in a language that is understandable by all parties involved, often using Behavior Driven Development (BDD) frameworks like Cucumber or SpecFlow. This ensures that the tests serve as both specification and validation.&lt;/p></description></item><item><title>使用openAI来分析自动化测试报告的错误信息</title><link>https://itest2025.pages.dev/using-openai-platform-to-analyse-automated-test-failures_86/</link><pubDate>Sat, 09 Mar 2024 09:03:44 +0800</pubDate><guid>https://itest2025.pages.dev/using-openai-platform-to-analyse-automated-test-failures_86/</guid><description>&lt;p>今天看到有人用 ai 去分析自动化测试报告里的错误，代码和概念都很简单，也很有意思，忍不住翻译了一下，供大家参考。&lt;/p>
&lt;p>原文地址&lt;a href="https://labs.pineview.io/using-openai-platform-to-analyse-automated-test-failures" target="_blank" rel="noopener noreffer ">https://labs.pineview.io/using-openai-platform-to-analyse-automated-test-failures&lt;/a>&lt;/p>
&lt;h2 id="引言">引言&lt;/h2>
&lt;p>当涉及到人工智能，尤其是 OpenAI 平台时，关于它将如何影响一切的内容并不缺乏。因此乍一看，本文可能似乎是另一篇过于热情和乐观的标题党文章，告诉你应该加入人工智能的行列，否则就会被落下。&lt;/p>
&lt;p>顺便说一下，本文并非由人工智能撰写。我只是使用我最喜欢的文本编辑器应用程序，它以西方文学中最令人激动的小说之一的名字命名——尤利西斯。除了一些基本的自动完成功能外，没有来自外部的干扰（或推理）。本文没有人工智能生成的废话，尽管我不能保证文章完全没有废话。&lt;/p>
&lt;p>但无论你对人工智能生成内容持何种立场，作为软件专业人士，我认为我们都可以达成共识：在自动化软件测试方面，调试和调查测试失败总是很繁琐。因此，我认为这可能是一个可以引入一些人工智能辅助的好领域，因为我们只是在扩展机器已经完成的工作。在这里，不存在冒充人类或“让人们误以为他们正在与真人互动”的风险，这正如哲学家丹尼尔·丹尼特在他最近在《大西洋月刊》上发表的文章中所提到的一个真正的文明风险。&lt;/p>
&lt;h2 id="什么是端到端测试">什么是端到端测试？&lt;/h2>
&lt;p>如果你对端到端测试还不熟悉，它是一种通过模拟真实用户的操作来测试整个应用程序的自动化软件测试类型。&lt;/p>
&lt;p>Nightwatch.js 是一个开源库，用于编写和执行网站和 Web 应用程序的自动化端到端测试。它于 2014 年发布，2021 年被转移到 BrowserStack 的开源计划办公室，目前正在进行开发。Nightwatch.js 是用 Node.js 编写的，它支持所有主要的 Web 浏览器，并且还可以在移动设备上运行测试。&lt;/p>
&lt;p>本教程将介绍如何开发一个 Nightwatch.js 插件，将测试失败和相关错误发送到与 OpenAI 平台集成的服务，以分析错误并获得一些可操作的反馈。默认情况下，Nightwatch 的最新版本已经对测试失败提供了相当好的反馈，并提供了一定程度的可操作反馈，因此我们将尝试使用 GPT-4 模型扩展其功能，以在输出消息中增加一些亮点，提供稍微更好的上下文，并学习如何开发结合人工智能辅助的服务。&lt;/p>
&lt;h2 id="为什么选择-nightwatch">为什么选择 Nightwatch？&lt;/h2>
&lt;p>诚然，目前市场上还有一些其他备受炒作和流行的测试工具，但实际上 Nightwatch 是我们在 2014 年在 Pineview 创建的项目，现在正在 BrowserStack 的开源计划办公室进行开发。我也是那个团队的一员，Nightwatch 仍然是我在所有其他项目中用于测试的最喜欢的工具，当然。&lt;/p>
&lt;p>此外，Nightwatch 作为一个库已经存在了相当长的时间，在这些年里享有不同程度的受欢迎程度。有大量的可用于机器学习模型训练的数据，因此 GPT-4 在编写 Nightwatch 测试和解释结果方面具有相当好的能力，这意味着我们已经有了一个强大的基础，可以构建一个辅助人工智能来解释我们的测试失败，并可能与我们对抗。&lt;/p>
&lt;h2 id="步骤-1---创建错误分析服务">步骤 1 - 创建错误分析服务&lt;/h2>
&lt;p>我们的小练习主要由两个部分组成，都相对简单：&lt;/p>
&lt;p>构建调用 OpenAI 服务的后端服务
编写 Nightwatch.js 插件，接收实际的测试失败并将其发送到后端服务进行分析
我们将从第 1 部分开始 - 构建错误分析服务。在当今时代，构建与人工智能相关的任何东西可能听起来非常奢侈和光鲜，但实际上这只是一个非常基本的任务，并没有太多特别之处。&lt;/p></description></item><item><title>测试同学绩效不好怎么办</title><link>https://itest2025.pages.dev/what-should-do-if-testers-performance-are-bad_26/</link><pubDate>Sat, 09 Mar 2024 09:02:11 +0800</pubDate><guid>https://itest2025.pages.dev/what-should-do-if-testers-performance-are-bad_26/</guid><description>&lt;p>今天是 2024 年的 3 月 8 日，祝所有的测试女神们节日快乐先。&lt;/p>
&lt;p>昨天跟一位同学聊天，发现他去年的年终绩效不是很好，最终可能影响了整体收入，忽然想到是时候聊一下测试同学的绩效问题了。&lt;/p>
&lt;p>作为一个有多年测试管理经验的老人家，之前也经历过非常多的绩效考核和 kpi/okr 制定，经历的多了自然就会发现一些套路，一般来说测试同学的绩效不好大概是因为如下几个方面。&lt;/p>
&lt;h3 id="质量输出效果很差">质量输出效果很差&lt;/h3>
&lt;p>这点容易理解，可以分解为两个方面的输出比较不尽如人意。&lt;/p>
&lt;ul>
&lt;li>结果指标。比如线上问题多，漏测率高等等；&lt;/li>
&lt;li>过程指标。比如测试过程中发现不了什么问题等等；&lt;/li>
&lt;/ul>
&lt;p>质量输出一般都是有指标来衡量的，要提升的话其实也比较直接：&lt;strong>想办法提升核心指标或者加入更多维度的指标来对冲核心指标带来的不利影响。&lt;/strong> 这点的原理其实有点反直觉，那就是数据其实是会说谎的，片面的指标其实不能真实的反映出现实的问题，举个例子，我的自动化测试用例覆盖率是 100%，这个指标看起来很厉害，但现实可能是我只有 1 个用例，而且完全被覆盖了。所以如果指标没有多维度多角度的话，其实想提升还是有办法的，比如漏测率指标可以想办法提高分母，覆盖率指标可以想办法减小分母之类，只要指标比较单薄，那么操作的空间还是有的。另外就是加入更多的对自己有利的指标，这样也能获得一个不错的观感，毕竟打绩效很多时候都是非常主观的事情。&lt;/p>
&lt;h3 id="输出效能不够理想">输出效能不够理想&lt;/h3>
&lt;p>我们现在经常说内卷，我觉得可以再加个外卷，从不原教旨主义的基础上扩展一下卷这个词的适用范围。&lt;/p>
&lt;ul>
&lt;li>外卷，向外去卷。卷竞品，卷价格，卷服务之类的，比较好理解的一个例子就是《唐伯虎点秋香》里面周星驰华府求职卖惨那一段，没错，就是小强这个梗梦开始的地方。&lt;/li>
&lt;li>内卷，卷死自己。就是要想马儿跑得快，又要马儿不吃草，别人加班到 9 点，我就要加到 10 点，别人 1 周测 1 个需求，我 1 天测 1 个，并美其名曰这是我效能突出。&lt;/li>
&lt;/ul>
&lt;p>所以输出效能不理想实际上就是你的 report 对象觉得你不够卷自己，单位时间内做的事情不够多，效率不够高。&lt;/p>
&lt;p>这种情况下，一般来说也是有指标加持的，提升的方式跟上面思路是一样的，想办法提升核心能效指标或者加入更多维度的对自己有利的能效指标。&lt;/p>
&lt;p>另外新技术和新方法的引入也能有效的缓解这个问题。说白了就是从上到下都焦虑，引入新的概念有点像是吃抗焦虑药，如果能持续吃下去的话，那么问题在很长一段时间是可以被缓解的。&lt;/p>
&lt;h3 id="测试不重要">测试不重要&lt;/h3>
&lt;p>团队或者你的 report 对象觉得测试不重要，因为绩效是很主观的东西，所以你的绩效一定不会太好。&lt;/p>
&lt;p>这个想要破局就非常困难了，毕竟没有统一的方法，不过既然有测试这个岗位存在，那就证明团队里大多数人是疏于测试的。加上现在微服务化之后，业务逻辑被拆的七零八落，很难有人能够从全局上去把握整体的业务逻辑了。所以建议还是要更贴近业务，做最熟悉业务的那个人，毕竟测试不重要但是业务很重要，用黑话说就是&lt;strong>把业务当抓手，更团队对齐颗粒度&lt;/strong>。&lt;/p>
&lt;h3 id="离业务太远或者业务不行了">离业务太远或者业务不行了&lt;/h3>
&lt;p>核心就是你的 report 对象觉得你对业务没有贡献，这点经常出现在测试开发和一些支撑型的测试人员身上，而且特别容易发生在业务处于平台期或者下降期的团队上。道理很简单，业务都不行了，盘子都变小了，那么就没必要维持那么大的团队规模了，不做业务或者离业务远的同学，不管开发还是测试也好，绩效都不会好。&lt;/p>
&lt;p>这点其实没有非常好的应对方法，要么换个好点的业务团队，要么做新业务领域的探索和尝试，但总是说的容易，做起来难。&lt;/p>
&lt;h3 id="跟上级关系不好">跟上级关系不好&lt;/h3>
&lt;p>这里分两种情况，一种是业务还行，但就是跟上级不对付，这证明有调整空间，可以参考第三点，先让自己变得重要起来，然后求同存异，希望有关系有所改善；第二种就是业务不行了，而且跟上级相处的不是很融洽，这时候就好好好考虑自己的未来了。&lt;/p>
&lt;h3 id="总结">总结&lt;/h3>
&lt;p>绩效不好首先不要焦虑吧，人生总是起起伏伏的过程，不可能一帆风顺。前几天看罗伯特 艾格的自传，发现这位迪士尼 ceo 大佬的人生也不是到处开挂爽文男主，在他最困难的时候他也焦虑到不能控制他自己（字面意义上的），不过他最终走了出来，药方就是：&lt;strong>工作只是生活的一部分&lt;/strong>， 别看的太重，但又不要不屑一顾，毕竟凭自己的能力自食其力的人都应该获得尊重。&lt;/p></description></item><item><title>单元测试用例该如何设计</title><link>https://itest2025.pages.dev/how-to-write-unittest_122/</link><pubDate>Fri, 08 Mar 2024 09:04:38 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-write-unittest_122/</guid><description>&lt;p>最近一些大公司在进行去测试化的操作，这一切的根源大概可以从几年前微软一刀切砍掉所有内部正式的测试人员开始说起，当时微软内部的测试工程师有一部分转职成了开发工程师，他们的职能中有很大一部分的职责是教会普通开发人员如何进行测试。我们都知道开发人员进行的测试一般以单元测试为主，假如有一天你所在的组织需要你转变成一名测试方面的教练，除了自动化测试之外还需要去推广单元测试，那么你该如何去定义单元测试用例的设计方法论呢？这里给大家一些思路，看看简单的单元测试用例究竟该如何设计。&lt;/p>
&lt;p>一个方法可以有任意数量的有效测试用例；它最终取决于方法的结构。有两种简单的方式可以帮助我们设计单元测试用例。&lt;/p>
&lt;ul>
&lt;li>参数方法&lt;/li>
&lt;li>执行路径方法&lt;/li>
&lt;/ul>
&lt;p>我将通过提供真实的代码来进行演示。所有代码片段都将用 C# 编写，断言将使用我最喜欢的单元测试包 Fluent Assertions。&lt;/p>
&lt;p>我们将为以下方法提供测试用例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">ContainsNamelessItems&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Item&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">items&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Any&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsNullOrEmpty&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>此方法将项目集合作为参数。它遍历项目列表，并针对每个项目&lt;code>Item&lt;/code>检查其 name 属性是否为空。如果 name 存在且不为空，我们返回&lt;code>True&lt;/code>，否则我们返回&lt;code>False&lt;/code>。&lt;/p>
&lt;h3 id="使用参数方法创建测试用例">&lt;strong>使用参数方法创建测试用例&lt;/strong>&lt;/h3>
&lt;p>这种方式主要考虑的是入参可以传递哪些值。&lt;/p>
&lt;p>查看该方法的参数 ContainsNamelessItems，我们有一个 List&lt;Item>名为 items. 此参数可能有几个可能的值：&lt;/p>
&lt;ul>
&lt;li>items 是空的&lt;/li>
&lt;li>items 至少包含 1 个 Item 具有 Name 未定义的属性&lt;/li>
&lt;li>items 不包含具有未定义 Name 属性的项目&lt;/li>
&lt;li>items 是 null&lt;/li>
&lt;/ul>
&lt;p>这些可能的值中的每一个都可以作为单独的用例存在。&lt;/p>
&lt;p>以下是一些可能的测试用例和断言：&lt;/p>
&lt;p>1，当&lt;code>List&amp;lt;Item&amp;gt;&lt;/code>为空时，我们期望返回值是&lt;code>False&lt;/code>因为其的&lt;code>List&amp;lt;Item&amp;gt;&lt;/code>无 name 属性。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">WhenItemsIsEmpty_ReturnFalse&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Item&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ContainsNamelessItems&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Should&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">BeFalse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;because an empty collection cannot contain nameless items&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>2，当&lt;code>List&amp;lt;Item&amp;gt;&lt;/code>包含至少 1 项没有 name 属性的&lt;code>Item&lt;/code>时，我们期望返回值是&lt;code>True&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">WhenItemsContainsANamelessItem_ReturnTrue&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Item&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Item&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Item1&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Item&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Empty&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1">// nameless item&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ContainsNamelessItems&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Should&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">BeTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;because there is a nameless item in the collection&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>3，当&lt;code>List&amp;lt;Item&amp;gt;&lt;/code>不包含任何没有 name 属性的项目时，我们期望返回值是&lt;code>False&lt;/code>，因为所有项目都有 name。&lt;/p></description></item><item><title>Selenium Manager可以用起来了</title><link>https://itest2025.pages.dev/selenium-manager-is-great_121/</link><pubDate>Fri, 08 Mar 2024 09:04:37 +0800</pubDate><guid>https://itest2025.pages.dev/selenium-manager-is-great_121/</guid><description>&lt;p>前几天随手写了几个 headless 的 selenium 爬虫脚本，运行的时候发现本地的 chromedriver 竟然不需要更新，一时间有点没反应过来，毕竟 selenium 有个痛点就是&lt;strong>chrome 浏览器自动升级之后需要下载新的 chromedrier&lt;/strong>， 否则之前的脚本将会报错。当然了，之前也有一些规避的方式，比如&lt;/p>
&lt;ul>
&lt;li>关掉 chrome 的自动升级&lt;/li>
&lt;li>用 firefox，毕竟&lt;a href="https://github.com/mozilla/geckodriver/releases" target="_blank" rel="noopener noreffer ">geckodriver 一年也就更新个 2-3 个版本&lt;/a>&lt;/li>
&lt;li>用第三方的 driver 管理工具，比如 python 有个&lt;a href="https://pypi.org/project/webdriver-manager/" target="_blank" rel="noopener noreffer ">webdriver-manager&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>这些方法其实都挺好，都能解决核心问题，特别是 python 的 webdriver-manager，几行代码就可以保持 driver 永远自动更新，举个例子&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># selenium 4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">selenium&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">webdriver&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">selenium.webdriver.chrome.service&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Service&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">ChromeService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">webdriver_manager.chrome&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChromeDriverManager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">webdriver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Chrome&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">service&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">ChromeService&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ChromeDriverManager&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">install&lt;/span>&lt;span class="p">()))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这次不用更新 driver 是因为使用了官方推出的&lt;a href="https://www.selenium.dev/documentation/selenium_manager/" target="_blank" rel="noopener noreffer ">selenium manager&lt;/a>，之前没留意，不过真到用的时候发现还是比较方便的。对我来说 selenium manager 最方便的就是初始化环境的功能，比如&lt;/p>
&lt;ul>
&lt;li>自动安装浏览器&lt;/li>
&lt;li>自动安装 driver&lt;/li>
&lt;li>支持多架构多系统&lt;/li>
&lt;li>可以配置代理，这点很重要&lt;/li>
&lt;li>自动管理浏览器和 driver，其实就是把浏览器和 driver 放在了系统 PATH 里&lt;/li>
&lt;/ul>
&lt;p>如果我有一个脚本需要在 windows 和 macos 的最新版本 chrome 上跑，那么环境初始化就非常容易了，只需要下面的命令&lt;/p></description></item><item><title>秒会selenium grid</title><link>https://itest2025.pages.dev/selenium-grid-quick-setup_119/</link><pubDate>Fri, 08 Mar 2024 09:04:34 +0800</pubDate><guid>https://itest2025.pages.dev/selenium-grid-quick-setup_119/</guid><description>&lt;p>今天在看 selenium grid 文档的时候，发现 selenium grid4 的设计还是不错的，想顺手体验一下，于是就发现了&lt;a href="https://github.com/SeleniumHQ/docker-selenium" target="_blank" rel="noopener noreffer ">docker-selenium&lt;/a>项目，可以快速的设置好 selenium grid 环境，非常简单方便。&lt;/p>
&lt;p>然而后面准备用 python 去写个简单例子的时候，发现很难找到 python 代码的例子，好不容易找到 1 个却发现跑不起来，于是简单的看了下源码，找到了正确的打开方式，这里简单分享一下。&lt;/p>
&lt;h3 id="selenium-grid-的使用场景">selenium grid 的使用场景&lt;/h3>
&lt;p>在我看来 grid 的使用场景有两个&lt;/p>
&lt;ul>
&lt;li>在不同的浏览器上并行跑用例，这比挨个在不同浏览器上跑要省不少时间&lt;/li>
&lt;li>启动多个节点在同一个浏览器上并行跑用例，同样也是节约了执行时间&lt;/li>
&lt;/ul>
&lt;h3 id="快速安装好-selenium-grid-环境">快速安装好 selenium grid 环境&lt;/h3>
&lt;p>Selenium grid 有多种模式，比如 Standalone, Hub and Node，对于初次体验来说无脑用 Standalone 是不会有问题的。&lt;/p>
&lt;p>传统的方式是使用 java 来运行 jar 包安装，不过 docker selenium 提供了更简单的方式，直接用 docker 跑镜像就好了。 比如下面的命令就启动了 1 个 firefox 的远程节点。&lt;/p>
&lt;pre tabindex="0">&lt;code>docker run -d -p 4444:4444 -p 7900:7900 --shm-size=&amp;#34;2g&amp;#34; selenium/standalone-firefox:4.11.0-20230801
&lt;/code>&lt;/pre>&lt;p>这里暴露了 2 个端口&lt;/p>
&lt;ul>
&lt;li>4444: hub 的端口，直接访问可以看到所有节点的信息&lt;/li>
&lt;li>7900: vnc 的端口，访问 http://localhost:7900/?autoconnect=1&amp;amp;resize=scale&amp;amp;password=secret 就可以看到远程节点上的浏览器运行情况，非常方便了&lt;/li>
&lt;/ul>
&lt;h3 id="连接远程节点进行测试">连接远程节点进行测试&lt;/h3>
&lt;p>代码很简单，我的运行环境是&lt;/p></description></item><item><title>如何实现线程安全的内存缓存</title><link>https://itest2025.pages.dev/how-to-implement-thread-safe-memory-cache-in-golang_117/</link><pubDate>Fri, 08 Mar 2024 09:04:31 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-implement-thread-safe-memory-cache-in-golang_117/</guid><description>&lt;p>这两天正好看到一个用 go 实现的线程安全的内存缓存，实现代码非常简洁高效，不卖弄不烧脑，非常值得初学者拿来学习。&lt;/p>
&lt;h3 id="项目地址">项目地址&lt;/h3>
&lt;p>项目地址在&lt;a href="https://github.com/muesli/cache2go" target="_blank" rel="noopener noreffer ">https://github.com/muesli/cache2go&lt;/a>，目前已经有 1.8k 的 star。&lt;/p>
&lt;h3 id="如何使用">如何使用&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s">&amp;#34;github.com/muesli/cache2go&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s">&amp;#34;time&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Keys &amp;amp; values in cache2go can be of arbitrary types, e.g. a struct.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">myStruct&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">text&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">moreData&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="c1">// Accessing a new cache table for the first time will create it.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">cache&lt;/span> &lt;span class="o">:=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="c1">// We will put a new item in the cache. It will expire after&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="c1">// not being accessed via Value(key) for more than 5 seconds.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">val&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">myStruct&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;This is a test!&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">{}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;someKey&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">val&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="c1">// Let&amp;#39;s retrieve the item from the cache.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">res&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Value&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;someKey&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Found value in cache:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Data&lt;/span>&lt;span class="p">().(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">myStruct&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Error retrieving value from cache:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="c1">// Wait for the item to expire in cache.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">6&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">res&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Value&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;someKey&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Item is not cached (anymore).&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="c1">// Add another item that never expires.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;someKey&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">val&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="c1">// cache2go supports a few handy callbacks and loading mechanisms.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SetAboutToDeleteItemCallback&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">e&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">cache2go&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">CacheItem&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Deleting:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Key&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Data&lt;/span>&lt;span class="p">().(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">myStruct&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">text&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">CreatedOn&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="c1">// Remove the item from the cache.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Delete&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;someKey&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="c1">// And wipe the entire cache table.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Flush&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>简单看一下核心 api&lt;/p></description></item><item><title>Slenium已死？</title><link>https://itest2025.pages.dev/is-selenium-dead_116/</link><pubDate>Fri, 08 Mar 2024 09:04:29 +0800</pubDate><guid>https://itest2025.pages.dev/is-selenium-dead_116/</guid><description>&lt;p>selenium 已死？其他的框架例如 playwright, cypress 当立？这是去年一个广泛讨论的话题。对于我来说这个观点很明显是偏颇的，因为&lt;/p>
&lt;ul>
&lt;li>selenium 本身已经成为了 w3c 规范的一部分，现在市面上所有的浏览器都遵循这个规范&lt;/li>
&lt;li>selenium 本来就不是一个纯测试工具，它是为自动化而生，除了浏览器测试之外，selenium 还有很广泛的用途，而 playwright/cypress 则更专精于测试领域&lt;/li>
&lt;li>selenium 的 api 相当稳定，对于一些需要长期维护的项目来说这是非常有诱惑力的，而我去年用 playwright 做了一个项目，今年由于 api 升级，去年的代码基本上已经完全不可用了&lt;/li>
&lt;/ul>
&lt;p>所以对我来说，selenium 尽管已经徐娘半老，吸引力大不如前，但在某些场景下，selenium 仍然会是我的首选工具，就像是 vb/php 一样，尽管大家都已经看衰很多年了，但这些技术一直没有落幕退场。&lt;/p>
&lt;p>selenium 官方可能也察觉到了这些广泛的讨论，昨天他们官网 blog 发了一篇文章，直接讨论 selenium 与其他工具的情感纠葛，上下文可能是不少人发博文比较 selenium 与其他工具，然后标题党一下，使得大家产生错觉：selenium 真的已经快死翘翘了。这篇内容专业简洁，适合给大家消除误解，原文地址：https://www.selenium.dev/blog/2024/selenium-vs-blog-posts/。&lt;/p>
&lt;p>下面是全文翻译。&lt;/p>
&lt;p>这篇博文讨论了那些比较 Selenium、Cypress 和 Playwright 的标题党文章。这些文章没有意义，也没有帮助。&lt;/p>
&lt;p>作者：David Burns (@AutomatedTester) | 2024 年 1 月 9 日星期二&lt;/p>
&lt;p>在博文中，关于自动化测试的标题党文章最容易的方式就是将 Selenium 与其他工具进行比较，并配以一个吸引人的标题，尤其是当它贬低现有工具时。&lt;/p>
&lt;p>不幸的是，这可能会使人们对这些产品中的哪些功能可用产生困惑，尤其是当我们进行同类的过度类比时。&lt;/p>
&lt;p>Selenium 一直是一个很好的浏览器自动化工具。对于该项目来说，幸运的是，它已经成为测试 Web 应用程序的首选工具近 20 年。该项目专注于构建越来越复杂的浏览器自动化的难点。项目的重点一直是稳定的 API 和可扩展性，以保证 Selenium 的运行。它没有关注人们如何进行测试，因为有非常好的测试框架可用，并且为 5 种不同的编程语言进行测试是一项非常重要的工程工作。&lt;/p>
&lt;p>然而，这些博文中经常出现一些误解。&lt;/p>
&lt;h3 id="与-playwright-和-cypress-相比设置浏览器和驱动程序太困难">与 Playwright 和 Cypress 相比，设置浏览器和驱动程序太困难&lt;/h3>
&lt;p>过去确实如此，因为您需要下载驱动程序。对于 GeckoDriver 和 SafariDriver 来说，这并不太糟糕，因为它们可以优雅地处理浏览器升级。另一方面，对于基于 Chromium 的浏览器，您需要为每个新版本更新驱动程序。&lt;/p></description></item><item><title>AI大语言模型在自动化用例生成中的探索</title><link>https://itest2025.pages.dev/how-to-use-ai-to-generate-test-cases_113/</link><pubDate>Fri, 08 Mar 2024 09:04:24 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-use-ai-to-generate-test-cases_113/</guid><description>&lt;p>最近读到&lt;a href="https://drlee.io/implementing-ai-in-software-testing-creating-a-text-generation-model-for-test-automation-7294b26f93c4" target="_blank" rel="noopener noreffer ">这篇文章&lt;/a>，原文在是https://drlee.io/implementing-ai-in-software-testing-creating-a-text-generation-model-for-test-automation-7294b26f93c4，里面涉及到一些基于ai进行自动化测试的探索，原文是这么说的：&lt;/p>
&lt;blockquote>
&lt;p>将人工智能 (AI) 纳入软件测试可谓是游戏规则的改变者，能够显著提升效率和有效性。本文利用 OpenAI 的文本生成模型——尤其是 GPT-3.5-turbo 和 GPT-4-turbo-preview——在 Google Colab 中构建了一个文本生成模型，重点关注测试自动化用例。&lt;/p>&lt;/blockquote>
&lt;p>看了一下，里面列举了 3 个测试用例。&lt;/p>
&lt;p>&lt;strong>The system shall allow users to securely login with a username and password.&lt;/strong>&lt;/p>
&lt;p>这是用户登录的用例。&lt;/p>
&lt;p>&lt;strong>Ensure that the shopping cart allows users to add items, remove items, and proceed to checkout.&lt;/strong>&lt;/p>
&lt;p>这是购物车的用例。&lt;/p>
&lt;p>&lt;strong>The weather API should return a JSON response with fields for temperature, humidity, and precipitation forecast for the next 5 days.&lt;/strong>&lt;/p></description></item><item><title>单元测试的最佳实践</title><link>https://itest2025.pages.dev/unit-test-best-practice_112/</link><pubDate>Fri, 08 Mar 2024 09:04:23 +0800</pubDate><guid>https://itest2025.pages.dev/unit-test-best-practice_112/</guid><description>&lt;p>看到一篇关于单元测试最佳实践的文章，简单翻译一下，很多都说到了点子上，不能赞同更多。&lt;/p>
&lt;p>单元测试是对软件应用程序中各个单元或组件进行的软件测试。单元测试旨在验证每个软件单元的执行是否符合设计预期。单元测试可以确保代码质量,提高可维护性,方便重构,并提高开发速度。&lt;/p>
&lt;p>当谈到最佳实践时,这里有一些应该遵循的:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>为每个缺陷编写新测试:当你遇到一个缺陷时,编写一个暴露该缺陷的测试。这也称为回归测试。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>保持测试的小而聚焦:一个单元测试应该限制在一个独立的函数或方法中。这使得当测试失败时更容易识别和修复问题。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>隔离你的测试&lt;/strong>:确保每个测试都是相互独立的。这允许你单独运行每个测试,并以任意顺序运行。(划重点了)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>按测试类型组织测试:你可以根据它们测试的对象类型或测试类型来组织测试。这使得查找和运行相关测试更容易。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>每次测试一条代码路径:每个测试应该验证方法中的一条明确的代码路径。这使得理解被测试的内容以及测试可能失败的原因更容易。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>避免在测试中加入逻辑&lt;/strong>:当你在测试中加入逻辑时,你有引入测试缺陷的风险。保持测试的简单。(重点)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>避免在被测试的类中使用静态方法:静态方法不能在子类中重写,这使得它们难以测试。避免在你要测试的类中使用静态方法。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>避免测试实现细节:你的测试应该关注代码的行为,而不是它的实现。如果测试实现细节,当你的代码行为保持不变时,测试仍可能中断。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>首先为对应用影响最大的方法编写测试:将测试工作集中在对应用影响最大的方法上。这通常包括包含复杂逻辑或与外部资源交互的方法。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>使用 AAA 模式&lt;/strong>:准备测试数据和测试环境(Arrange)、执行(Act)、断言(Assert)是编写单元测试的典型模式。单元测试方法的安排部分初始化对象和传递给被测试方法的数据值。执行部分调用带有 Arrange 参数的被测试方法。断言部分验证被测试方法的行为符合预期。(划重点)&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>原文如下:&lt;/p>
&lt;p>𝗨𝗻𝗶𝘁 𝗧𝗲𝘀𝘁𝗶𝗻𝗴 𝗕𝗲𝘀𝘁 𝗣𝗿𝗮𝗰𝘁𝗶𝗰𝗲𝘀&lt;/p>
&lt;p>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.&lt;/p>
&lt;p>When we talk about best practices, here is a list of that one should follow:&lt;/p></description></item><item><title>自己编写命令行工具来提高测试效率</title><link>https://itest2025.pages.dev/write-cli-to-increase-your-test-efficiency_111/</link><pubDate>Fri, 08 Mar 2024 09:04:21 +0800</pubDate><guid>https://itest2025.pages.dev/write-cli-to-increase-your-test-efficiency_111/</guid><description>&lt;p>测试同学在很多时候都是在沟通问题，复现问题和定位问题。&lt;/p>
&lt;p>记得多年前刚入行的时候，我一直对复现问题不够重视，觉得自己是测试人员，主营业务应该是测试，复现问题应该是解决问题的人做的事情。后来尽管岗位分工越来越细，但是测试同学做的事情并没有越来越聚焦，反而整体的工作范围更加的宽泛，其实说白了就是专业性上有提升，但是专注度上的改善其实并不明显。&lt;/p>
&lt;p>复现问题其实很多时候是测试人员的痛点，比如我们在测试过程中发现了一个问题，并发给了开发同学，然而，你可能会有下面的经历&lt;/p>
&lt;ul>
&lt;li>开发人员表示在自己的环境上是好的呀，这个不讨论，各种梗太多了&lt;/li>
&lt;li>开发人员表示看不懂 bug 上的描述，这点可以用截图和录屏来解决&lt;/li>
&lt;li>开发人员表示你帮我复现一下吧，这时候就比较痛苦了，因为复现本质上就是重复&lt;/li>
&lt;/ul>
&lt;p>测试的时候一再的重复自己是痛苦且低效的，如果问题是用户上报的，那么除去重复之外，还要加上阅读理解的过程，阅读用户的操作过程，理解他们做了些什么，以及猜测系统或者产品的表现。稍微归纳一下，发现复现基本上都是在做下面的一些事情&lt;/p>
&lt;ol>
&lt;li>创建数据，可以是从 app 上，也可以是从网页上，甚至可以在 admin 后台去创建&lt;/li>
&lt;li>收集或查询一些周边数据，比如数据库或者是被测的系统和产品上&lt;/li>
&lt;li>结合数据和实际表现去判断产品或者系统的表现是不是正常&lt;/li>
&lt;/ol>
&lt;p>所以除了第 3 步之外，前 2 步都是非常适合用自动化脚本的方式去实现的。我就用 python 写了很多的辅助脚本去帮助我重现问题，下面是我的一些经验&lt;/p>
&lt;h3 id="用直接调用-api-的方式去创建查询数据">用直接调用 api 的方式去创建/查询数据&lt;/h3>
&lt;p>因为现在大部分产品的管理端都是前后端分离的，所以管理端是可能有纯后台的 api 的；以及移动端 app 都是有 api 的，调用 api 创建各种测试数据其实是可以实现的，只要做好鉴权工作，调 api 去创建和查询的实现成本和维护成本都是可控的。&lt;/p>
&lt;p>之前我是用直连数据库的方案去做查询和创建，不过后面逐渐废弃了这种做法，这是因为&lt;/p>
&lt;ul>
&lt;li>数据库表结构可能会发生变化，从而导致脚本执行失败，从而降低了脚本执行的稳定性以及提高了维护成本&lt;/li>
&lt;li>脚本需要去关注分库分表的实现，增加了实现难度，降低了长期的可维护性&lt;/li>
&lt;li>因为安全审计的关系，数据库的连接和密码会周期性的发生变化，增加了维护成本&lt;/li>
&lt;li>我们生产环境的数据库只有生产环境的机器才能访问，本地办公网没有办法访问，所以做线上问题复现的时候，直接连数据库就会显得有心无力了&lt;/li>
&lt;li>直接写数据库可能会绕过后端的数据校验，可能会写入脏数据&lt;/li>
&lt;/ul>
&lt;p>所以在做自动化工具的时候&lt;strong>一般不推荐直接连数据库进行数据的生成和查询&lt;/strong>&lt;/p>
&lt;h3 id="用爬虫的方式去查询周边数据">用爬虫的方式去查询周边数据&lt;/h3>
&lt;p>很多时候我们需要获取一些周边团队的数据，有时候我们可以通过 api 调用来获取，但也会有获取不到的情况；另外有些祖传的系统可能前后端不分离，后端并没有提供 api 接口，这种情况下我一般用爬虫来做数据的收集。&lt;/p>
&lt;p>爬虫的方式有很多好处，但对于一些系统来说可能存在开发难度高，运行效率低的情况，所以如果有更好的方式可以方便的拿到数据的话，爬虫的优先级可以低一点。&lt;/p>
&lt;h3 id="用缓存去提高执行效率">用缓存去提高执行效率&lt;/h3>
&lt;p>收集数据，特别是收集多个页面或者是多个系统的数据往往是比较慢的，这时候可以用 redis 等缓存数据库来保存一下结果，这样可以加速代码的执行。这里不推荐用数据去做，因为收集数据是一项相对来说比较高频且随意的工作，数据结构一般不会特别稳定，如果表结构频繁变更的话，那么开发和维护的成本都非常高。我一般是把数据直接序列化成 json 字符串，然后丢到 redis 的 sting 对象里，简单方便。&lt;/p>
&lt;p>另外我的测试数据创建过程一半也是用爬虫加 api 的方式，缓存下来的数据可以帮助我进行更加复杂的测试数据创建工作。&lt;/p>
&lt;h3 id="编写-cli-工具让测试过程更加工程化">编写 cli 工具，让测试过程更加工程化&lt;/h3>
&lt;p>有一段时间我积累了相当多的 python 脚本，在实际工作中调用脚本就成了一门学问，一些脚本要这样调，另一些可能要在其他脚本调用后才能调用，记忆的成本很高，文档化的价值又基本没有，后来我发现使用 cli 工具去整合脚本调用，写好每个命令的 help message，这样文档和调用编排都有了。&lt;/p></description></item><item><title>chrome上更好的录制回放工具？Jesteer介绍及试用</title><link>https://itest2025.pages.dev/chrome-jesteer_110/</link><pubDate>Fri, 08 Mar 2024 09:04:20 +0800</pubDate><guid>https://itest2025.pages.dev/chrome-jesteer_110/</guid><description>&lt;p>之前跟大家分享后 chrome 上原生的录制回放功能，今天看到了一款最新的的录制回放工具 jesteer，于是第一时间来了解和试用一下。&lt;/p>
&lt;h3 id="主要功能">主要功能&lt;/h3>
&lt;ul>
&lt;li>不用写代码，直接可以录制和回放&lt;/li>
&lt;li>可以录制基本的页面交互&lt;/li>
&lt;li>自动创建基于 Puppeteer 的脚本&lt;/li>
&lt;li>回放时的快照检查功能&lt;/li>
&lt;li>简单舒适的 web ui&lt;/li>
&lt;/ul>
&lt;h3 id="安装">安装&lt;/h3>
&lt;p>jesteer 是一款 chrome 插件，直接去 chrome 商店里所有 jesteer 点击安装既可。&lt;/p>
&lt;h3 id="界面">界面&lt;/h3>
&lt;p>jesterr 的界面很简单，就 3 个按钮&lt;/p>
&lt;ul>
&lt;li>Record：开始录制&lt;/li>
&lt;li>Take a snapshot：dom 高亮&lt;/li>
&lt;li>Copy to clipboard&lt;/li>
&lt;/ul>
&lt;h3 id="简单上手使用">简单上手使用&lt;/h3>
&lt;ul>
&lt;li>点击 Record 开始录制&lt;/li>
&lt;li>录制过程中点击 Take a snapshot 进行断言&lt;/li>
&lt;li>点击 Stop Recording 停止录制&lt;/li>
&lt;li>点击 Copy to clipboard 拷贝生成的代码到剪切板&lt;/li>
&lt;/ul>
&lt;p>这几步还是非常简单的，后来我遇到了一个问题，怎么进行用例的回放呢？之前 chrome 自带的 Recorder 是可以录制完成后直接回放的，而 jesteer 则找不到回放按钮。折腾一小会后我终于找到了答案。&lt;/p>
&lt;h3 id="把生成的代码粘贴到测试项目中">把生成的代码粘贴到测试项目中&lt;/h3>
&lt;p>为了可以运行生成的代码，我决定新建一个 nodejs 项目来进行尝试。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">mkdir&lt;/span> &lt;span class="nx">jesteer_example&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">cd&lt;/span> &lt;span class="nx">jesteer_example&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">npm&lt;/span> &lt;span class="nx">init&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">npm&lt;/span> &lt;span class="nx">install&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="nx">save&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">dev&lt;/span> &lt;span class="nx">jest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">npm&lt;/span> &lt;span class="nx">install&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="nx">save&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">dev&lt;/span> &lt;span class="nx">puppteer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">touch&lt;/span> &lt;span class="nx">example&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">test&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">js&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>修改 package.json&lt;/p></description></item><item><title>2023年 Selenium 是否正在消亡</title><link>https://itest2025.pages.dev/is-selenium-dying-2023_109/</link><pubDate>Fri, 08 Mar 2024 09:04:18 +0800</pubDate><guid>https://itest2025.pages.dev/is-selenium-dying-2023_109/</guid><description>&lt;p>看到一篇文章&lt;a href="https://sdetunicorns.com/blog/is-selenium-dead" target="_blank" rel="noopener noreffer ">Is Selenium Dead? Explore the State of Selenium Automation in 2023 !&lt;/a>，里面比较详细的描述了 2023 年 selenium 的现状以及竞品分析，感觉还是非常全面的，简单的翻译了一下，供大家参考。&lt;/p>
&lt;p>技术世界中，工具和技术以飞快的速度发展，软件测试也不例外。在过去的几年里，涌现出了许多新的测试工具（如 Cypress、Playwright 和 Robot Framework 等），每种工具都有其独特的特性和方法来解决同一个问题：测试自动化。&lt;/p>
&lt;p>然而，在测试自动化领域，多年来只有一个框架一直是基石：Selenium。它已经存在并开源了 15 年以上，直到最近，一场激烈的争论浮出水面：Selenium 是否已死？是否存在更好的替代品？它是否在面对新的测试工具和方法时失去了竞争力？&lt;/p>
&lt;p>在这篇文章中，我将从自己的角度深入探讨 Selenium 在 2023 年的现状，探索其相对于竞争对手的优势和劣势，以及在快速变化的外部世界中中面临的挑战。&lt;/p>
&lt;h2 id="前提条件">前提条件：&lt;/h2>
&lt;ul>
&lt;li>成为一名 QA 爱好者 😉&lt;/li>
&lt;/ul>
&lt;h3 id="selenium-的崛起和统治">Selenium 的崛起和统治&lt;/h3>
&lt;p>Selenium 最初由 Jason Huggins 于 2004 年开发，作为一个基于 JavaScript 的自动化工具（名为 JavaScriptTestRunner），用于自动化内部应用程序的测试，该应用程序需要耗费大量的手工工作。多年来，随着他意识到该程序的潜力，它演变成了 Selenium-core 并开源。然后，通过多位贡献者实现了许多功能，例如：&lt;/p>
&lt;ul>
&lt;li>Selenium IDE：它支持通过录制和回放功能运行自动化测试&lt;/li>
&lt;li>Selenium Grid：一种强大的功能，可以执行并行测试，以将测试执行时间减少到最低限度&lt;/li>
&lt;li>Selenium 2：这是 Selenium 1 和 Webdriver 的合并，形成了我们今天所知道的工具。&lt;/li>
&lt;/ul>
&lt;p>自 Selenium 2 以来，它获得了很大的关注度，其受欢迎程度迅速上升，这可以归因于几个关键因素，这些因素改变了游戏规则：&lt;/p>
&lt;ul>
&lt;li>开源：Selenium 是开源的，这意味着它可以免费使用，并有一个庞大的社区可以帮助您解决遇到的任何问题，并有许多贡献者和用户。这导致了持续的改进和更新。&lt;/li>
&lt;li>跨浏览器兼容性：Selenium 支持各种网络浏览器，如 Chrome、Firefox、Safari、Edge 和 Internet Explorer，使其成为网络应用程序测试的通用选择。&lt;/li>
&lt;li>多种编程语言：Selenium 兼容多种编程语言，包括 Java、Python、C#、Ruby、JavaScript 和最近的 Kotlin。测试自动化工程师可以选择他们喜欢的语言进行测试自动化。&lt;/li>
&lt;li>广泛采用：许多组织和公司已将 Selenium 纳入其测试自动化套件中，这导致了大量的资源、教程和支持在线可用。（现在，我自己也为我的雇主的公司管理着一个 Selenium 套件 😉）&lt;/li>
&lt;li>与测试框架的集成：Selenium 可以与流行的测试框架如 TestNG 和 Cucumber 集成，从而实现结构化的测试用例管理和报告。&lt;/li>
&lt;li>并行性：Selenium 使用 Selenium Grid 支持强大的测试用例并行性，这大大减少了执行时间。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Selenium 在 2023 年的现状：已死还是未死？&lt;/strong>&lt;/p></description></item><item><title>你应该知道的python自动化脚本</title><link>https://itest2025.pages.dev/python-scripts-you-should-know_108/</link><pubDate>Fri, 08 Mar 2024 09:04:17 +0800</pubDate><guid>https://itest2025.pages.dev/python-scripts-you-should-know_108/</guid><description>&lt;p>&lt;a href="https://www.freecodecamp.org" target="_blank" rel="noopener noreffer ">www.freecodecamp.org&lt;/a>是我密切关注的免费学习编程的网站，里面不定期会有高质量且最新的各种编程以及技术教程，今天偶然在freecodecap看到一篇讲python进行日常自动化的帖子，原文在这里https://www.freecodecamp.org/news/python-automation-scripts/，觉得有点意思，顺便就把里面的内容拿出来分享和评论一下。&lt;/p>
&lt;h3 id="自动校对">自动校对&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Python Proofreading&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># pip install lmproof&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">lmproof&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">proofread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">proofread&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">lmproof&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;en&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">correction&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">proofread&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">proofread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Original: &lt;/span>&lt;span class="si">{}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Correction: &lt;/span>&lt;span class="si">{}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">correction&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">proofread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Your Text&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>说实话，似乎用不上。&lt;/p>
&lt;h3 id="自动随机播放音乐">自动随机播放音乐&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">random&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">music_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;E:&lt;/span>&lt;span class="se">\\&lt;/span>&lt;span class="s1">music diretory&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">songs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">listdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">music_dir&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">song&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">randint&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">songs&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Prints The Song Name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">songs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">song&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startfile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">music_dir&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">songs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>思路很好，不过似乎也用不上。&lt;/p>
&lt;h3 id="pdf-转-csv">pdf 转 csv&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">tabula&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">filename&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">input&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Enter File Path: &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">df&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tabula&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read_pdf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">spreadsheet&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">df&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_csv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;output.csv&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>需要&lt;code>pip install tabula&lt;/code>，在做数据处理的时候就很有用了，因为有些站点的收据下载的格式默认就是 pdf 的。&lt;/p>
&lt;h3 id="自动压缩照片">自动压缩照片&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">PIL&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">PIL&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Image&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">tkinter.filedialog&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="o">*&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">fl&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">askopenfilenames&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">img&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Image&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fl&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">img&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;output.jpg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;JPEG&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">optimize&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">quality&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>需要安装 PIL(Python Imaging Library) ，在归档的时候应该有些用处。&lt;/p></description></item><item><title>playwright会成为下一个selenium吗？</title><link>https://itest2025.pages.dev/is-playwright-able-to-become-the-next-selenium_107/</link><pubDate>Fri, 08 Mar 2024 09:04:15 +0800</pubDate><guid>https://itest2025.pages.dev/is-playwright-able-to-become-the-next-selenium_107/</guid><description>&lt;p>playwright 是微软推出的一款 e2e（端到端）测试工具，支持多种语言及浏览器，那么它会成为下一个 selenium 吗？前几天看到外国的一篇文章发表了其观点，这里翻译了一下&lt;strong>并夹杂了一点点的私货&lt;/strong>，希望可以对大家所有帮助。&lt;/p>
&lt;p>selenium 作为浏览器自动化项目来说是非常成功的存在。Selenium 现在已经被下载了几百万次，并继续在全球范围内被广泛接受和使用。&lt;/p>
&lt;h3 id="selenium-的成功的原因">Selenium 的成功的原因&lt;/h3>
&lt;ol>
&lt;li>Selenium 是开源的，支持多种（如 Java、C#、Js、Python、Ruby、Perl 等），支持所有的浏览器（chrome、firefox、edge、ie、safari、opera 等），可以在多种操作系统（Windows、MAC、Linux）上运行。&lt;/li>
&lt;li>Selenium 功能强大&amp;ndash;它可以做 web 测试，也能做跨浏览器兼容性测试。另外 selenium 设计的初衷是浏览器的自动化，所以除了用作测试之外，selenium 还在 web 自动化操作领域有所建树。&lt;/li>
&lt;li>Selenium 有一个庞大的用户社区，可以帮助你快速入门。&lt;/li>
&lt;li>与其他开源工具相比，Selenium 非常稳定，它的实现甚至成了标准的 w3c 协议。&lt;/li>
&lt;li>最后，Selenium 社区是充满活力的，定期举行许多活动和研讨会，你可以与志同道合的人讨论最新的工具和技术。&lt;/li>
&lt;/ol>
&lt;h3 id="playwright-会成为下一个-selenium-吗">playwright 会成为下一个 selenium 吗？&lt;/h3>
&lt;p>考虑到现代 Web 应用自动化，Selenium WebDriver 似乎是最受欢迎的工具之一，然而，像 Playwright、Puppeteer、Cypress 这样的替代工具正在出现，并争取在一段长时间之后能对其进行超越。&lt;/p>
&lt;p>Playwright 是一个 JavaScript 框架，支持在前端实现 Web 应用程序的自动化。它在后端使用 Node.js，就像 Puppeteer 那样。它扩展了该框架，为用户提供了编写端到端测试或隔离测试应用程序特定部分所需的所有工具。&lt;/p>
&lt;p>支持使用包括 Java、Js、C#、Python 在内的语言编写测试用例，并像 Selenium WebDriver 一样在任何浏览器和任何操作系统上运行。它是开源的，很容易使用，支持单兵作战和团队协同。&lt;/p>
&lt;p>在 UI 自动化领域，Playwright 能够成为下一个 Selenium 的主要原因有以下七个方面。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Playwright 得到了微软的支持，其作者来自 Puppeteer（谷歌）团队，因此 playwright 可以吸收 Puppeteer 积极的方面。另外，它已经了一些版本来支持多种编程语言，社区的反馈也非常积极。简而言之微软的钞能力和干爹属性使其相对其他开源项目来说可能会有更多的持续性。&lt;/p></description></item><item><title>自动化测试覆盖率要到多少才算足够</title><link>https://itest2025.pages.dev/how-much-test-coverage-is-enough-for-automated-testing_105/</link><pubDate>Fri, 08 Mar 2024 09:04:13 +0800</pubDate><guid>https://itest2025.pages.dev/how-much-test-coverage-is-enough-for-automated-testing_105/</guid><description>&lt;p>本文非原创，翻译自&lt;a href="https://qahiccupps.blogspot.com/2021/09/693-ok.html?utm_campaign=Software%2BTesting%2BWeekly&amp;amp;utm_medium=email&amp;amp;utm_source=Software_Testing_Weekly_89" target="_blank" rel="noopener noreffer ">https://qahiccupps.blogspot.com/2021/09/693-ok.html&lt;/a>&lt;/p>
&lt;p>“我们的测试用例中有多少是自动化的？”&lt;/p>
&lt;p>这个问题有很多内容，特别是当它是监控测试状态的指标的时候。这也不是我第一次被问到。根据我的经验，当有人提出这个问题时，可能是因为（a）他们听说过它，（b）测试用例的数量是可数的，并且（c）他们的任务是提供一个管理层可以接受的数字，并且每月向大量不会看它的人发邮件做 ppt，然后阐述自动化“测试”的价值。&lt;/p>
&lt;p>如果这听起来很愤世嫉俗……好吧，我想是的。&lt;/p>
&lt;p>但是，并不意味着我对了解您的需求并试图帮助你获得可以你需求的东西不感兴趣。&lt;/p>
&lt;p>我们能谈谈你的追求吗？为什么？&lt;/p>
&lt;p>我们可以？太好了！&lt;/p>
&lt;p>我会开始。我对这个问题的一些考量是：&lt;/p>
&lt;ul>
&lt;li>自动化测试覆盖率似乎被认为是我们测试的一种衡量标准&lt;/li>
&lt;li>这样的数字不会说明做过的测试的价值&lt;/li>
&lt;li>测试用例的定义没有实际意义&lt;/li>
&lt;li>&amp;hellip;而且，不管它们是什么，测试用例只是我们测试的一部分&lt;/li>
&lt;li>有一个隐含的假设，即自动化程度越高越好&lt;/li>
&lt;li>&amp;hellip;但自动化有其自身的风险&lt;/li>
&lt;li>&amp;hellip;而且，无论自动化意味着什么，自动化测试用例只是我们测试自动化的一部分&lt;/li>
&lt;/ul>
&lt;p>如果我看看我们的测试方式，以及我们可能称之为测试用例的内容，我现在可以想到三种方式来回答您的问题：&lt;/p>
&lt;ol>
&lt;li>从我认为问题的意图来看，我们没有测试用例。我们所有正在进行的测试都是探索性测试，虽然我们可能会用自动化的方式记录测试结果，测试完成以后我们把现存的用例转换成自动化是没有意义的，我们标记为得分为 0%。&lt;/li>
&lt;li>出于练习的目的，我准备将回归测试用例集中的每个断言描述为一个测试用例。因为它们将是我们唯一的测试用例，而且它们都是自动化的。得分 100%！&lt;/li>
&lt;li>好的，我们在测试用例管理系统中有一些项目。这些是历史的发布验证记录，（大多数）测试团队以外的人在我们发布之前会进行这些检查。我更喜欢将它们视为检查点，但我很现实，并且知道我的一些同事只是想遵循步骤。相对于“自动化测试用例”的数量，它们很少，但如果我们将它们包括在我们的计算中，我们会将分数降低到 99%。&lt;/li>
&lt;/ol>
&lt;p>这些答案对我们中的任何一个来说似乎都不是很令人满意，是吗？&lt;/p>
&lt;p>对我来说，这种指标充其量只涵盖了我们所做工作的一小部分，其背后的假设非常值得怀疑。对您而言，该指标的重要性不及某个合理的数字，该数字可以在 ppt 中用来量化测试的最终效果。对此我也有一些想法：&lt;/p>
&lt;ul>
&lt;li>对我来说，测试是知识性的工作，因此很难用简单的数字来衡量&lt;/li>
&lt;li>测试不是独立于其他产品开发活动而存在的&lt;/li>
&lt;li>如果没有创建测试用例等人工产出物的话我们也可以完成良好的测试&lt;/li>
&lt;li>没有经过对话交流和正当理由而强加的指标可能会受到怀疑&lt;/li>
&lt;li>强加的指标可能会有被造假的风险&lt;/li>
&lt;li>从产出物（bug 列表，用例设计）开始是本末倒置&lt;/li>
&lt;li>&amp;hellip; 最好先问你想测量的指标是什么以及为什么&lt;/li>
&lt;/ul>
&lt;p>那么，例如，是否希望衡量客户对产品的满意度？是为了衡量测试的贡献吗？是否要查看时间花在了企业想要停止的活动上？是寻找瓶颈吗？或者是其他东西？如果我们同意使用某种指标，我们如何向测试人员保证他们没有受到不公正的评价，并且他们不应该为了使数字看更好而修饰或者歪曲他们的工作实践？&lt;/p>
&lt;p>我们需要的不仅仅是花言巧语。想象一下，你被告知你的表现将根据你发送的电子邮件数量来衡量，你会如何反应？你会在讥讽它的同时但还是发送更多的电子邮件吗？您会发送电子邮件而不是进行丁丁或者是企业微信的沟通吗？您会关心对您、他人和企业的潜在不利影响吗？别人怎么能说服你采取不同的行为？最后，您是否真的希望以良好的意图研究合理的指标并根据结果采取行动？如果是这样，那么我将尽我所能帮助获得一些合理的东西，有明确的警告，公平的，透明的，承认其收集涉及的混乱，可以有效地从数据中得出我们有，这在商定的误差范围内，这反映了我们正在做的工作。如果没有，那么我会问你什么样的数字会更好的反映出实际的工作效果？我会简单地告诉你：让我们说 69.3%，好吗？&lt;/p>
&lt;p>作者使用的英文没有那么通俗，加上翻译能力有限，所以最后可能需要简单的提炼一下文章的观点&lt;/p>
&lt;ul>
&lt;li>自动化测试的覆盖率应该不能作为汇报测试效果的指标&lt;/li>
&lt;li>测试的目的不是为了产出好看的数据或者指标&lt;/li>
&lt;li>不合理的指标会造成不合理的结果而忽略了测试工作的实质&lt;/li>
&lt;/ul></description></item><item><title>索引构造与信息检索 让ChatGPT成为Selenium问答助手</title><link>https://itest2025.pages.dev/use-chatgpt-build-selenium-qa_104/</link><pubDate>Fri, 08 Mar 2024 09:04:11 +0800</pubDate><guid>https://itest2025.pages.dev/use-chatgpt-build-selenium-qa_104/</guid><description>&lt;p>这是 chatgpt 为我生成的 3 个标题，我选了第 3 个。&lt;/p>
&lt;ol>
&lt;li>利用 Langchain 和 GPT 实现 Selenium 机器人自动问答&lt;/li>
&lt;li>向量化存储和检索：如何用相似度搜索匹配 Selenium 知识？&lt;/li>
&lt;li>索引构造与信息检索：让 ChatGPT 成为 Selenium 问答助手&lt;/li>
&lt;/ol>
&lt;p>之前有很多同学会问我一些有关 selenium 的问题，因为精力有限，不能一一回答。最近正好 chatgpt 为代表的生成式 ai 非常火爆，那么我们能不能训练一个有 selenium 知识的
机器人帮我回答问题呢？人的时间有限，但是机器人可以一直在线工作，妥妥的劳模了。&lt;/p>
&lt;p>经过一番调研，我发现 langchain 这个库也许可以祝我一臂之力。&lt;/p>
&lt;h3 id="整体思路">整体思路&lt;/h3>
&lt;p>整理思路很简单，就是 gpt 是预先训练好的模型，有自己的先见知识，这也是为什么我们问它问题它会给出答案的原因，因为它的模型里就固化了这些知识。然而 gpt3 模型用的数据应该是截止到 2021 年的，也就是 2 年多之前的数据了。尽管 selenium 总体得带速度不快，但这两年也多多少少发生了一些变化，直接用 gpt 模型的先见知识可能会造成一些回答不准确的情况。&lt;/p>
&lt;p>所以我们可以用 gpt 的两大能力，而不是先见知识。&lt;/p>
&lt;ul>
&lt;li>推断的能力。给出一些上下文，让 gpt 总结和推断问题的答案&lt;/li>
&lt;li>生成内容的能力&lt;/li>
&lt;/ul>
&lt;p>所以现在问题就变成了如何给 gpt”灌“入最近的 selenium 知识？&lt;/p>
&lt;p>其实我们可以在 gpt 的 prompt 里面加上一些实时的准确的上下文，然后让 gpt 去根据上下文内容实时的生成问题的答案，比如下面这个 prompt&lt;/p>
&lt;pre tabindex="0">&lt;code>你是selenium问答机器人，你的任务是根据我提供的selenium相关信息来回答用户提出的问题。忘掉你之前的学到的知识，只用我提供的信息去回答。如果问题超出了我提供的信息范围，你就回答不知道。

==============
selenium的一些最新的介绍
...
...
...
==============
问题：selenium是什么？
&lt;/code>&lt;/pre>&lt;p>这样一来任务就变成了&lt;strong>如何根据用户的问题搜索相关的 selenium 知识？&lt;/strong> 这里就要用到向量化存储和检索了。&lt;/p></description></item><item><title>强行为新项目写ui自动化用例是一种什么样的体验</title><link>https://itest2025.pages.dev/write-ui-test-case-for-brand-new-project_102/</link><pubDate>Fri, 08 Mar 2024 09:04:08 +0800</pubDate><guid>https://itest2025.pages.dev/write-ui-test-case-for-brand-new-project_102/</guid><description>&lt;p>最近接了一个新项目，配套有个 web 管理后台页面，尽管需求一直在迭代以及测试时间相对不宽裕，我还是决定写点自动化用例作为功能测试的补充和回归测试的输入，顺便玩一下 playwright，不在真是项目中使用一种技术其实是很难对这种技术产生深刻理解的。&lt;/p>
&lt;h3 id="项目介绍">项目介绍&lt;/h3>
&lt;p>管理后台是前后端分离的，前端用的 react 加上蚂蚁的前端组件库，后端是基于 golang 构建的微服务。其实这种项目更适合做接口测试，ui 自动化作为补充就好了。&lt;/p>
&lt;h3 id="技术选型">技术选型&lt;/h3>
&lt;p>这点很清楚，之前 benchmark 过，playwright 比 cypress 性能要好，所以直接选 playwright，另外 playwright 的 python 版本完全安装之后自带了 pytest 和一系列断言，基本上开箱即用，非常方便。另外为什么不用 js 而用 python，主要是因为我用 python 写了一点接口用例，有些数据库操作的代码可以稍微复用一下，所以统一起见用 python+playwright&lt;/p>
&lt;h3 id="第一个难点登录">第一个难点：登录&lt;/h3>
&lt;p>管理后台接的是 google auth，由于我的账号开启了二次验证，需要收验证码，所以从 ui 上输入用户名和密码登录就走不通了。不过登录的原理基本相同，就是往 cookie 里写一些东西，后面所有的请求都自动带 cookie 到后端，后端通过之前写的那些东西就可以判断用户是谁，什么时候登录失效等。知道了原理后面就是随便试试了，我的代码大概是这样写的&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">login&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">page&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">page&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">goto&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;url&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">page&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">evaluate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;()=&amp;gt; document.cookie=&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">COOKIE&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其实就是先访问被测页面，然后自动跳到登录页面，这时候去用 js 设置 cookie，之后再访问一次被测页面就可以自动登录了。&lt;/p>
&lt;p>cookie 的话可以从浏览器的开发者工具里直接拷贝出来，因为是测试环境，所以 cookie 的有效期很长，基本可以放心使用。&lt;/p>
&lt;p>至于如何定期刷新 cookie 其实也不难搞，写个浏览器插件，每次打开被测页面的时候就把 cookie 发到一个自建后台服务，这个服务就是把 cookie 存到 redis 里，在测试用例里直接访问 redis 拿最新的 cookie 就好了。&lt;/p>
&lt;h3 id="定位有点麻烦">定位有点麻烦&lt;/h3>
&lt;p>作为熟练工，定位对我来说应该不会是大问题，然而现在的前端组件层级嵌套厉害，html 的表意性不强，而且 id，name 等比较有标志性的属性也不是很多，踌躇良久之后我决定请前端同学在一些关键的组件上面加上 id 或者 name，尽管他们不是很愿意，但是我倚老卖老，还是让他们从了。&lt;/p></description></item><item><title>如何加速执行集成测试</title><link>https://itest2025.pages.dev/how-to-speed-up-integration-test_100/</link><pubDate>Fri, 08 Mar 2024 09:04:05 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-speed-up-integration-test_100/</guid><description>&lt;p>今天我看到了一个有趣的 UI 集成测试加速方法，该方法与我想的底层优化、分布式运行或多线程运行方式不同。该作者重新编排了测试用例的执行顺序，删除了一些代码，并从执行流程上进行了优化。这确实很有趣。在本文中，我将简单介绍他的原文，希望对大家有所帮助。&lt;/p>
&lt;p>我们将展示如何加速一个客户的构建过程，这样他们可以在相同的预算下多运行 40%的构建。&lt;/p>
&lt;p>我必须承认，我是单元测试的超级粉丝。我早期的一些博客文章就是关于单元测试的。&lt;/p>
&lt;p>单元测试是快速的、可预测的、而且隔离的。它们被很好地隔离在一个沙盒中，这就是它们的可预测性和快速性。&lt;/p>
&lt;p>我们推出了一个版本，用户无法进入登录页面，但这是另一个故事了。我把它命名为“一个没有集成测试和几十个单元测试的项目”。&lt;/p>
&lt;p>并不是说我完全停止写单元测试，而是我很少使用它们。大部分是用于可以安全地进行隔离测试的功能，例如一个接受各种输入并返回各种输出的函数。用集成测试来测试可能的值矩阵是不值得的。&lt;/p>
&lt;p>想象一下，有一个日历组件，用户可以输入一个日期，从一个下拉日历中选择数据，然后点击一个按钮来获得一个建议的日期。所有的日期都可以是不同的格式，这取决于用户如何输入它们。是否值得为每种可能的日期格式进行集成测试？也许不值得！&lt;/p>
&lt;p>然而，我们把输入的日期发送到一个负责解释许多不同格式的输入日期的实用函数，但它总是返回一个 Date 对象。为这个实用函数写一个单元测试将是一个很好的主意！对于整个应用来说，就不是那么回事了。&lt;/p>
&lt;p>假设你正在建立一个问题跟踪器。你有一个集成测试，打开一个对话框，试图保存一个里程碑而不指定名称。这将导致一个错误。你的另一个测试试图创建一个里程碑，但现在指定了一个名称。这不会导致错误，并显示消息“新的里程碑已保存”。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">beforeEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">createNewProject&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">it&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;cannot create milestone without a name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">clickNewMilestoneButton&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newMilestoneDialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">clickSave&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newMilestoneDialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">should&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;have.text&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Milestone name is required&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">it&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;creates a milestone&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">clickNewMilestoneButton&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newMilestonDialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getNameField&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;February Milestone&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newMilestoneDialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">clickSave&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newMilestoneDialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">should&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;have.text&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;New milestone saved&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>乍一看，上面的测试没有问题。然而，从零开始创建一个新项目涉及设置名称，填写一些起始/结束日期，键入描述，从下拉列表中选择项目所有者，为项目提供相同的标签，填写其他几个字段。我们的测试运行器（我现在使用 Cypress）必须单击所有这些字段，键入一些内容，等待一些建议出现，然后单击，再等待。&lt;/p>
&lt;p>或者，将运行时间放在注释里，像这样想象：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">beforeEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 运行10秒
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">createNewProject&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">it&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;cannot create milestone without a name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 运行2秒
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">clickNewMilestoneButton&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newMilestoneDialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">clickSave&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newMilestoneDialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">should&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;have.text&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Milestone name is required&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">it&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;creates a milestone&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 运行3秒
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">clickNewMilestoneButton&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newMilestonDialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getNameField&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nx">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;February Milestone&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newMilestoneDialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">clickSave&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newMilestoneDialog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">should&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;have.text&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;New milestone saved&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>整个测试套件运行 25 秒（2x10 + 2 +3），但我们感兴趣的事情只占了其中的 5 秒（2 + 3）。&lt;/p></description></item><item><title>放弃postman？一个月4k star？接口测试工具hoppscotch评测</title><link>https://itest2025.pages.dev/hoppscotch-is-a-great-postman-alternative_99/</link><pubDate>Fri, 08 Mar 2024 09:04:03 +0800</pubDate><guid>https://itest2025.pages.dev/hoppscotch-is-a-great-postman-alternative_99/</guid><description>&lt;p>天下苦 postman 久矣！&lt;/p>
&lt;p>记得当初 potman 刚横空出世时，其形态只是一个浏览器扩展而已，尽管功能简单，不过却带来了另一个非常大的优势，那就是软件体积非常小，安装到浏览器上以后可以借助于浏览器同步的功能，实现各种跨平台支持，特别是对我我这种拥有 win/mac/iinux 的人来说，方便快捷是第一位的。&lt;/p>
&lt;p>后来 postman 推出了独立的桌面版本，功能逐渐迭代，性能差的慢慢变态，现在劝退我的是两点：启动速度慢和我用不到的功能慢慢变多；当然除了这两点外还有个相当大的槽点是：postman 会想方设法让你登录，如果你不小心使用了同步功能的话，你的测试文件会公开分享到 postman 上供人品评，这是一个巨大的安全隐患。&lt;/p>
&lt;p>D 轮融了 2.25 亿美金，postman 注定要在商业化的道路上越走越远，注定会增加很多我不需要的功能，各种同步，花式协作，满屏的效率提升，不厌其烦的提示我升级等等，对我来说其实需求很简单，只要可以让我朴素的调试接口就可以了。&lt;/p>
&lt;p>于是各种 postman 的替代工具应运而生，比如 postwoman，insomnia 等等，这种工具的技术栈都差不多，都是用 js 开发的类似于原生客户端的跨平台工具，今天给大家带来的是一款很火的开源 postman 替代工具: hoppscotch，这个工具在 github 上目前有 40,000 的 star，3 月份新增 4000 的 star，应该是目前最火的测试工具了。&lt;/p>
&lt;h3 id="安装">安装&lt;/h3>
&lt;p>hoppscotch 只需要安装一个浏览器扩展就可以了，支持 chrome 和 firefox。比 postman 动则上百兆的安装包来说，安装过程简单了不少。&lt;/p>
&lt;p>安装好扩展之后访问&lt;a href="https://hoppscotch.io/" target="_blank" rel="noopener noreffer ">https://hoppscotch.io/&lt;/a>就可以使用了。&lt;/p>
&lt;h3 id="ui">UI&lt;/h3>
&lt;p>hoppscotch 的界面跟 postman 差不多，会用 postman 的同学应该会感到比较亲切。&lt;/p>
&lt;h3 id="功能">功能&lt;/h3>
&lt;p>功能上 hoppscotch 也跟 postman 不相上下&lt;/p>
&lt;ul>
&lt;li>支持 rest api 调试&lt;/li>
&lt;li>支持 GraphQL 语法&lt;/li>
&lt;li>支持 websocket 和 socket.io&lt;/li>
&lt;li>支持从 collection 生成文档，这个我不会用&lt;/li>
&lt;li>支持 collection 的创建及导出&lt;/li>
&lt;li>支持多种 Authorization 方式&lt;/li>
&lt;li>支持 pre-request script&lt;/li>
&lt;li>支持断言，跟 postman 的写法不能说很像，只能说是一摸一样&lt;/li>
&lt;li>完善的快捷键支持&lt;/li>
&lt;li>支持 pwa，轻量化的网页解决方案，让网页应用的体验跟 desktop 一样，再也不用忍受 postman 的龟速启动了&lt;/li>
&lt;li>支持 proxy，支持自建 proxy&lt;/li>
&lt;li>个性化定制：白天模式和暗夜模式，各种颜色主题，可以调整字体大小&lt;/li>
&lt;li>支持 cli，这个真是没想到，看了一眼是 go 写的，功能有限，不过可以在命令行运行 collection 了，不过似乎不支持 websocket&lt;/li>
&lt;li>完全开源，前端应该是 vue 写的，有开发能力的同学可以进行定制&lt;/li>
&lt;/ul>
&lt;h3 id="部署">部署&lt;/h3>
&lt;p>我们可以在远程服务器上部署个 hoppscotch 版本，然后远程进行访问。不过用 docker 部署的话似乎有点问题，就是浏览器插件无法识别的问题，于是就需要部署个 proxy 用来转发请求，这样就会出现本地 localhost 无法解析的问题，所以如果不是必须的话，用 pwa 的版本体验上就已经很好了。&lt;/p></description></item><item><title>跟AI一起结对编程，测试驱动开发</title><link>https://itest2025.pages.dev/tdd-with-ai_98/</link><pubDate>Fri, 08 Mar 2024 09:04:02 +0800</pubDate><guid>https://itest2025.pages.dev/tdd-with-ai_98/</guid><description>&lt;p>&lt;a href="https://martinfowler.com" target="_blank" rel="noopener noreffer ">martin fowler&lt;/a>的博客上新发了一篇关于 AI 和 TDD 的&lt;a href="https://martinfowler.com/articles/exploring-gen-ai.html" target="_blank" rel="noopener noreffer ">文章&lt;/a>，先翻译一下，然后聊聊我的看法。&lt;/p>
&lt;p>使用测试驱动开发(TDD)与 GitHub Copilot 编码助手&lt;/p>
&lt;p>AI 编程助手如 GitHub Copilot 的出现意味着我们不再需要测试了吗?TDD 将过时吗?为了回答这个问题,我们来看一下 TDD 如何帮助软件开发的两点:提供良好的反馈,以及在解决问题时“分治法”。&lt;/p>
&lt;p>TDD 提供良好的反馈
良好的反馈要快速和准确。在这两方面,没有什么能比一个编写良好的单元测试更好。不论是手动测试,文档,代码审查,甚至是生成式 AI,都不能取代。事实上,大型语言模型提供无关信息,甚至 hallucinate。当使用 AI 编程助手时,尤其需要 TDD。我们需要对自己编写的代码快速准确的反馈,也同样需要对 AI 编程助手编写的代码快速准确的反馈。&lt;/p>
&lt;p>TDD 通过分治法解决问题
通过分治法解决问题意味着较小的问题可以比较大的问题更早解决。这实现了持续集成,基于主线的开发,最终实现持续交付。但是如果 AI 助手为我们编写了代码,我们是否真的还需要所有这些?&lt;/p>
&lt;p>是的。大语言模型很少能在一次提示后就提供我们所需的确切功能。所以迭代开发还没有走到尽头。此外,大语言模型似乎在通过思路提示的增量方式解决问题时“激发了推理”(参见相关研究)。基于 LLM 的 AI 编程助手在分治法解决问题时表现最好,而 TDD 是我们在软件开发中所做的。&lt;/p>
&lt;h3 id="使用-github-copilot-的-tdd-技巧">使用 GitHub Copilot 的 TDD 技巧&lt;/h3>
&lt;p>Thoughtworks 从年初开始就一直在使用带有 TDD 的 GitHub Copilot。我们的目标是实验、评估和发展一系列围绕使用该工具的有效实践。&lt;/p>
&lt;h4 id="0-开始">0. 开始&lt;/h4>
&lt;p>从一个空白的测试文件开始并不意味着从一个空白的上下文开始。我们通常从一个带一些粗略笔记的用户故事开始。我们也会与配对伙伴讨论一个起点。&lt;/p>
&lt;p>所有这些上下文都是 Copilot 在我们把它放入一个打开的文件之前(例如测试文件顶部)“看不到”的。Copilot 可以处理拼写错误、点式格式、糟糕的语法等等。但是它无法处理一个空白文件。&lt;/p>
&lt;p>一些对我们有效的启动上下文示例:&lt;/p>
&lt;ul>
&lt;li>ASCII 艺术画布&lt;/li>
&lt;li>验收标准&lt;/li>
&lt;li>引导假设,例如:&lt;/li>
&lt;li>&amp;ldquo;不需要 GUI&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;使用面向对象编程&amp;rdquo;(而不是函数式编程)
Copilot 使用打开的文件作为上下文,所以同时保持测试文件和实现文件打开(例如并排)大大提高了 Copilot 的代码补全能力。&lt;/li>
&lt;/ul>
&lt;h4 id="1-红色">1. 红色&lt;/h4>
&lt;p>我们首先编写一个描述性的测试示例名称。名称越描述性越好,Copilot 代码补全的表现也越好。&lt;/p></description></item><item><title>grpc简介</title><link>https://itest2025.pages.dev/what-is-grpc_97/</link><pubDate>Fri, 08 Mar 2024 09:04:00 +0800</pubDate><guid>https://itest2025.pages.dev/what-is-grpc_97/</guid><description>&lt;h3 id="rpc">RPC&lt;/h3>
&lt;p>试想这样一种场景，一个复杂系统中的两个模块之前需要互相调用，一般的做法是什么？&lt;/p>
&lt;p>可能这两个模块是跑在同一个进程上，那么通信起来其实是非常方便的，也有可能这两个模块分别是跑在不同的进程之上，那么就涉及到复杂一点的跨进程通信的技术了。但这些都是模块部署在同一机器下的情景，大家想象起来也会比较容易。&lt;/p>
&lt;p>更加深入一些，如果两个模块跑在不同的机器之间，那么模块之前的调用如何实现呢？这就需要使用 RPC 技术了。&lt;/p>
&lt;p>RPC（Remote Procedure Call）— 远程过程调用，它是一种通过网络从远程计算机程序上请求服务，而不需要了解底层网络技术的协议。RPC 协议假定某些传输协议的存在，如 TCP 或 UDP，为通信程序之间携带信息数据。在 OSI 网络通信模型中，RPC 跨越了传输层和应用层。RPC 使得开发包括网络分布式多程序在内的应用程序更加容易。&lt;/p>
&lt;p>RPC 采用客户端/服务器模式。请求程序就是一个客户端，而服务提供程序就是一个服务器。首先，客户端调用进程发送一个有进程参数的调用信息到服务进程，然后等待应答信息。在服务器端，进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达，服务器获得进程参数，计算结果，发送答复信息，然后等待下一个调用信息，最后，客户端调用进程接收答复信息，获得进程结果，然后调用执行继续进行。&lt;/p>
&lt;p>简单来说 RPC 需要 server 端和 client 端，server 端定义一些函数，client 端通过网络请求去调用这些函数拿到返回值。server 端和 client 端跑在不同的机器上，结合微服务的概念就是 server 端就是一个独立的微服务，其他微服务需要通过启动 client 端来调用该微服务提供的服务。&lt;/p>
&lt;h3 id="grpc">gRPC&lt;/h3>
&lt;p>gRPC 一开始由 Google 开发，是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。&lt;/p>
&lt;p>在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法，使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似，gRPC 也是基于以下理念：定义一个服务，指定其能够被远程调用的方法（包含参数和返回类型）。在服务端实现这个接口，并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。&lt;/p>
&lt;h3 id="组成">组成&lt;/h3>
&lt;p>典型的 grpc 实现有两端组成，分别是&lt;/p>
&lt;ul>
&lt;li>server&lt;/li>
&lt;li>client&lt;/li>
&lt;/ul>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="grpc%E7%AE%80%E4%BB%8B%2082e57cd2e8434314addbe47d6725a434/Untitled.png"
 data-srcset="grpc%E7%AE%80%E4%BB%8B%2082e57cd2e8434314addbe47d6725a434/Untitled.png, grpc%E7%AE%80%E4%BB%8B%2082e57cd2e8434314addbe47d6725a434/Untitled.png 1.5x, grpc%E7%AE%80%E4%BB%8B%2082e57cd2e8434314addbe47d6725a434/Untitled.png 2x"
 data-sizes="auto"
 alt="grpc%E7%AE%80%E4%BB%8B%2082e57cd2e8434314addbe47d6725a434/Untitled.png"
 title="grpc%E7%AE%80%E4%BB%8B%2082e57cd2e8434314addbe47d6725a434/Untitled.png" />&lt;/p>
&lt;h3 id="grpc-的特性">gRPC 的特性&lt;/h3>
&lt;ul>
&lt;li>由于 client 和 server 需要通过网络进行消息的传递，那么网络协议成了 grpc 里重要的一环。grpc 协议是 HTTP/2，这是一种优化过的 http 协议，实现了连接多路复用、双向流、服务器推送、请求优先级、首部压缩等机制。可以节省带宽、降低 TCP 链接次数、节省 CPU，帮助移动设备延长电池寿命等。&lt;/li>
&lt;li>服务端向外提供了一些可供调用的函数，这些函数的原型通过 ProtoBuf 协议来进行定义。ProtoBuf 是由 Google 开发的一种数据序列化协议（类似于 XML、JSON、hessian）。ProtoBuf 能够将数据进行序列化，并广泛应用在数据存储、通信协议等方面。压缩和传输效率高，语法简单，表达力强。&lt;/li>
&lt;li>支持多种编程语言。比如支持 golang/java/c++/ruby/python/nodejs 等。&lt;/li>
&lt;/ul>
&lt;h3 id="grpc-的优点">gRPC 的优点&lt;/h3>
&lt;ul>
&lt;li>使用 protobuf 进行消息的序列化，压缩率高，性能好，毕竟压缩的越小在网络上传播的速度就相对会更快一点&lt;/li>
&lt;li>序列化反序列化直接对应程序中的数据类，不需要解析后在进行映射，其实除了可读性差之外，pb 的使用方式跟 json 基本都差不多了&lt;/li>
&lt;li>支持向前兼容和向后兼容，升级比较简单&lt;/li>
&lt;li>支持多语言&lt;/li>
&lt;/ul>
&lt;h3 id="典型的-grpc-实现">典型的 gRPC 实现&lt;/h3>
&lt;p>典型的 gRPC 实现有 3 个部分，分别是&lt;/p></description></item><item><title>QA要像消防队一样</title><link>https://itest2025.pages.dev/qa-team-as-a-fire-department_96/</link><pubDate>Fri, 08 Mar 2024 09:03:59 +0800</pubDate><guid>https://itest2025.pages.dev/qa-team-as-a-fire-department_96/</guid><description>&lt;p>原文地址: &lt;a href="https://medium.com/@dstekanov.tech/qa-team-as-a-fire-department-9b5abd470e44" target="_blank" rel="noopener noreffer ">https://medium.com/@dstekanov.tech/qa-team-as-a-fire-department-9b5abd470e44&lt;/a>&lt;/p>
&lt;p>翻译及总结如下。&lt;/p>
&lt;p>作者是一名有十多年经验的 QA 工程师，作者曾经在不同的公司和项目中工作过，从小型的创业公司到大型的跨国公司，从传统的水瀑式方法到现代的敏捷方法。在这些年里，作者观察到了 QA 团队在软件开发过程中扮演着不同的角色和职责，有时候是成功的，有时候是失败的。作者想分享一下作者对 QA 团队的看法和建议，希望能对那些想要提高 QA 团队效率和价值的人有所帮助。&lt;/p>
&lt;p>作者喜欢用消防队来比喻 QA 团队。消防队是一个重要的组织，它可以在紧急情况下保护人们和财产免受火灾的危害。消防队员需要具备以下几个特点：&lt;/p>
&lt;ul>
&lt;li>快速反应：消防队员需要能够在第一时间到达火灾现场，并迅速采取措施控制火势。&lt;/li>
&lt;li>高效协作：消防队员需要能够和其他消防队员以及其他救援人员密切合作，共同完成救援任务。&lt;/li>
&lt;li>专业技能：消防队员需要掌握各种消防设备和技术，以及如何应对不同类型和规模的火灾。&lt;/li>
&lt;li>持续学习：消防队员需要不断地更新自己的知识和技能，以适应不断变化的环境和需求。&lt;/li>
&lt;/ul>
&lt;p>类似地，QA 团队也是一个重要的组织，它可以在软件开发过程中保证产品的质量和性能。QA 工程师也需要具备以下几个特点：&lt;/p>
&lt;ul>
&lt;li>快速反馈：QA 工程师需要能够在第一时间发现并报告产品中存在的问题，并提供有效的解决方案。&lt;/li>
&lt;li>高效沟通：QA 工程师需要能够和开发工程师以及其他利益相关者密切沟通，共享信息和反馈。&lt;/li>
&lt;li>专业技能：QA 工程师需要掌握各种测试工具和技术，以及如何针对不同类型和规模的产品进行测试。&lt;/li>
&lt;li>持续学习：QA 工程师需要不断地更新自己的知识和技能，以适应不断变化的环境和需求。&lt;/li>
&lt;/ul>
&lt;p>基于这个比喻，作者想给出以下几点建议，来帮助 QA 团队更好地发挥自己的作用和价值&lt;/p>
&lt;ul>
&lt;li>QA 团队应该和开发团队紧密合作，共享信息和反馈，而不是互相指责或隔离。这样可以提高沟通效率，减少误解和冲突，提升产品质量。作者举例说，他曾经在一个项目中，QA 团队和开发团队使用同一个工具来管理问题和需求，这样可以及时地交流和解决问题，避免了重复工作和延误。&lt;/li>
&lt;li>QA 团队应该使用自动化测试工具，提高测试效率和覆盖率，而不是依赖人工测试或过时的方法。这样可以节省时间和资源，发现更多的问题，提高测试质量。作者举例说，他曾经在一个项目中，QA 团队使用了自动化测试框架来执行回归测试，这样可以在短时间内完成大量的测试用例，而不需要人工干预。&lt;/li>
&lt;li>QA 团队应该关注用户的需求和体验，而不是只关注技术细节或功能。这样可以更好地满足用户的期望，增加用户的满意度，提高产品价值。作者举例说，他曾经在一个项目中，QA 团队使用了用户故事来定义测试场景，这样可以从用户的角度来验证产品的功能和性能，而不是只从技术的角度来检查。&lt;/li>
&lt;li>QA 团队应该持续地学习和改进，跟上行业的发展和变化，而不是停滞不前或满足现状。这样可以提高自身的能力和知识，适应不同的环境和需求，保持竞争力和创新力。作者举例说，他曾经在一个项目中，QA 团队使用了敏捷方法来进行测试活动，这样可以灵活地调整测试计划和策略，根据实际情况来优化测试过程。&lt;/li>
&lt;/ul>
&lt;p>作者最后总结说，QA 团队是一个重要的角色，它可以帮助产品成功地上市和运行，但是它也需要不断地适应和创新，才能保持竞争力和价值。&lt;/p></description></item><item><title>在软件开发和测试中为失败而设计</title><link>https://itest2025.pages.dev/designing-for-failure-in-software-development-and-testing_95/</link><pubDate>Fri, 08 Mar 2024 09:03:57 +0800</pubDate><guid>https://itest2025.pages.dev/designing-for-failure-in-software-development-and-testing_95/</guid><description>&lt;p>原文地址: &lt;a href="https://medium.com/@peterdtitan/designing-for-failure-in-software-development-and-testing-9f3f2d0bbd7b" target="_blank" rel="noopener noreffer ">https://medium.com/@peterdtitan/designing-for-failure-in-software-development-and-testing-9f3f2d0bbd7b&lt;/a>&lt;/p>
&lt;p>软件开发是一个复杂的过程，涉及到许多不同的技术、工具、方法和人员。在这个过程中，失败是不可避免的，甚至是必要的，因为它可以帮助我们发现错误、改进质量和提高效率。然而，并不是所有的失败都是有益的，有些失败可能会导致严重的后果，如数据丢失、系统崩溃、客户不满或法律责任。因此，我们需要在软件开发和测试中为失败而设计，即预见可能发生的失败情况，并采取相应的措施来防止、检测、恢复和学习。&lt;/p>
&lt;h3 id="为失败而设计的原则">为失败而设计的原则&lt;/h3>
&lt;p>为失败而设计并不意味着我们要故意制造失败，而是要在开发过程中考虑到失败的可能性，并采用一些原则来应对它们。以下是一些常见的为失败而设计的原则：&lt;/p>
&lt;ul>
&lt;li>容错：容错是指让系统能够在出现错误时继续正常运行，而不是崩溃或停止工作。容错可以通过多种方式实现，如使用异常处理、重试机制、备份系统、冗余组件等。&lt;/li>
&lt;li>隔离：隔离是指将系统分割成不同的模块或服务，使得一个模块或服务的失败不会影响其他模块或服务的正常运行。隔离可以通过多种方式实现，如使用微服务架构、消息队列、断路器模式等。&lt;/li>
&lt;li>降级：降级是指当系统无法提供完整的功能或性能时，能够提供一些基本的或替代的功能或性能，以满足用户的最低需求。降级可以通过多种方式实现，如使用缓存、静态页面、功能开关等。&lt;/li>
&lt;li>监控：监控是指收集和分析系统的运行状态、性能指标、错误日志等信息，以便及时发现和解决问题。监控可以通过多种方式实现，如使用日志系统、度量系统、报警系统等。&lt;/li>
&lt;li>反馈：反馈是指向用户或开发者提供有关系统状态或问题的信息，以便他们能够做出适当的响应或改进。反馈可以通过多种方式实现，如使用用户界面、通知系统、报告系统等。&lt;/li>
&lt;/ul>
&lt;h3 id="为失败而设计的好处">为失败而设计的好处&lt;/h3>
&lt;p>为失败而设计可以带来许多好处，如：&lt;/p>
&lt;ul>
&lt;li>提高系统的可用性和可靠性：通过容错、隔离和降级等手段，可以使系统在出现错误时仍能保持运行，从而减少停机时间和损失。&lt;/li>
&lt;li>提高系统的可扩展性和可维护性：通过隔离和监控等手段，可以使系统更容易分布式部署和管理，从而适应不同的负载和环境。&lt;/li>
&lt;li>提高用户和开发者的满意度和信任度：通过监控和反馈等手段，可以使用户和开发者更清楚地了解系统的状态和问题，并及时得到解决方案或建议。&lt;/li>
&lt;/ul>
&lt;h3 id="为失败而设计的挑战">为失败而设计的挑战&lt;/h3>
&lt;p>为失败而设计也有一些挑战，比如增加系统的复杂度和成本：为了实现容错、隔离、降级等功能，需要增加额外的代码、配置、测试和部署&lt;/p></description></item><item><title>五款最值得日常使用的命令行应用</title><link>https://itest2025.pages.dev/5-everyday-cli_93/</link><pubDate>Fri, 08 Mar 2024 09:03:54 +0800</pubDate><guid>https://itest2025.pages.dev/5-everyday-cli_93/</guid><description>&lt;p>、&lt;/p>
&lt;p>命令行应用很多时候可以提升我们的工作效率，这里给大家推荐 4 款常见好用的命令行应用，希望对大家有所帮助。&lt;/p>
&lt;h3 id="vim">vim&lt;/h3>
&lt;p>大名鼎鼎的命令行编辑器，有时间的同学都可以尝试一下。&lt;/p>
&lt;p>说起来比较惭愧，当初学习 vim 的原因有两个。第一个是十多年前的室友表示 vim 这种工具的学习成本底，因为学会以后键位几十年不变，学一次用终生，性价比极高，尽管入门的时候学习曲线非常的陡峭，甚至有点反直觉。第二个理由是学习 vim 可以让我比较方便的在线上环境改代码，是的，你没看错，很多年前我们用 php 的时候确实做过线上调试和改代码的极限操作。&lt;/p>
&lt;p>如今随着运维的规范以及自动化发布的普及，线上改代码这种高危操作应该是被严令禁止了。不过使用 vim 仍然可以让你在任意机器 ssh 进远程服务器进行代码的编写和执行。配合上自定义的配置和第三方的插件，vim 也是日常代码编辑的一个不错选择。&lt;/p>
&lt;p>另外很多编辑器都支持 vim 键位，比如 atom，vscode，这会让你在写代码的时候更有如鱼得水的感觉。&lt;/p>
&lt;h3 id="tmux">tmux&lt;/h3>
&lt;p>tmux 之前是运维同学的钟意之物，因为该工具可以&lt;/p>
&lt;ul>
&lt;li>分屏，将 1 个 terminal 分成多个部分&lt;/li>
&lt;li>独立运行 session，每个部分都是独立的会话，互不干涉&lt;/li>
&lt;li>快照，任意时刻退出 terminal，tmux 都会保存当前会话，下一次可以无缝恢复&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>命令行的典型使用方式是，打开一个终端窗口（terminal window，以下简称&amp;quot;窗口&amp;quot;），在里面输入命令。用户与计算机的这种临时的交互，称为一次&amp;quot;会话&amp;quot;（session） 。
会话的一个重要特点是，窗口与其中启动的进程是连在一起的。打开窗口，会话开始；关闭窗口，会话结束，会话内部的进程也会随之终止，不管有没有运行完。
一个典型的例子就是，SSH 登录远程计算机，打开一个远程窗口执行命令。这时，网络突然断线，再次登录的时候，是找不回上一次执行的命令的。因为上一次 SSH 会话已经终止了，里面的进程也随之消失了。
为了解决这个问题，会话与窗口可以&amp;quot;解绑&amp;quot;：窗口关闭时，会话并不终止，而是继续运行，等到以后需要的时候，再让会话&amp;quot;绑定&amp;quot;其他窗口。&lt;/p>&lt;/blockquote>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="%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"
 data-srcset="%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, %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 1.5x, %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 2x"
 data-sizes="auto"
 alt="%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"
 title="%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" />&lt;/p>
&lt;blockquote>
&lt;p>Tmux 就是会话与窗口的&amp;quot;解绑&amp;quot;工具，将它们彻底分离。
（1）它允许在单个窗口中，同时访问多个会话。这对于同时运行多个命令行程序很有用。
（2） 它可以让新窗口&amp;quot;接入&amp;quot;已经存在的会话。
（3）它允许每个会话有多个连接窗口，因此可以多人实时共享会话。
（4）它还支持窗口任意的垂直和水平拆分。&lt;/p>&lt;/blockquote>
&lt;p>tmux 对于我来说的典型用法就是&lt;/p>
&lt;ul>
&lt;li>把一个窗口分成几块，小的窗口运行 mysql，redis 之类的服务&lt;/li>
&lt;li>用一个窗口运行主服务，比如 python 的 flask 开发服务器&lt;/li>
&lt;li>用最大的窗口来运行 vim 编辑器，做代码的编辑&lt;/li>
&lt;/ul>
&lt;p>这样任意时刻我退出 ssh，上面的这些服务都会一直运行，下次我再 ssh 上去的话就可以无缝的继续工作了。&lt;/p></description></item><item><title>gemini在自动化测试中的潜力</title><link>https://itest2025.pages.dev/gemini-for-test_91/</link><pubDate>Fri, 08 Mar 2024 09:03:51 +0800</pubDate><guid>https://itest2025.pages.dev/gemini-for-test_91/</guid><description>&lt;p>昨天 google 发布了全新的 ai 模型 gemini，在视频里 google 演示了 gemini 近乎神奇的实时反馈能力，比如测试人员画一只鸭子，ai 可以立即识别，以及后面的一些互动小游戏中，gemini 展示出了出人意料的精准度和推理能力，这是令我印象最为深刻的多模态模型的实时演示了。&lt;/p>
&lt;p>珠玉在前，gpt4 的文本类型的推理能力已经到达了一个相对可用的状态，基于该能力，我们也看到了一些比较有意思的自动化类型的项目，比如通过简化 html 输入大语言模型，让大语言模型去推断用户想要进行的操作，最终调用浏览器 api 进行自动化操作的项目，以及利用 llm 的解释器能力，一步一步推理并进行浏览器操作的项目，这些项目都非常有创意，也有一定的实用性，但是缺乏完善的多模态支持总让人觉得想象空间可能没有变得非常广阔。gemini 的出现，强大的推理能力，代码生成能力以及多模态能力的结合，也许会为自动化测试领域带来翻天覆地的变化。&lt;/p>
&lt;h3 id="ui-自动化">UI 自动化&lt;/h3>
&lt;p>这是难度最高的自动化测试类型，比如浏览器和 app 的自动化，尽管概念出现的非常早，而且也发展了相当长的一段时间，但是 ui 自动化的普及度却相对来说不算太高，在资源和时间有限的情况下，ui 自动化测试往往是被大家优先降本增效的不二选择。&lt;/p>
&lt;p>类型 gemini 的大语言模型的出现，可能会给 ui 自动化带来革命性的变化。今后，我们可以直接向 ai 输入一些描述相对明确的测试用例，当然，是用自然语言编写的，ai 可以推断出我们的测试意图，还原测试步骤，最终将测试步骤转化成测试代码，自动运行，通过运行中提供的截图和代码报错信息自动分析结果，自动迭代和修正代码错误，直到输出最终代码，基本上 ui 自动化是可以完美闭环的。&lt;/p>
&lt;p>也就是说也许不久之后我们只需要写好手工测试用例就可以完成相对完美的自动化测试工作了，以后的测试人员可能不再需要每个用例都去执行一遍，执行的工作应该可以被 ai 和机器替代绝大部分。&lt;/p>
&lt;p>顺便再考虑一下测试用例，如果 ai 的推断能力足够强的话，ai 应该可以写出大部分的测试用例，这时候测试人员只需要补充一些极端场景的用例就可以了。&lt;/p>
&lt;p>那测试用例是从哪里来呢？可能是从产品文档里分析出来的，同样，如果模型精通多模态和推断的话，那么产品文档和产品设计图也是可以用 ai 去生成一大部分的，人工只要负责查缺补漏和提出修改意见就好了。同理，代码的话 ai 也能写个大部分，也许今后我们的工作方式会发生巨大的转变，从单纯的跟人合作变成跟人和 ai 一起协作，通用知识和能力将变得廉价，垂类的信息和领域知识可能会越来越值钱。&lt;/p>
&lt;h3 id="接口自动化">接口自动化&lt;/h3>
&lt;p>接口自动化可能不太需要多模态，基于模型的推理和生成能力，如果模型支持海量的上下文输入的话，那么我们是完全可以为接口自动化的用例定义规则，并教会模型如何应用这些规则生成测试用例。&lt;/p>
&lt;p>举个例子，如果我们 curd 的接口遵循 restful 规则的话，ai 是完全有能力生成 restful 接口的测试用例的，毕竟 restful 的规则比较清晰，而且对于具体资源来说接口和用例的数量都是可以枚举的。&lt;/p>
&lt;p>因此对于一些相对简单的接口，用 ai 去编写用例从直觉上来说是可行的，不过对于复杂接口，比如一些接口有很繁杂的前置依赖和操作步骤，ai 自动根据接口文档的话可能不太好去推断各种具体场景，这种情况下我们可能需要一种接近自然语言的简化版中间语言，这种语言描述一些接口的输入输出以及构造复杂的接口测试场景，ai 根据这种 DSL 去生成代码的话可能效率和准确性都要更高一点。&lt;/p></description></item><item><title>测试并不能改进产品质量</title><link>https://itest2025.pages.dev/testing-doesnt-improve-product-michael-bolton_90/</link><pubDate>Fri, 08 Mar 2024 09:03:50 +0800</pubDate><guid>https://itest2025.pages.dev/testing-doesnt-improve-product-michael-bolton_90/</guid><description>&lt;p>今天看到了有人给出了这样的观点，那就是&lt;strong>测试并不能改进产品质量&lt;/strong>，觉得挺有意思的，所以把作者的原文翻译一下，希望对大家有所裨益。&lt;/p>
&lt;p>在世界上有一种天经地义的看法，那就是在软件开发过程的早期预防问题会比在测试后期发现问题带来更高质量的产品,但那不是真的。&lt;/p>
&lt;p>这是不真实的，但不是因为大多数人可能首先想到的原因。问题不在于尽早解决问题是个坏主意。这通常是一个非常好的主意。&lt;/p>
&lt;p>问题是，测试自身不会导致更高质量的产品，在所有。问题预防和测试是不同的追求。测试不能预防问题，测试不能改进产品。&lt;/p>
&lt;p>对我而已——一名教师和熟练测试的倡导者——这可能看起来很疯狂，但这是真的。称体重不会让你减肥。验血不会让你更健康。学校的标准化考试不会让孩子更聪明，当然也不会提高教育质量。&lt;/p>
&lt;p>测试可以做的是通过质疑产品以对其进行评估来提高我们对可能摆在我们面前的事物的理解和意识。测试——通过体验、探索和试验了解产品来评估产品的过程——帮助我们意识到可能需要解决的问题。&lt;/p>
&lt;p>在日常生活中，浴室秤上的某个特定读数可能会促使我们更加谨慎地进食，或者进行更多的锻炼。验血可能会促使医生开抗疟疾药物。这些标准化的学校考试可能会建议改变课程、教育资金或教师培训。但在有人采取行动之前，测试只会提高对现状的认识，而不是情况本身。&lt;/p>
&lt;p>在软件开发中，除非有人解决测试帮助我们发现的问题，否则改进不会发生。当然，如果问题没有被发现，改进的可能性就会大大降低——这就是测试如此重要的原因。测试帮助我们了解我们拥有的产品，因此我们可以判断是否是我们想要的产品。在需要改进的地方，测试揭示了改进的必要性。&lt;/p>
&lt;p>有些人认为测试要求我们操作产品，这是软件开发流程的一部分。确实这是是一种非常重要的测试，但也只是产品测试的一种方式而已。&lt;/p>
&lt;p>将产品更广泛地理解为某人生产的东西会很有帮助。这意味着我们可以有单元，模块，原型等多种手段的测试。&lt;/p>
&lt;p>尽管我们通常将其称为审查，但我们确实可以在产品开发之前就进行全方位的思考，在这种情况下，产品其实就是之前所有的讨论，设计和思考的产出物，测试其实在产品的研发的初期就开始了。&lt;/p>
&lt;p>测试的结果是通过这些活动进行的评估和学习。这种学习可以应用于产品——但它是响应测试而发生的事情，而不是测试本身，这是可以改进产品。&lt;/p>
&lt;p>正如测试不能改进产品一样，测试也不能防止问题。作为测试人员，我们有一个持久的信念，即我们被要求测试的任何东西都已经存在问题。也就是说，产品中的问题在我们遇到之前就已经存在。&lt;/p>
&lt;p>那么，如果测试不能预防问题，那又有什么用呢？测试可以帮助我们意识到存在的问题，然后人们可以进行更改以防止这些问题进一步发展。&lt;/p>
&lt;p>在开发早期尝试预防问题是一个好主意。如果尝试能够成功，我们就更有可能开发出高质量的产品。尽管如此，即使是高度规范的开发过程也可能会出现问题。至少有两种方法可以确定是否发生了这种情况。&lt;/p>
&lt;p>第一种方法是全程测试产品。通过与一系列客户互动并了解他们所做的事情，确定我们开发的东西就是客户想要的。通过检查和讨论，测试那些最初模糊然后却越来越清晰的设计。&lt;/p>
&lt;p>另一种方式是在代码开发的初级就做单元测试，然后在后续的构建步骤里进行集成测试，code review，静态代码扫描和自动化测试，从而找出组件自身的问题或者是配置的问题。这些形式的测试通常不是很深入——这是一件好事，因为深度测试可能需要时间、精力和准备，这可能会对开发人员造成更多负担。&lt;/p>
&lt;p>在开发人员测试的同时，测试人员专注于并执行深度测试。深度测试的目标是测试少见的、隐藏的、微妙的、偶尔发生的、紧急的错误，这些错误可以通过手工测试或者探索性测试来发现。&lt;/p>
&lt;p>如果您的问题预防和问题缓解策略是成功的，并且你的代码和产品是可测试的，那你不太可能在测试后期遇到一些显而易见的低级问题。如果您不需要测试和报告这些问题，那么后续的深度测试会相对更快、更容易。&lt;/p>
&lt;p>如果您的问题预防和问题缓解策略不成功，全面和深入测试是找出问题的一种方法。您发现的问题可以解决；可以改进产品，并且可以在产品交付之前防止业务和客户出现问题。&lt;/p>
&lt;p>当人们面临损失、伤害、不良情绪或价值降低的风险时，最好在为时已晚之前意识到问题，这就是测试的终极意义之所在。&lt;/p>
&lt;p>测试本身既不能防止问题也不能改进产品。但是测试确实可以预测需要预防的问题，并且测试可以照亮产品需要改进的地方。&lt;/p>
&lt;p>因为全文大部分是机翻，所以行文肯定有不流畅的地方，另外加上作者本身的笔法比较曲折，有一些点机器翻译可能难以驾驭，因此全文可能看起来观点比较分散，没有强一致性，不过大家可以简单的意会，忽略那些遣词造句里的机器感。&lt;/p>
&lt;p>总而言之作者的观点其实并不离经叛道，而且还是有一定的道理的，比如癌症筛查并不能治疗癌症，不过如果你尽可能早的可以通过筛查确定自身的风险，提前治疗和预防，那么癌症很有可能将不会发生。测试也是如此，测的再多开发和产品不去修改，那么产品本身的质量也不会得到改进，但是如果我们频繁的提前的进行测试，那么我们就可以尽早的暴露问题，我们的开发和产品也就会有足够的时间去进行修复工作。这也是为什么我们总强调 bug 生命周期的原因，测试并不只是简单的提 bug，而是要尽早提 bug，并保证 bug 得到及时修复并得到验证。&lt;/p>
&lt;p>原文地址：&lt;a href="https://www.linkedin.com/pulse/testing-doesnt-improve-product-michael-bolton?utm_campaign=Software%2BTesting%2BWeekly&amp;amp;utm_medium=email&amp;amp;utm_source=Software_Testing_Weekly_96" target="_blank" rel="noopener noreffer ">Testing Doesn&amp;rsquo;t Improve the Product (linkedin.com)&lt;/a>&lt;/p></description></item><item><title>使用重试模式来提升自动化测试的稳定性</title><link>https://itest2025.pages.dev/enhancing-automation-reliability-with-retry-patterns_88/</link><pubDate>Fri, 08 Mar 2024 09:03:47 +0800</pubDate><guid>https://itest2025.pages.dev/enhancing-automation-reliability-with-retry-patterns_88/</guid><description>&lt;p>看到一篇非常好的关于在自动化测试中引入重试模式的文章，https://www.thegreenreport.blog/articles/enhancing-automation-reliability-with-retry-patterns/enhancing-automation-reliability-with-retry-patterns.html 忍不住跟大家分享一下。&lt;/p>
&lt;p>自动化测试，特别是 ui 自动化的稳定性提升是非常困难的，出错了就重试这个简单的策略可以帮助我们处理一些难以预料的异常情景，从而提升测试的整体稳定性。&lt;/p>
&lt;p>这篇文章的作者就分享了几种重试的设计模式，通俗易懂而且相当实用。&lt;/p>
&lt;h3 id="动态重试">动态重试&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">dynamicRetry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">actions&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">maxRetries&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">retryCount&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">retryAction&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">actions&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">retryCount&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nx">maxRetries&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">retryCount&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">retryAction&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="nx">error&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">retryAction&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">it&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Testing the dynamic retry pattern&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">demoPage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">navigateToDemoPageTextBoxes&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">browser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pause&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">dynamicRetry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">demoPage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fillFullName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;John Doe&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">demoPage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fillEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test@gmail.com&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">demoPage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fillCurrentAddress&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test address&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">demoPage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">clickFakeButton&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>该模式的核心是设置一个最大的重试次数，每次重试过后次数加一，直到达到阈值。&lt;/p>
&lt;h3 id="轮询重试">轮询重试&lt;/h3>
&lt;p>另一种常见的重试模式是使用超时和轮询。这种方法会重复检查一组操作是否成功完成或者是否达到了超时时间。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">pollRetry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">actions&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">timeout&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">pollInterval&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">startTime&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">Date&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">now&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">let&lt;/span> &lt;span class="nx">lastError&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">Date&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">now&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nx">startTime&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nx">timeout&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">actions&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">lastError&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">error&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">pollInterval&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="nx">lastError&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ms&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Promise&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">resolve&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resolve&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ms&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">it&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Testing the poll retry pattern&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">demoPage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">navigateToDemoPageTextBoxes&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">browser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pause&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">pollRetry&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">async&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">demoPage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fillFullName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;John Doe&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">demoPage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fillEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test@gmail.com&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">demoPage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fillCurrentAddress&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test address&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">await&lt;/span> &lt;span class="nx">demoPage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">clickFakeButton&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">20000&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">5000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个模式的核心是设置一个总的超时时间，如果在这个时间段内发生了异常，那么就每隔一段时间重试一下。&lt;/p></description></item><item><title>什么是向量数据库</title><link>https://itest2025.pages.dev/what-is-vector-db_81/</link><pubDate>Fri, 08 Mar 2024 09:03:35 +0800</pubDate><guid>https://itest2025.pages.dev/what-is-vector-db_81/</guid><description>&lt;p>对于向量数据库有疑问吗？以下是对这个数据库的简要解释。&lt;/p>
&lt;h3 id="什么是向量">什么是向量？&lt;/h3>
&lt;p>向量是一组数字的数组，表示点在空间中沿多个维度的位置。例如，向量{12, 13, 19, 8, 9}表示一个点在 5 个维度上的位置。向量可以几何地表示数据，使得数据点之间可以进行数学比较。&lt;/p>
&lt;h3 id="向量与嵌入有什么关系">向量与嵌入有什么关系？&lt;/h3>
&lt;p>在机器学习中，嵌入是表示数据点（如单词、句子、图像等）的向量。嵌入将数据的语义含义编码为向量形式。它们由经过训练的神经网络生成，这些网络将数据点映射到有效的向量表示。这些嵌入将相关概念放置在向量空间中靠近的位置。&lt;/p>
&lt;h3 id="向量数据库如何工作">向量数据库如何工作？&lt;/h3>
&lt;p>向量数据库存储了各种数据点（如文档、图像、产品等）的嵌入，并将这些嵌入映射回其原始数据。数据库可以基于向量的相似性进行快速的相似性搜索。查询嵌入可以与存储的嵌入进行比较，最相似的向量可以几乎即时地被检索出来。这为语义搜索、推荐和其他人工智能应用提供了支持。&lt;/p>
&lt;h3 id="向量数据库在人工智能中的应用是什么">向量数据库在人工智能中的应用是什么？&lt;/h3>
&lt;p>向量数据库具有几个关键的人工智能应用：&lt;/p>
&lt;ul>
&lt;li>语义搜索 - 基于含义而不仅仅是关键词来检索信息&lt;/li>
&lt;li>推荐 - 提供与用户兴趣最相关的内容&lt;/li>
&lt;li>大型语言模型 - 理解上下文中的单词，用于生成和摘要&lt;/li>
&lt;li>异常检测 - 识别与正常情况相比的异常数据点&lt;/li>
&lt;li>图像分类 - 基于视觉属性对图像进行分类&lt;/li>
&lt;/ul>
&lt;p>通过促进快速的相似性比较，向量数据库使得机器学习模型能够在数据之间建立联系。这支持了使用原始数据无法实现的高级人工智能功能。向量空间充当算法构建理解的一种&amp;quot;记忆&amp;quot;。&lt;/p></description></item><item><title>移动测试指南</title><link>https://itest2025.pages.dev/guide-to-mobile-testing_80/</link><pubDate>Fri, 08 Mar 2024 09:03:33 +0800</pubDate><guid>https://itest2025.pages.dev/guide-to-mobile-testing_80/</guid><description>&lt;p>&lt;a href="https://medium.com/@iamfaisalkhatri/guide-to-mobile-testing-d0dd2d9b59f1" target="_blank" rel="noopener noreffer ">Guide to Mobile Testing&lt;/a>&lt;/p>
&lt;p>移动应用已经风靡全球，在移动互联网如此普及的今天，移动测试的重要性不言而喻。个人认为移动应用现在是大部分互联网公司的主要业态，在竞争激烈的现时，移动应用的质量可能会影响某个产品的成败与存亡，在去测试化思潮如此普遍的今天，移动端应用应该是最不可能砍掉测试人员的。因为&lt;/p>
&lt;ul>
&lt;li>移动应用的质量是第一大事&lt;/li>
&lt;li>竞争越激烈，好的质量越重要&lt;/li>
&lt;li>测试移动应用的效率目前还是偏低，用高薪的开发人员去兼任测试职能有点得不偿失&lt;/li>
&lt;li>移动端更新成本相对较高，测试充分再发出去才能尽可能地规避更新应用进行 bugfix 的风险&lt;/li>
&lt;/ul>
&lt;p>诚然，有些公司在推进去测试化，但那些公司可能是&lt;/p>
&lt;ul>
&lt;li>所在行业垄断性质明显，没有其他公司可以进行正面竞争，比如微软&lt;/li>
&lt;li>公司和用户对线上问题忍耐度相对较高，应用形态相对单纯，试错成本相对较低，比如之前的头条&lt;/li>
&lt;li>行业已过高速增长期但存量竞争激烈，产品基本处于维护阶段，不需要投入太多的开发和测试资源，比如腾讯的某些产品&lt;/li>
&lt;/ul>
&lt;p>从这里开始就是翻译了，原文地址在：&lt;a href="https://medium.com/@iamfaisalkhatri/guide-to-mobile-testing-d0dd2d9b59f1" target="_blank" rel="noopener noreffer ">https://medium.com/@iamfaisalkhatri/guide-to-mobile-testing-d0dd2d9b59f1&lt;/a>&lt;/p>
&lt;p>所以大部分情况下，移动端的测试以及质量提升是需要专职测试人员进行负责和统筹的，所以不必焦虑。相反，了解一些基本的移动端测试知识是一件较为必要的事情。&lt;/p>
&lt;p>质量是关键&lt;/p>
&lt;p>我们需要检查所有内容以及所有可能的排列和组合，以免出现任何 bug。由于移动设备也存储了最终用户的个人数据，因此有必要对安全性和数据完整性进行检查。&lt;/p>
&lt;p>测试应用程序的性能同样重要，因为如今人们对应用程序的速度更感兴趣。如果功能能用，但应用响应时间过长，可能无法吸引用户。因此，应用程序的性能测试也是需要考虑的重要因素。&lt;/p>
&lt;p>谈到测试移动应用程序，我认为我们应该首先弄清楚测试策略，因为它可以帮助我们分解测试阶段并进行高质量的测试，并帮助我们避免漏测一些重要内容。&lt;/p>
&lt;h3 id="定义测试策略">定义测试策略&lt;/h3>
&lt;p>首先我们需要弄清楚移动应用的类型&lt;/p>
&lt;ul>
&lt;li>原生应用：离线可以直接用安装包进行安装，一般情况下可以在应用商店下载&lt;/li>
&lt;li>移动 web 应用：比如 h5 应用，基本是用 html+css+js 进行开发的，可能长得跟原生应用很像，但基本上不是一回事&lt;/li>
&lt;li>混合应用：既有原生也有移动 web 的应用，一般情况下对开发人员比较友好，毕竟 web 应用比原生应用开发要容易一点点&lt;/li>
&lt;/ul>
&lt;p>我们以混合应用为例在说明一下如何定义我们的测试策略，在进入细节之前，我们先来了解一下测试类型的相关知识。&lt;/p>
&lt;h3 id="测试类型">测试类型&lt;/h3>
&lt;p>理想情况下，虑到混合应用程序，我认为应该考虑以下测试类型&lt;/p>
&lt;ol>
&lt;li>功能测试。&lt;/li>
&lt;li>性能测试&lt;/li>
&lt;li>安全测试&lt;/li>
&lt;li>可用性测试&lt;/li>
&lt;li>UI/UX 测试&lt;/li>
&lt;/ol>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="%E7%A7%BB%E5%8A%A8%E6%B5%8B%E8%AF%95%E6%8C%87%E5%8D%97%2031ddadb1681b4287803ef98d6ca78960/Untitled.png"
 data-srcset="%E7%A7%BB%E5%8A%A8%E6%B5%8B%E8%AF%95%E6%8C%87%E5%8D%97%2031ddadb1681b4287803ef98d6ca78960/Untitled.png, %E7%A7%BB%E5%8A%A8%E6%B5%8B%E8%AF%95%E6%8C%87%E5%8D%97%2031ddadb1681b4287803ef98d6ca78960/Untitled.png 1.5x, %E7%A7%BB%E5%8A%A8%E6%B5%8B%E8%AF%95%E6%8C%87%E5%8D%97%2031ddadb1681b4287803ef98d6ca78960/Untitled.png 2x"
 data-sizes="auto"
 alt="%E7%A7%BB%E5%8A%A8%E6%B5%8B%E8%AF%95%E6%8C%87%E5%8D%97%2031ddadb1681b4287803ef98d6ca78960/Untitled.png"
 title="Untitled" />&lt;/p>
&lt;h3 id="何时以及如何开始测试">何时以及如何开始测试&lt;/h3>
&lt;p>由于我们处于当今软件世界虔诚地遵循敏捷的时代，因此最好尽早开始测试。&lt;/p>
&lt;p>测试应该在软件开发生命周期的每个阶段进行，&lt;strong>而不仅仅是&lt;/strong>在功能完全开发时进行。&lt;/p>
&lt;p>话虽如此，请始终确保开发人员正在编写单元测试。还应涵盖集成和服务层测试。只写测试没有帮助，代码覆盖率报告应该显示单元测试覆盖率至少大于 80%，如果有可能的话，可以逐渐增加到 100%。有一条流水线可以帮助我们轻松监控生命周期并在每个阶段采取纠正措施，这很好。因此，除非构建是绿色的，否则继续进行测试并尽快进行所需的修复是不好的。&lt;/p>
&lt;h3 id="测试计划">测试计划&lt;/h3>
&lt;p>有一个测试计划是很好的，因为它会更容易检查所有的测试活动，所以我们不会遗漏任何东西，顺利地执行测试并提供高质量的输出。&lt;/p>
&lt;p>第一个也是最重要的情况是用户是否能够使用 PlayStore/App Store 成功安装应用程序。&lt;/p></description></item><item><title>Selenium Manager使用指南</title><link>https://itest2025.pages.dev/how-to-use-selenium-manager_79/</link><pubDate>Fri, 08 Mar 2024 09:03:32 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-use-selenium-manager_79/</guid><description>&lt;p>Selenium 社区最近发布了 Selenium Manager 工具，主要是解决每隔一段时间就要重新去下载 driver 的痛点。当然了，这也是我痛点。因为 chrome 浏览器会自动更新的关系，之前我都是关掉自动更新从而回避去下载新 driver 的问题，不过因为用户的版本大多都是较新的，所以测试浏览器每隔一段时间还是要更新一下才比较好。我自己试用了一下，感觉还是很不错的，非常有意思的一个点是 sm 竟然是 rust 开发的，在我的印象里这种对性能和稳定性要求不是特别高的命令行用 go 开发和维护的话效率可能会更高一点。下面是官方 blog 的翻译以及我自己的一点点体验。&lt;/p>
&lt;p>大多数人使用 Selenium 的第一次经验都会出现这样的错误消息：&lt;/p>
&lt;pre tabindex="0">&lt;code>java.lang.IllegalStateException: The path to the driver executable must be set by the webdriver.chrome.driver system property; for more information, see https://chromedriver.chromium.org/. The latest version can be downloaded from https://chromedriver.chromium.org/downloads
&lt;/code>&lt;/pre>&lt;p>然后他们不得不在网上搜索有关如何处理他们下载的驱动程序的说明。&lt;/p>
&lt;h3 id="selenium现在已经内置了驱动程序">Selenium：现在已经内置了驱动程序！&lt;/h3>
&lt;p>Selenium 项目希望改善用户体验，其中一项首要任务是帮助所有用户简化他们设置环境的方式。多年来，配置浏览器驱动程序一直是用户需要执行的任务，以便运行 Selenium。&lt;/p>
&lt;p>设置一次浏览器驱动程序并不那么复杂，但随着浏览器发布周期缩短，现在每 4-6 周就有一个新的 Chrome/Firefox/Edge 版本，使得保持浏览器驱动程序与浏览器版本同步的任务变得不那么容易了。&lt;/p>
&lt;p>Selenium Manager 是一个新的工具，可帮助轻松获得运行 Selenium 所需的工作环境。如果 Chrome、Firefox 或 Edge 不在 PATH 中，Selenium Manager Beta 1 将配置它们的浏览器驱动程序。&lt;/p></description></item><item><title>确认：代码覆盖率是一个无用的管理指标</title><link>https://itest2025.pages.dev/confirmed-code-coverage-is-a-useless-management-metric_78/</link><pubDate>Fri, 08 Mar 2024 09:03:30 +0800</pubDate><guid>https://itest2025.pages.dev/confirmed-code-coverage-is-a-useless-management-metric_78/</guid><description>&lt;p>看标题就知道这篇文章很有意思，作为一个之前专注过做代码覆盖率平台的人，这个观点一直模模糊糊是存在于我的心里的，不过从来没有这么直截了当的说出来，这篇文章的作者有理有据，值得一看。原文在这里: &lt;a href="https://drpicox.medium.com/confirmed-code-coverage-is-a-useless-management-metric-35afa05e8549" target="_blank" rel="noopener noreffer ">https://drpicox.medium.com/confirmed-code-coverage-is-a-useless-management-metric-35afa05e8549&lt;/a>，我只做了一些翻译。&lt;/p>
&lt;p>有一种强烈的信念，即代码覆盖率是衡量软件产品质量的重要指标，这个信念多年来一直被技术领导者们毫无疑问地共享。从表面上看，这个理论似乎很有道理：测试越彻底，代码覆盖率越高，因此我们的软件应该更加健壮和无错误。这个观念已经深深植入我们的思维中。但是，如果我能证明代码覆盖率从根本上是错误的呢？如果我能向你展示一个如此简单的想法，让你对此毫无疑问？所以，请做好准备，做好心理准备。&lt;/p>
&lt;p>鉴于本文仅展示了哪些指标不适用于管理（尽管对开发人员非常有用），但并未说明应该遵循哪些指标，因此我最近写了一篇后续文章，解释了应该使用哪四个基本指标以及为什么要这样做，这些观点都有科学依据&lt;/p>
&lt;h2 id="the-code-coverage-代码覆盖率">The Code Coverage 代码覆盖率&lt;/h2>
&lt;p>代码覆盖率，简单来说，是衡量你的代码有多少被测试所“触及”或“覆盖”的一种度量。我们假设在我们的产品中有测试，并且我们至少在每次发布之前运行这些测试。当这些测试执行时，它们会对产品进行操作，从而使代码执行。很快，我们意识到如果我们追踪哪些代码被测试执行，我们就可以开始衡量有多少代码被执行了。我们将执行的代码与产品中的总代码量的比例称为“代码覆盖率”。&lt;/p>
&lt;pre tabindex="0">&lt;code>code coverage = executed code by tests / size of the code
&lt;/code>&lt;/pre>&lt;p>这是一个非常简单的度量标准。如果我们有 100 行代码，但测试只执行了其中的 75 行，那么我们的代码覆盖率为 75%。&lt;/p>
&lt;p>很快我们意识到了更重要的事情。如果代码覆盖率不是 100％，那就意味着我们的测试没有执行到某些代码，换句话说：我们有未经测试的代码！&lt;/p>
&lt;p>因此，拥有未经测试的代码是危险的，因为它可能包含错误。此外，它还可能包含业务关键功能，如果我们触碰到该代码，我们可能会丧失这些功能。&lt;/p>
&lt;p>所以，拥有高代码覆盖率是必须的。&lt;/p>
&lt;h2 id="代码覆盖率的谬论">代码覆盖率的谬论&lt;/h2>
&lt;p>但是，现在我们面临一个谬论：我们知道揭示代码意味着我们的测试遗漏了重要的情况，但反过来并不成立。&lt;/p>
&lt;p>例如，在之前的例子中，我们的代码覆盖率为 75%。换句话说，这个指标表示有 25%的代码行没有被任何测试执行过，这明确指出了一个风险区域。我们可以确定地说，这 25%的代码库没有经过任何测试验证，因此可能成为问题和维护困难的滋生地。&lt;/p>
&lt;p>然而，这就是我们冒险陷入谬误的时候：虽然我们可以自信地说未经测试的代码隐藏了潜在的错误和对未来发展的阻碍，但我们可能相信相反的情况是真实的。我们可能相信代码覆盖意味着它有更少的错误和更少的维护问题。但是，这只是一个直觉，甚至可能看起来很合乎逻辑，但事实证明这并不正确。&lt;/p>
&lt;p>事实是，我们可以实现 100%的代码覆盖率，但代码仍然可能存在大量的错误，并且难以维护。&lt;/p>
&lt;h3 id="一个基本的例子">一个基本的例子&lt;/h3>
&lt;p>想象一个简单的函数，它计算两个数字的和：&lt;/p>
&lt;pre tabindex="0">&lt;code>function addition(a, b) {
 return a + b;
}
&lt;/code>&lt;/pre>&lt;p>哪个是能够覆盖 100%的最简单的测试？只需添加一个附加项即可使所有代码执行：&lt;/p>
&lt;pre tabindex="0">&lt;code>test(&amp;#39;the addition function&amp;#39;, () =&amp;gt; {
 addition(3, 4);
});
&lt;/code>&lt;/pre>&lt;p>这个测试覆盖了 100%的代码。然而，它是无用的。为什么呢？如果我们将加法的实现改为这样一个：&lt;/p>
&lt;pre tabindex="0">&lt;code>function addition(a, b) {
 return a - b;
}
&lt;/code>&lt;/pre>&lt;p>测试仍然通过！&lt;/p></description></item><item><title>Postman越来越难用了</title><link>https://itest2025.pages.dev/postman-is-getting-worse_77/</link><pubDate>Fri, 08 Mar 2024 09:03:29 +0800</pubDate><guid>https://itest2025.pages.dev/postman-is-getting-worse_77/</guid><description>&lt;p>今天看到有个哥们吐槽 postman 的新版界面，这位是 Spotify 的资深工程师，所以他的槽点还是有些代表性，他是这么说的&lt;/p>
&lt;blockquote>
&lt;p>Uhhhh what happened to Postman? I just want to send, and inspect http requests?&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>What is all this shit?&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>Adam Rackis
Senior Web Engineer at Spotify. Prev, Riot. Next, React, Svelte, C++ when I&amp;rsquo;m feeling nasty. Beer, whiskey, coffee snob. Book lover. Jr Developer for life.
&lt;a href="https://github.com/arackaf" target="_blank" rel="noopener noreffer ">https://github.com/arackaf&lt;/a>&lt;/p>&lt;/blockquote>
&lt;p>怀着看热闹不嫌事大的心情，我把本机的 postman 更新了一下。对于更新这件事情我是能不更就不更，因为我一直觉得 postman 的旧版本就是比新版本要好用。&lt;/p>
&lt;p>更新到最新版本之后，有趣的事情发生了，界面确实变化很大，之前 postman 的核心就是请求构造页面，现在我随便点了几下，结果 postman 频繁弹出登录页面，我大概点了 2 个菜单，分别是 Home 和 Explore，登录框弹出来 3 次，最无语的是在 home 页面的全屏登录框，我一时半会找不到 X 按钮，当时让我无比的慌乱，不登录难道就不配用你吗？幸好在一个不起眼的地方看到可以忽略登录的链接，终于让我这种不愿意透露姓名的 postman 忠实用户有了继续使用的权利。&lt;/p></description></item><item><title>string算法之二进制加法</title><link>https://itest2025.pages.dev/leetcode-string-binary-add_76/</link><pubDate>Fri, 08 Mar 2024 09:03:27 +0800</pubDate><guid>https://itest2025.pages.dev/leetcode-string-binary-add_76/</guid><description>&lt;p>最近寒风凛冽，估计不少同学都在刷算法题了，这里给大家介绍一个 python 实现的算法仓库，里面有很多常见算法的实现，有兴趣的同学可以先通过既有算法学习，掌握一些套路之后再由易到难把题刷起来。仓库地址：&lt;a href="https://github.com/keon/algorithms" target="_blank" rel="noopener noreffer ">https://github.com/keon/algorithms&lt;/a>&lt;/p>
&lt;p>先给大家分享一个二进制加法的算法吧。&lt;/p>
&lt;h3 id="题目">题目&lt;/h3>
&lt;p>输入：2 个二进制的字符串&lt;/p>
&lt;p>输出：也以二进制的方式返回相加后的结果&lt;/p>
&lt;p>比如&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;11&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Return&lt;/span> &lt;span class="s2">&amp;#34;100&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="分析">分析&lt;/h3>
&lt;p>这道题看完之后我基本上没有任何思路，丝毫没有一点点挣扎，直接看答案了。&lt;/p>
&lt;h3 id="实现">实现&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">add_binary&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">s&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">zero&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">ord&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;0&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nb">ord&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">zero&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">i&lt;/span> &lt;span class="o">-=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">j&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nb">ord&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">zero&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">j&lt;/span> &lt;span class="o">-=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">s&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">chr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">zero&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c&lt;/span> &lt;span class="o">//=&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">s&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>看完以后发现实现思路非常的精妙。&lt;/p>
&lt;p>首先确定’0’这个字符的 ascii 码，zero 的值就是整形的 48。&lt;/p>
&lt;p>下面遍历两个字符串的每一位，从低位到高位，如果当前位存在的话，则取这一位的 ascii 码减去 zero，因为是二进制，所以当前位的值要么是 0 要么是 1，所以每次遍历相减后得到的结果要么是 0 要么是 1，这就等于是把当前位从字符转成了二进制。所以后面如果有类似的字符或字符串转成二进制的题，可以参考每一位减去 ascii 0 的实现方式。&lt;/p>
&lt;p>取一个数 c，保存两个字符串当前位的和，所以 c 的取值一定是固定的&lt;/p>
&lt;ul>
&lt;li>无进位的情况
&lt;ul>
&lt;li>c = 1 + 1 = 2&lt;/li>
&lt;li>c = 1 + 0 = 1&lt;/li>
&lt;li>c = 0 + 1 = 1&lt;/li>
&lt;li>c = 0 + 0 = 0&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>有进位，c 就等于 1
&lt;ul>
&lt;li>c = 1 + 1 + 1 = 3&lt;/li>
&lt;li>c = 1 + 0 + 1 = 2&lt;/li>
&lt;li>c = 0 + 1 + 1 = 2&lt;/li>
&lt;li>c = 1 + 0 + 0 = 1&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>好了，下面再用 c 去模 2，在 c = 2 或者是 0 的时候，就可以得到当前位的值是 0，c 是 1 或 3 的时候当前位就是 1，这里用取模的方式实现了二进制的加法，简单却深刻和优雅。&lt;/p></description></item><item><title>如何从code review中获得最大的收益</title><link>https://itest2025.pages.dev/how-do-you-get-maximum-benefit-from-code-reviews_75/</link><pubDate>Fri, 08 Mar 2024 09:03:26 +0800</pubDate><guid>https://itest2025.pages.dev/how-do-you-get-maximum-benefit-from-code-reviews_75/</guid><description>&lt;p>翻译一篇关于 code review 的不错文章。&lt;/p>
&lt;p>原文地址: &lt;a href="https://shecodesthecosmos.co.uk/2023/04/11/how-do-you-get-maximum-benefit-from-code-reviews" target="_blank" rel="noopener noreffer ">https://shecodesthecosmos.co.uk/2023/04/11/how-do-you-get-maximum-benefit-from-code-reviews&lt;/a>&lt;/p>
&lt;p>Code Review 是软件开发中常用的质量关和最佳实践之一，也可以看作是一种软件测试形式。&lt;/p>
&lt;p>当 Code Review 被设计和使用得当时，可以为软件产品质量带来巨大的好处：允许发现和修复缺陷、改善文档和对代码进行检查。但当 Code Review 被设计不当时，它们往往会成为开发人员和测试人员的负担和瓶颈。&lt;/p>
&lt;p>那么如何确保 Code Review 为团队和产品质量带来最大的好处呢？我有三个建议。&lt;/p>
&lt;h3 id="自动化自动化自动化">自动化！自动化！自动化！&lt;/h3>
&lt;p>利用自动化工具来简化审查过程，减轻审查者的工作量。许多 Code Review 中更标准和重复的元素都可以自动化：&lt;/p>
&lt;ul>
&lt;li>使用自动化测试套件检查功能，并确保在审查之前通过此套件。测试自动化工具通常会生成报告，如果需要，审查者可以快速分析这些报告。&lt;/li>
&lt;li>使用 Linting 软件验证格式，以帮助确保代码按标准化和可读性的方式编写。&lt;/li>
&lt;li>通过使用静态分析工具在编译之前分析代码，识别潜在的运行时缺陷。&lt;/li>
&lt;li>通过自动化创建 Code Review 和需求之间的链接，确保代码的可追溯性。&lt;/li>
&lt;/ul>
&lt;p>这些检查可以集成到 CI/CD pipeline 中，并在代码提交或创建拉取请求等事件触发执行。在审查人员甚至看到代码之前自动执行这些检查可以极大地减少执行审查所需的时间，也使 Code Review 更少重复和行政性工作。&lt;/p>
&lt;p>有许多 DevOps 和 CI/CD 软件可以支持这一点，可参考的示例包括 Azure DevOps、GitLab、Jenkins 和 Atlassian 工具套件。&lt;/p>
&lt;h3 id="定义一个过程">定义一个过程&lt;/h3>
&lt;p>当然，有些代码质量方面是无法自动检查的：检查文档是否完整并且合理，查看代码架构等。这是审查发挥最大价值的地方。为确保从 Code Review 中获得最大的质量，定义一个流程、指南、标准或清单供审查者在审查时使用，可以很有帮助。制定一个标准的检查清单可以帮助防止在审查过程中漏掉任何东西。&lt;/p>
&lt;p>这些流程或指导方针应该始终具备灵活性。Code Review 的范围、规模和风险可能会大不相同，因此并非每个审查都需要进行所有检查。可能包括以下一些内容的 Code Review 清单：&lt;/p>
&lt;ul>
&lt;li>文档的合理性检查：文档是否有意义？是否存在任何漏洞？&lt;/li>
&lt;li>代码是否结构合理？&lt;/li>
&lt;li>代码中是否存在任何错误或潜在的安全风险？&lt;/li>
&lt;li>所有自动检查是否都通过？&lt;/li>
&lt;li>代码是否遵循您组织的最佳实践政策？（如果此检查无法自动化）&lt;/li>
&lt;li>审查的代码大小是否过大？这非常重要！如果 Code Review 太大或太复杂，审查时间将大大增加。保持 Code Review 的简单性和小规模，可能使用单一职责原则来定义应被视为 Code Review 大小的项目。&lt;/li>
&lt;/ul>
&lt;h3 id="利用审查作为学习机会">利用审查作为学习机会&lt;/h3>
&lt;p>最后，不要忘记利用 Code Review 作为团队之间共享知识的机会。团队经常会陷入这样的习惯，即让同一组人员审查代码库的相似区域。这可能会导致两个问题：&lt;/p></description></item><item><title>如何测试微服务中的熔断器</title><link>https://itest2025.pages.dev/%E5%A6%82%E4%BD%95%E6%B5%8B%E8%AF%95%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%AD%E7%9A%84%E7%86%94%E6%96%AD%E5%99%A8_74/</link><pubDate>Fri, 08 Mar 2024 09:03:24 +0800</pubDate><guid>https://itest2025.pages.dev/%E5%A6%82%E4%BD%95%E6%B5%8B%E8%AF%95%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%AD%E7%9A%84%E7%86%94%E6%96%AD%E5%99%A8_74/</guid><description>&lt;p>在软件系统领域，熔断器就像守护天使一样，防止级联故障的发生。这篇博客文章深入探讨了这些守护者在各种场景下的细致测试，探索了加强应用程序抵御故障和失败的重要实践。加入我们，一起踏上测试场景、容错性、集成、性能等方面的探索之旅吧！&lt;/p>
&lt;h3 id="理解熔断器">理解熔断器&lt;/h3>
&lt;p>本质上，熔断器模式是一种设计策略，用于分布式系统中屏蔽应用程序免受故障的连锁反应。类似于电气熔断器在发生故障时中断电流通路，其软件对应版本通过停止向故障组件或服务发送请求来保护系统。&lt;/p>
&lt;p>想象一下，系统中单个组件发生故障，引发多米诺骨牌效应，使整个应用程序崩溃。这就是熔断器发挥作用的地方，它作为哨兵检测异常并隔离故障组件。通过这样做，它防止故障在整个系统中蔓延，允许应用程序的其他部分继续运行。&lt;/p>
&lt;p>为了使这个概念更加具体，请考虑您家里的电气系统。当出现电涌或故障时，电气熔断器会中断电流通路，防止损坏您的电器。同样，在软件世界中，熔断器作为保护屏障，隔离故障并确保整个系统的完整性。&lt;/p>
&lt;h3 id="qa-自动化工程师的作用">QA 自动化工程师的作用&lt;/h3>
&lt;p>熔断器作为系统弹性的守护者，必须经过严格的测试才能有效地完成其任务。QA 工程师（我们）带着必要的工具和方法进入这个领域，以验证熔断器在各种场景下的功能和响应。我们的角色超越了传统的测试；我们是保证的架构师，为潜在的系统故障构建盾牌。&lt;/p>
&lt;p>软件应用程序的可靠性是用户满意度的基石。我们精心设计和执行模拟真实世界的测试场景，将熔断器推到极限。通过这样做，我们确保当系统遇到压力、故障或意外事件时，熔断器能够精确响应，减少对整体用户体验的影响。&lt;/p>
&lt;p>一个有弹性的系统必须能够优雅地处理故障并无缝恢复。我们设计和执行测试，仔细检查嵌入在熔断器中的容错机制。我们安排组件故障场景，验证熔断器是否隔离了问题，允许系统优雅地恢复。&lt;/p>
&lt;h3 id="测试场景">测试场景&lt;/h3>
&lt;p>当我们深入熔断器测试领域时，阐明这些沉默守护者发挥作用的各种场景，加强应用程序抵御潜在中断，是非常重要的。让我们解开常见场景，深入研究我们用来验证熔断器有效性的特定测试用例。&lt;/p>
&lt;h4 id="熔断器常见场景">熔断器常见场景：&lt;/h4>
&lt;ul>
&lt;li>服务中断：
&lt;ul>
&lt;li>场景：模拟关键服务发生中断的情况。&lt;/li>
&lt;li>目标：验证熔断器是否能迅速检测到服务故障并隔离受影响区域，防止级联故障在整个系统中发生。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>高延迟：
&lt;ul>
&lt;li>场景：在系统组件之间的通信中引入高延迟。&lt;/li>
&lt;li>目标：评估熔断器如何响应增加的响应时间，确保它及时介入以防止整体系统性能下降。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>资源耗尽：
&lt;ul>
&lt;li>场景：创建导致资源耗尽（例如，内存、CPU）的条件。&lt;/li>
&lt;li>目标：评估熔断器识别资源相关问题和保护系统免于潜在崩溃或性能下降的能力。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="熔断器功能特定测试用例">熔断器功能特定测试用例：&lt;/h4>
&lt;ul>
&lt;li>阈值验证：
&lt;ul>
&lt;li>测试用例：验证当失败数量超过预定义阈值时，熔断器是否激活。&lt;/li>
&lt;li>目标：确保熔断器对不断升级的失败数量做出积极响应，防止对整个系统产生广泛影响。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>平滑降级：
&lt;ul>
&lt;li>测试用例：有意降低组件的性能，并验证熔断器如何处理这种情况。&lt;/li>
&lt;li>目标：确认熔断器是否介入隔离性能下降的组件，允许系统其他部分以最小的中断运行。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>恢复时间评估：
&lt;ul>
&lt;li>测试用例：触发故障并测量熔断器允许流量恢复所需的时间。&lt;/li>
&lt;li>目标：评估熔断器在故障解决后迅速恢复正常运行的效率。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="容错与恢复熔断器的守护之力">容错与恢复：熔断器的守护之力&lt;/h3>
&lt;p>在熔断器测试的复杂世界中，让我们聚焦两个关键方面：容错性和恢复机制。熔断器犹如警惕的守护者，在应用程序面对故障时发挥着不容小觑的作用。&lt;/p>
&lt;p>容错性是系统在出现故障或失败时仍能继续运行的能力。熔断器能够检测和隔离故障组件，是增强应用程序抗干扰性的重要手段。通过迅速控制问题，它们可以防止单个故障演变成灾难性的系统崩溃。&lt;/p>
&lt;p>当故障发生时，熔断器中嵌入的恢复机制就会启动，负责将系统恢复到正常状态。这些机制可能包括自动重试、回退机制或受控地重新引入之前被隔离的组件。其目标不仅是防止级联故障，而且要促进系统性能无缝恢复到最佳状态。&lt;/p>
&lt;p>在细致的测试过程中，我们扮演着验证这些恢复机制有效性的关键角色。通过有针对性的测试场景，我们评估熔断器从故障中恢复的良好程度，确保恢复过程不仅迅速，而且能保持应用程序的整体完整性。&lt;/p>
&lt;p>我们关注的关键验证点：&lt;/p>
&lt;ul>
&lt;li>恢复时间
&lt;ul>
&lt;li>测试用例: 验证熔断器在故障解决后能迅速恢复。&lt;/li>
&lt;li>目标：评估恢复机制在最小化停机时间和恢复正常运行方面的效率。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>数据完整性
&lt;ul>
&lt;li>测试用例: 测试数据完整性在故障和后续恢复过程中可能存在风险的场景。&lt;/li>
&lt;li>目标: 确保熔断器的恢复机制保持数据一致性和完整性。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>回退机制
&lt;ul>
&lt;li>测试用例: 评估当主组件仍然不稳定时，熔断器如何顺畅切换到回退机制。&lt;/li>
&lt;li>目标: 确认回退机制提供可靠的替代方案，防止服务长时间降级。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="集成测试">集成测试&lt;/h3>
&lt;p>应用程序很少单独存在，它们通常依赖外部服务或 API 来实现特定的功能。集成测试是确保这些互连组件协同工作的关键。对于熔断器而言，测试它们与外部实体的交互对于保证系统整体弹性至关重要。&lt;/p>
&lt;p>我们设计测试场景，在其中与外部服务或 API 等集成组件遇到故障。模拟这些故障是一门细致的艺术，涉及注入故障或操控响应以模拟真实世界的情况。这种方法使我们能够观察到当外部依赖项出现故障时，熔断器如何响应。&lt;/p>
&lt;p>集成测试场景示例&lt;/p>
&lt;ul>
&lt;li>服务不可用：
&lt;ul>
&lt;li>测试用例: 暂时使外部服务不可用。&lt;/li>
&lt;li>目标： 验证熔断器是否识别到不可用性，并激活以防止故障在系统中传播。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>延迟峰值：
&lt;ul>
&lt;li>测试用例: 引入集成 API 的响应时间突然峰值。&lt;/li>
&lt;li>目标： 评估熔断器处理增加延迟的良好程度，确保其介入以保持应用程序的整体响应性。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>间歇性错误：
&lt;ul>
&lt;li>测试用例： 在外部服务的响应中引入间歇性错误。&lt;/li>
&lt;li>目标： 验证熔断器是否能有效识别和隔离间歇性错误，防止对系统产生连锁反应。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>作为集成架构师，我们精心设计和执行这些测试，细致地检查熔断器在各种集成场景下的行为。通过这样做，我们可以确保熔断器成为弹性的守门员，保护应用程序免受外部依赖项不可预测的影响。&lt;/p></description></item><item><title>不要再使用了mock了</title><link>https://itest2025.pages.dev/why-mocks-are-considered-harmful_73/</link><pubDate>Fri, 08 Mar 2024 09:03:23 +0800</pubDate><guid>https://itest2025.pages.dev/why-mocks-are-considered-harmful_73/</guid><description>&lt;p>最近看到一篇关于 mock 的文章，觉得挺有道理的，，简单总结分享一下，原文在这里：&lt;a href="https://levelup.gitconnected.com/why-mocks-are-considered-harmful-b4e8fe60478d" target="_blank" rel="noopener noreffer ">https://levelup.gitconnected.com/why-mocks-are-considered-harmful-b4e8fe60478d&lt;/a>。 文章的观点很简单，就是 mock 其实是弊大于利的。&lt;/p>
&lt;p>自动化测试涉及到很多技术，其中 mock 技术我们应该尽量避免使用。mock 用的好其实也只是锦上添花，用的不好的话则会给你带来一些过度的自信。&lt;/p>
&lt;p>首先看下什么是 mock。mock 其实就是模拟代码中的一些外部访问行为，比如访问第三方的付款 api 或者是对数据库进行访问，这会给单元测试带来一些好处&lt;/p>
&lt;ul>
&lt;li>运行加速，因为你不需要访问额外的服务；&lt;/li>
&lt;li>提升稳定性，第三方服务提供商的一些不稳定因素被规避掉了&lt;/li>
&lt;/ul>
&lt;p>不过 mock 是为一些具有副作用的代码服务的，也就是说这些代码其实是依赖于外部服务，并不是依赖于各种参数的输入。我们可以把函数分成两类&lt;/p>
&lt;ul>
&lt;li>纯粹的函数：没有外部依赖的函数&lt;/li>
&lt;li>不纯粹的函数：有外部依赖的函数&lt;/li>
&lt;/ul>
&lt;h3 id="mock-带来的问题">mock 带来的问题&lt;/h3>
&lt;p>&lt;strong>mock 不等同于其替换掉的服务。&lt;/strong>&lt;/p>
&lt;p>比如你把代码中数据库访问的部分给 mock 掉了，这意味着你的代码可能会在有 mock 的时候工作良好，然而你还是需要进行集成测试以确保在没有 mock 的时候也可以正常工作。&lt;/p>
&lt;p>&lt;strong>不可能进行特性平替&lt;/strong>&lt;/p>
&lt;p>如果你使用的是简单的 mock 方式，那么 mock 可能不会返回一些有用的数据，你花在 mock 上的时间越多，mock 返回的 data 可能会约实用。然而 mock 不能平替被 mock 系统的方方面面。&lt;/p>
&lt;p>&lt;strong>开发了 mock 但不使用其实就是浪费时间&lt;/strong>&lt;/p>
&lt;p>如果你开发了数据库的 mock 但从不使用的话，这就是浪费时间了。在现实世界中这是很有可能发生的，因为代码可能需要获取一些真实配置去做初始化，而 mock 的时候却很难满足这一条件。&lt;/p>
&lt;h3 id="如何去找到-mock-的替代方案">如何去找到 mock 的替代方案&lt;/h3>
&lt;p>很简单，就是重构代码，把代码重构成纯粹的函数和不纯粹的函数两种，纯粹的函数是不需要 mock 就可以测试的，，不纯粹的函数则可以通过集成测试来进行验证。&lt;/p>
&lt;p>举个例子&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">logic&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">addition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">multiplication&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">z&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">y&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">database_write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">z&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>上面的函数就是不纯粹的函数，因为其访问了 db，然而我们还是可以把这个函数重构成 1 个纯粹函数和另一个不纯粹的函数&lt;/p></description></item><item><title>手把手教你xss攻击</title><link>https://itest2025.pages.dev/how-to-xss_72/</link><pubDate>Fri, 08 Mar 2024 09:03:21 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-xss_72/</guid><description>&lt;p>一句话解释 xss 就是通过提供恶意的用户输入让浏览器执行恶意的 JavaScript 代码。&lt;/p>
&lt;h3 id="最简单的例子">最简单的例子&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="nx">Welcome&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nx">your&lt;/span> &lt;span class="nx">input&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里页面希望你通过文本框输入自己名字，然后把你输入的名字展示出来。假如你输入的名字是:&lt;code>&amp;lt;script&amp;gt;alert('XSS')&amp;lt;/script&amp;gt;&lt;/code>&lt;/p>
&lt;p>这时候浏览器将解析下面的 html 代码&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Welcome&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">alert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;XSS&amp;#39;&lt;/span>&lt;span class="p">)&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>script 标签里的 js 代码就被执行了，这就是一个最简单的 xss。&lt;/p>
&lt;h3 id="复杂一点点的例子">复杂一点点的例子&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="nx">Welcome&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">input&lt;/span> &lt;span class="na">value&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;{your input}&amp;#34;&lt;/span>&lt;span class="err">&amp;lt;/&lt;/span>&lt;span class="na">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这时候如果你输入&lt;code>&amp;lt;script&amp;gt;alert('XSS')&amp;lt;/script&amp;gt;&lt;/code> 那么将得到类似下面的结果&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="nx">Welcome&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">input&lt;/span> &lt;span class="err">&amp;#34;&amp;lt;&lt;/span>&lt;span class="na">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">alert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;XSS&amp;#39;&lt;/span>&lt;span class="p">)&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>因为 script 标签被包含在双引号里，浏览器会认为这是一个普通的字符串，所以不会执行 js 代码。不过如果你输入&lt;code>“&amp;quot;&amp;gt;&amp;lt;script&amp;gt;alert('XSS');&amp;lt;/script&amp;gt;”&lt;/code> 那么你将会打开一个新的局面&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="nx">Welcome&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">input&lt;/span> &lt;span class="err">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nx">alert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;XSS&amp;#39;&lt;/span>&lt;span class="p">)&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">h1&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>input 标签被强行闭合，script 标签成功上位。这时候 js 代码又可以执行了。&lt;/p>
&lt;h3 id="再来个例子">再来个例子&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementsByClassName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">innerHTML&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;{input}&amp;#39;&lt;/span>&lt;span class="p">;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个例子里我们要想办法在现有的代码中插入恶意的 js 代码，这里需要用到 2 点小知识&lt;/p>
&lt;ul>
&lt;li>js 代码中&lt;code>;&lt;/code>号可以结束一行语句&lt;/li>
&lt;li>js 代码中&lt;code>//&lt;/code>表示后面的所有内容都是注释，可以被忽略掉&lt;/li>
&lt;/ul>
&lt;p>因此如果我们输入的是&lt;code>';alert('XSS');//&lt;/code> 那么中间的 alert 语句是可以执行的&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementsByClassName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">innerHTML&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="nx">alert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;XSS&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="c1">//&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>前面的单引号被第 1 个单引号匹配并终结，后面的分号直接结束了这一句，而后面的那个单引号则被&lt;code>//&lt;/code> 注释掉了，所以中间的 alert 代码是可以顺利执行的。&lt;/p></description></item><item><title>工程度量的5个类别</title><link>https://itest2025.pages.dev/%E5%B7%A5%E7%A8%8B%E5%BA%A6%E9%87%8F%E7%9A%845%E4%B8%AA%E7%B1%BB%E5%88%AB_71/</link><pubDate>Fri, 08 Mar 2024 09:03:18 +0800</pubDate><guid>https://itest2025.pages.dev/%E5%B7%A5%E7%A8%8B%E5%BA%A6%E9%87%8F%E7%9A%845%E4%B8%AA%E7%B1%BB%E5%88%AB_71/</guid><description>&lt;p>当 Mojtaba Hosseini 帮助指导 Zapier 成为一个更加以数据和指标为导向的工程组织时，当团队继续增加和使用指标时，他发现他们有时会遇到这个问题。我还应该注意和使用哪些指标？&lt;/p>
&lt;p>让我们加入 Mojtaba 的另一个嘉宾系列，他将探讨 5 类工程指标，这些指标可以帮助团队实现指标的多样化和平衡化&lt;/p>
&lt;p>但首先，打个比方&amp;hellip;&lt;/p>
&lt;p>想想一个汽车的主仪表盘。&lt;/p>
&lt;p>有许多指标和表盘，每个都以不同的方式帮助司机。&lt;/p>
&lt;ul>
&lt;li>速度和性能：汽车的速度有多快？发动机的工作强度如何？&lt;/li>
&lt;li>维护：发动机健康状况，机油和电池健康状况，汽油水平，发动机温度。&lt;/li>
&lt;li>状态：车门、后备箱、引擎盖开/关，灯开/关，指示灯开/关，手刹开/关。&lt;/li>
&lt;/ul>
&lt;p>汽车越复杂，这些类别中的表盘和仪表盘就越多。如果司机只能接触到一个类别，他们可能会损坏汽车（或更糟）。&lt;/p>
&lt;h2 id="工程测量的-5-个类别">工程测量的 5 个类别&lt;/h2>
&lt;p>可以说，工程团队可以使用的指标有 5 类。&lt;/p>
&lt;h3 id="客户指标">客户指标&lt;/h3>
&lt;p>这些指标主要是衡量团队的客户情况。这个类别的指标包括&lt;/p>
&lt;ul>
&lt;li>客户净推荐值(NPS，可以理解为口碑)&lt;/li>
&lt;li>产品 HEART 指标。幸福感、参与度、采用率、留存率、任务完成率&lt;/li>
&lt;li>关于我们对客户咨询的反应速度的 SLI 指标&lt;/li>
&lt;/ul>
&lt;p>这些通常被认为是一个团队最重要的一些指标，因为它们涉及到团队的客户。然而，这些指标可能是滞后指标，可能无法让团队充分了解客户满意（或不满意）的原因。&lt;/p>
&lt;h3 id="团队工作量指标">团队工作量指标&lt;/h3>
&lt;p>一些团队发现衡量团队的工作量是很有用的，可以深入了解各种类别的工作量。比如说。&lt;/p>
&lt;ul>
&lt;li>参与到 feature 开发工作与非 feature 开发工作的百分比&lt;/li>
&lt;li>团队的支持工作的负荷及其对整体工作量的影响&lt;/li>
&lt;li>团队的 bug fixing 工作的负荷及其对整体工作量的影响&lt;/li>
&lt;li>战术性工作与战略性工作的百分比&lt;/li>
&lt;/ul>
&lt;p>这些指标可以帮助团队理解和阐述他们的痛点，甚至有时可以与客户指标联系起来。它们有时也能揭示出团队以外的问题和瓶颈，这些问题和瓶颈影响了团队的工作量。&lt;/p>
&lt;h3 id="团队效能指标">团队效能指标&lt;/h3>
&lt;p>如果说团队工作量指标衡量的是团队的工作投入，那么团队绩效指标的目的是看团队在处理工作量方面的情况。比如说。&lt;/p>
&lt;ul>
&lt;li>周期时间和吞吐量以及在制品（WIP 半成品）的大小：团队如何处理其工作负荷&lt;/li>
&lt;li>4 个 DORA 指标：一个团队部署到生产的速度和频率以及这些部署的质量，可以参考这篇文章:&lt;a href="https://cloud.google.com/blog/products/devops-sre/using-the-four-keys-to-measure-your-devops-performance" target="_blank" rel="noopener noreffer ">https://cloud.google.com/blog/products/devops-sre/using-the-four-keys-to-measure-your-devops-performance&lt;/a>&lt;/li>
&lt;li>团队速度：完成需求的平均速度&lt;/li>
&lt;/ul>
&lt;p>这些指标通常是最有可能被管理层错误使用的指标&amp;ndash;最糟糕的是被武器化。如果使用得当，它们是团队工作量和客户指标的绝佳配套指标。&lt;/p>
&lt;h3 id="服务水平度量">服务水平度量&lt;/h3>
&lt;p>软件团队也可以衡量他们维护的服务的健康状况。比如说。&lt;/p>
&lt;ul>
&lt;li>资源使用率（CPU/内存）：服务使用资源的程度，通常在一段时间内查看&lt;/li>
&lt;li>(云）成本：资源的成本（通常会进一步细分），通常会随着时间的推移查看&lt;/li>
&lt;li>服务正常运行时间：服务正常运行的时间百分比&lt;/li>
&lt;li>服务错误率：一项服务出错或超时的频率&lt;/li>
&lt;/ul>
&lt;p>请看这篇来自亚马逊的文章，关于服务指标的各个层次以及为运营可见性创建仪表盘。&lt;a href="https://aws.amazon.com/cn/builders-library/building-dashboards-for-operational-visibility/" target="_blank" rel="noopener noreffer ">https://aws.amazon.com/cn/builders-library/building-dashboards-for-operational-visibility/&lt;/a>&lt;/p>
&lt;p>请注意，对于一些团队，这些服务指标与客户指标有直接或间接的关系&amp;ndash;当客户是服务的直接用户时，甚至可能是客户指标。&lt;/p></description></item><item><title>测试人员不可不知的7个浏览器小技巧</title><link>https://itest2025.pages.dev/7-browser-tips-for-tester_70/</link><pubDate>Fri, 08 Mar 2024 09:03:17 +0800</pubDate><guid>https://itest2025.pages.dev/7-browser-tips-for-tester_70/</guid><description>&lt;p>今天我将谈论浏览器技巧——你甚至可能没有意识到这些小功能的存在，主要是在浏览器的“开发者工具”部分。 这些不是关于代码，而是使测试更容易一些的方法。&lt;/p>
&lt;h3 id="响应式模式">响应式模式&lt;/h3>
&lt;p>这是我之前在做前端开发时候每天必用的工具，因为那时候响应式前端框架刚刚兴起，我们都希望用一套代码实现 pc 端和移动端的统一体验。这个工具是 chrome 和 firefox 上的轻量手机模拟器，可以模拟网页在不同设备上的显示情况。在 Firefox 中，它是 Tools-&amp;gt;Web Developer-&amp;gt;Responsive Design Mode，而在 Chrome 中是 View-&amp;gt;Developer tools，然后单击设备工具栏 - 看起来像全尺寸屏幕旁边的移动设备图片 . 单击后，您将看到 chrome 认为该网站将如何显示在设备上。&lt;/p>
&lt;h3 id="自己编辑网页">自己编辑网页&lt;/h3>
&lt;p>如果您发现前端错误，你可以直接编辑页面上的 html 代码，并查看是否修复了错误。 以 chrome 为例，就是这样：&lt;/p>
&lt;p>打开开发者工具，选择第一个 tab 页也就是 Elements，选择你想要编辑的 html 代码， 在左侧会有三个点，单击它们会出现一个包含“Edit as HTML”的菜单； 然后您可以编辑元素本身。 当您按 ENTER 或 RETURN 时，网页就会发生变化了。&lt;/p>
&lt;p>这意味着你可以做的不仅仅是查找错误，而是实际提出修复方案。 这样依赖测试是开发团队的一个完全参与的部分了，从辅助变成了输出。&lt;/p>
&lt;h3 id="离线模式">离线模式&lt;/h3>
&lt;p>有时候我们会希望测试一下网页在弱网甚至是断网时候的表现，直接关 wifi 或者拔网线的话其实挺麻烦的，这时候离线模式就派上用场了。&lt;/p>
&lt;p>以 chrome 为例，打开开发者工具，选择 Network 标签，紧挨着的下面一行有个默认是&amp;quot;No throtting&amp;quot;并且旁边带个小三角箭头的东西，这其实就是个下拉列表了，点开可以选择&amp;quot;Offline&amp;quot;，如果你想模拟弱网的话，还可以选择&amp;quot;Slow 3G&amp;quot; 等选项。&lt;/p>
&lt;h3 id="javascript-控制台">javascript 控制台&lt;/h3>
&lt;p>这个控制台里可以执行任意的 js 代码，最有利的一点是我们可以在控制台里直接写代码去调用后端接口进行调试，之前阿里内部员工抢月饼的脚本应该就是在控制台里运行代码去实现的。当我们学会操作 dom 之后，我们还可以直接用控制台写代码去实现一些页面上的简单自动化，提高我们的测试效率。&lt;/p>
&lt;h3 id="修改文件的保存路径">修改文件的保存路径&lt;/h3>
&lt;p>在 Firefox 中，在首选项下，你可以看到下载的位置选项。 如果您像我一样，每天可能会保存数十个文件，那么将它们直接放在桌面上将为您节省两次点击。 这样一天就是五十次点击，也许一周会节约一两分钟。 这样你在花时间阅读我这篇文章时候就不会感到内疚了。&lt;/p></description></item><item><title>优秀测试工程师的5c特质</title><link>https://itest2025.pages.dev/five-cs-of-a-test-engineer_68/</link><pubDate>Fri, 08 Mar 2024 09:03:14 +0800</pubDate><guid>https://itest2025.pages.dev/five-cs-of-a-test-engineer_68/</guid><description>&lt;p>原文地址：&lt;a href="https://medium.com/geekculture/five-cs-of-a-test-engineer-59f0f74049d8" target="_blank" rel="noopener noreffer ">https://medium.com/geekculture/five-cs-of-a-test-engineer-59f0f74049d8&lt;/a>&lt;/p>
&lt;p>简单来说，优秀的测试工程师应该具备下面的 5 种特质，因为对应的英文单词都是 c 开头，所以可以简称为 5c 特质。&lt;/p>
&lt;h3 id="critical-thinking">&lt;strong>Critical Thinking&lt;/strong>&lt;/h3>
&lt;p>批判性思维。&lt;/p>
&lt;p>这是经常被人忽视的点，测试同学经常需要收集信息，对多个渠道的获取的信息进行整合和判断，具有批判性思维可以让我们理性清晰的思考，了解信息之间的逻辑性以及对业务和用户的造成的影响。&lt;/p>
&lt;h3 id="communication">&lt;strong>Communication&lt;/strong>&lt;/h3>
&lt;p>沟通。&lt;/p>
&lt;p>这点是毋庸置疑的了，如果无法进行高效的沟通和表达，那么发现再多的缺陷和问题都没什么大用处了，测试工程师往往是杰出的沟通者。&lt;/p>
&lt;h3 id="collaboration">&lt;strong>Collaboration&lt;/strong>&lt;/h3>
&lt;p>协作。&lt;/p>
&lt;p>测试同学一般不会孤立的工作，或者这样说，不应该孤立的工作。我们是团队的一员，大部分时间都在与开发人员和产品人员通力合作，同时收集有关业务规则和上下文的知识。因此，协作便成为了测试工程师为团队带来贡献的日常基本技能。&lt;/p>
&lt;h3 id="creativity">&lt;strong>Creativity&lt;/strong>&lt;/h3>
&lt;p>创造力。&lt;/p>
&lt;p>创造性和跳出条条框框进行思考是一种技能，它经常让我们找出那些可能会对整个产品产生巨大影响的不受欢迎的边缘情况，从而提升我们的自身的影响力，而影响力则在高级测试工程师所具有的独门技能中排名第一。创造力可以理解为不走寻常路，独辟蹊径的能力，在探索性测试的时候非常关键。&lt;/p>
&lt;h3 id="coding">&lt;strong>Coding&lt;/strong>&lt;/h3>
&lt;p>每个测试工程师都需要具备表达和阐明业务规则并轻松为其进行领域建模的技能。他们是将实际用例和业务逻辑转换为代码的专家。&lt;/p>
&lt;p>综上，不过这几个技能套在开发同学身上也能行得通，可以这么认为在软件相关的行业，优秀的人才往往具备一些共性的特质，而这些特质是可以通过工作和学习进行培养和训练的。&lt;/p></description></item><item><title>playwright 大战cypress</title><link>https://itest2025.pages.dev/cypress-vs-playwright_67/</link><pubDate>Fri, 08 Mar 2024 09:03:12 +0800</pubDate><guid>https://itest2025.pages.dev/cypress-vs-playwright_67/</guid><description>&lt;p>之前看到一篇文章比较系统的比较了 playwright 和 cypress，这里简单翻译了一下，希望可以给大家在技术选型时提供一些参考。原文地址在这里：&lt;a href="https://applitools.com/blog/cypress-vs-playwright/?utm_campaign=Software%2BTesting%2BWeekly&amp;amp;utm_medium=email&amp;amp;utm_source=Software_Testing_Weekly_133" target="_blank" rel="noopener noreffer ">https://applitools.com/blog/cypress-vs-playwright/&lt;/a>。文本中的演示代码可以在 github 仓库找到：&lt;a href="https://github.com/applitools/webinar-cypress-vs-playwright" target="_blank" rel="noopener noreffer ">https://github.com/applitools/webinar-cypress-vs-playwright&lt;/a>&lt;/p>
&lt;h3 id="第一轮元素操作">第一轮：元素操作&lt;/h3>
&lt;p>首先从最常见的场景进行比较，看看最简单的流程。&lt;/p>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled.png"
 data-srcset="playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled.png, playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled.png 1.5x, playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled.png 2x"
 data-sizes="auto"
 alt="playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled.png"
 title="Untitled" />&lt;/p>
&lt;p>这是一个简单的登录场景，初相见感觉是差别不大，不过 cypress 显得更加简洁一些。在用户投票中，cypress 以 61%的得票胜出。&lt;/p>
&lt;h3 id="第二轮如何对-iframe-进行测试">第二轮：如何对 iframe 进行测试&lt;/h3>
&lt;p>尽管 iframe 的测试在日常的工作中并不是特别常见，不过 iframe 的处理对 qa 来说确实是一件非常有挑战的事情。实际上 cypress 需要引入插件才能进行处理，所以可能在这一轮 playwright 会稍微占据一些优势。playwright 原生支持对 iframe 进行操作，省去了额外安装插件的工序。&lt;/p>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled%201.png"
 data-srcset="playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled%201.png, playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled%201.png 1.5x, playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled%201.png 2x"
 data-sizes="auto"
 alt="playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled%201.png"
 title="Untitled" />&lt;/p>
&lt;h3 id="第三轮等待与重试">第三轮：等待与重试&lt;/h3>
&lt;p>因为现代 web 页面的特性，等待与重试成为了自动化测试工具非常重要的一种能力。参赛双方都有自动等待和重试的机制，直接看代码吧，在用户投票中这一轮 cypress 以 53%的选票获胜。&lt;/p>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled%202.png"
 data-srcset="playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled%202.png, playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled%202.png 1.5x, playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled%202.png 2x"
 data-sizes="auto"
 alt="playwright%20%E5%A4%A7%E6%88%98cypress%2011b717732e2e4ceebd0b58c3fd83cc16/Untitled%202.png"
 title="Untitled" />&lt;/p>
&lt;h3 id="第四轮处理浏览器的原生-alert">第四轮：处理浏览器的原生 alert&lt;/h3>
&lt;p>鉴于每个工具的不同设计，看看它们各自如何处理本地浏览器事件是很有趣的事情。Playwright 使用 websocket 服务器与浏览器进行通信，而 Cypress 则被注入到浏览器中，并从那里实现应用程序的自动化。处理本地浏览器的事件可能会更复杂，在这一轮比赛中也证明了这一点。虽然 Playwright 对警报和提示显示了一致的解决方案，不过 Cypress 对所有三种情况都有自己的解决方案，最后 Playwright 在本轮比赛中取得了 91%的横扫胜利。&lt;/p></description></item><item><title>为什么你不能只做api测试</title><link>https://itest2025.pages.dev/limitations-of-api-only-testing_66/</link><pubDate>Fri, 08 Mar 2024 09:03:11 +0800</pubDate><guid>https://itest2025.pages.dev/limitations-of-api-only-testing_66/</guid><description>&lt;p>翻译了一篇&lt;a href="https://www.checklyhq.com/blog/limitations-of-api-only-testing/" target="_blank" rel="noopener noreffer ">Limitations of API-only testing: Why it shouldn’t be your sole testing strategy&lt;/a>，这篇文章的核心观点是讨论为什么不能只做 api 级的测试，挺有道理的，适合泛读以及扩充观点库。&lt;/p>
&lt;p>前几天我收到了一篇独特的文章，它提出了一个大胆的观点：“API 测试比 UI 测试更好”。在软件世界中，像“A 比 B 更好”这样的绝对陈述很少成立。“这取决于”才是大多数技术问题的答案，因为有原因。&lt;/p>
&lt;p>让我们比较 API 和 UI 测试，并讨论为什么一个不如另一个。这两个方法是“敌人还是朋友”，但永远是不同的。这很好。&lt;/p>
&lt;h3 id="api-测试和-ui-测试两种截然不同的挑战">API 测试和 UI 测试——两种截然不同的挑战&lt;/h3>
&lt;p>当你比较 API 和 UI 测试并说一个更好时，这句话背后是什么？人们经常比较这两种测试方法的可扩展性、稳定性或可维护性。然而，这些比较都没有价值，因为 API 和 UI 测试非常不同。&lt;/p>
&lt;p>根据我的经验，这些比较的基础总是相同的：API 测试比 UI 测试更容易维护。让我们深入研究一下，看看为什么这并不重要！&lt;/p>
&lt;h3 id="api-测试易于实现">API 测试易于实现&lt;/h3>
&lt;p>API 测试比 UI 测试更容易维护的说法是成立的。在最简单的形式中，你甚至可以用你喜欢的编程语言编写一个 50 行的脚本，对本地或暂存环境进行 HTTP 调用，并解析 JSON 来评估响应是否正确。使用 HTTP 并不难。你甚至可以使用一些 shell 魔法来连接请求并将脚本并行化，以单个命令覆盖整个 API 表面。&lt;/p>
&lt;p>很快，你会意识到你需要测试报告、合适的测试运行器和高级测试断言。此外，没有人知道如何处理你自制的测试脚本。因此，你将转向 Postman 或类似的工具。恭喜你！现在，你可以创建 HTTP 请求并测试整个 API 表面。而且可以扩展！这很棒！&lt;/p></description></item><item><title>聊聊ui自动化用例的尺度</title><link>https://itest2025.pages.dev/should-test-automation-just-handle-it_64/</link><pubDate>Fri, 08 Mar 2024 09:03:08 +0800</pubDate><guid>https://itest2025.pages.dev/should-test-automation-just-handle-it_64/</guid><description>&lt;p>自动化用例的尺度到底怎么拿捏，每个测试团队或者每个人都有自己的想法和方法论，今天看到一篇文章以处理弹框为例比较详细的讨论了这个问题，觉得跟我的思路很接近，这里拿出来分享一下。原文地址：&lt;a href="https://responsibleautomation.wordpress.com/2023/01/31/should-test-automation-just-handle-it/?utm_campaign=Software%2BTesting%2BWeekly&amp;amp;utm_medium=web&amp;amp;utm_source=Software_Testing_Weekly_154" target="_blank" rel="noopener noreffer ">https://responsibleautomation.wordpress.com/2023/01/31/should-test-automation-just-handle-it/&lt;/a>&lt;/p>
&lt;p>在文章里作者列举了几个场景&lt;/p>
&lt;ul>
&lt;li>有时我们收到这个弹出窗口，所以我们检查一下它是否在那里。如果有，我们就点击 &amp;ldquo;取消&amp;rdquo;，然后继续。&lt;/li>
&lt;li>有时我们得到一个网页，上面写着 &amp;ldquo;系统不可用&amp;rdquo;，所以我们就点击 F5，然后继续，因为这只发生在 QA 环境中。&lt;/li>
&lt;li>有时，应用程序让我们在支付过程中二次登录。&lt;/li>
&lt;/ul>
&lt;p>碰到这些情况，自动化脚本应该如何处理？&lt;/p>
&lt;p>从技术上讲我们始终是可以用代码处理这些情况的，不过核心的问题是，遇到这些突发情况，我们是要断言用例成功与否，还是写代码去 pass 这些细枝末节？有时候这类问题确实很困扰。&lt;/p>
&lt;p>作者把这些问题分为两类&lt;/p>
&lt;ul>
&lt;li>对业务没影响的，可以容忍的问题&lt;/li>
&lt;li>不可容忍的问题&lt;/li>
&lt;/ul>
&lt;h3 id="可容忍的问题">可容忍的问题&lt;/h3>
&lt;p>作者举了个例子，他们的 ios app 在进入的时候会弹框，要求用户授权定位和通知权限。遇到这种情况，脚本是直接点掉还是返回错误呢？作者表示他们也不能做决定，最后通过跟客户讨论，客户认为这种问题可以忽略，所以他们最终的策略是点掉，继续后面的流程，这就是可容忍问题的例子。&lt;/p>
&lt;h3 id="不可容忍的问题">不可容忍的问题&lt;/h3>
&lt;p>还是上面那个 app，一些情况下用户没有配置签名会弹出提示框，要求提供签名后继续，这种问题直接影响到了业务流程，就是不可容忍的问题，在这种情况下进行断言会比较好，因为没有签名后面的业务流程都走不通了，应该当做一个异常用例来进行专门的测试。&lt;/p>
&lt;h3 id="总结">总结&lt;/h3>
&lt;p>作者给的例子都是弹框的，确实很少见到有人专门写东西来讨论弹窗的处理，简而言之一些弹框跟业务无关，那么就直接点掉或者不让它弹。比如之前我在写 wordpress 用例的时候，打开编辑器后总会出现全屏的用户引导教程，页面上所有文本框都没办法进行输入，我这时候认为新手教程不影响业务的核心流程，所以用了一些 js 代码直接让引导教程没办法弹出来。&lt;strong>另一方面，如果出现的弹框是与业务强相关，那么弹与不弹就是两个用例，分别进行测试就好。&lt;/strong>&lt;/p>
&lt;p>另外除了弹框，还有一些处理也可以分类来探讨。比如测试环境下超时时间调长一点，加载不出来就刷新一下，这种问题再测试环境是可以容忍的，不影响主业务和流程。&lt;/p></description></item><item><title>AI自动化探索之gpt4与playwright</title><link>https://itest2025.pages.dev/chatgpt-with-playwright_63/</link><pubDate>Fri, 08 Mar 2024 09:03:07 +0800</pubDate><guid>https://itest2025.pages.dev/chatgpt-with-playwright_63/</guid><description>&lt;p>之前介绍过一个使用 chatgpt4 分析 dom，然后生成 puppeteer 代码进行自动化的测试工具 Taxy AI。今天发现有人推荐了一个使用 chatgpt4 生成 playwright 代码的测试工具&lt;a href="https://github.com/mayt/BrowserGPT" target="_blank" rel="noopener noreffer ">BrowserGPT&lt;/a>，稍微看了一下，原理比较简单，比较适合我们去研究一下，顺便打开思路。&lt;/p>
&lt;h3 id="演示动画">演示动画&lt;/h3>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif"
 data-srcset="https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif, https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif 1.5x, https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif 2x"
 data-sizes="auto"
 alt="https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif"
 title="https://github.com/mayt/BrowserGPT/raw/master/public/browsergpt.gif" />&lt;/p>
&lt;h3 id="具体使用">具体使用&lt;/h3>
&lt;p>因为我没有 chatgpt4 的 key，所以没办法直接上手使用，只能通过文档去猜测一下具体用法。&lt;/p>
&lt;p>BrowserGPT 设置了 openai 的 key 以及 start url 之后就可以在命令行里运行了，大致的使用方式是输入一些自然语言，然后 BrowserGPT 执行 AI 生成的 playwright 代码，实现自然语言自动化的功能。&lt;/p>
&lt;pre tabindex="0">&lt;code>go to hn
click on the abc article
&lt;/code>&lt;/pre>&lt;p>比如上面的一些描述就实现了去 hacknews 网站点击 abc 这篇文章的功能。&lt;/p>
&lt;h3 id="原理分析">原理分析&lt;/h3>
&lt;p>大致看了一下，执行的流程是这样的，代码在&lt;a href="https://github.com/mayt/BrowserGPT/blob/master/index.js" target="_blank" rel="noopener noreffer ">这里&lt;/a>&lt;/p>
&lt;ul>
&lt;li>获取初始化的 url，打开 chrome 浏览器，跳转到这个 url&lt;/li>
&lt;li>在命令行里启动 prompt，也就是给用户一个输入的 ui&lt;/li>
&lt;li>初始化 openai 的 api&lt;/li>
&lt;li>写个死循环，每次用户输入之后调用&lt;code>doAction&lt;/code>函数&lt;/li>
&lt;li>在&lt;code>doAction&lt;/code>函数里简化当前页面的 dom 元素&lt;/li>
&lt;li>将简化过的 dom 元素传给 chatgpt，让 gpt 根据 playwright 的示例生成代码&lt;/li>
&lt;li>执行 chatgpt 生成的代码&lt;/li>
&lt;/ul>
&lt;p>这里最有意思的部分是&lt;code>doAction&lt;/code>函数&lt;/p></description></item><item><title>如何在软件开发中实现速度和质量的平衡</title><link>https://itest2025.pages.dev/how-to-balance-speed-and-quality_62/</link><pubDate>Fri, 08 Mar 2024 09:03:05 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-balance-speed-and-quality_62/</guid><description>&lt;p>软件开发的节奏和期望在最近的时期急剧升级。以前，团队会提前几个月规划大多数软件产品的交付周期。瀑布式开发方法是开发软件的主流方法，并且许多组织在不同行业广泛采用了这种方法。例如，Microsoft Windows 操作系统的开发在上市之前需要数年时间。即使是以服务包形式的增量更新也需要数年才能发布。&lt;/p>
&lt;p>在当今快节奏的环境中，任何花费数月或数年时间向客户交付新功能的组织都将被抛在后头。构建和部署软件的方法发生了巨大变化，大多数公司采用了快速开发周期以保持竞争力。回到上面提到的例子，Microsoft 近期的 Windows 版本转向滚动更新，而不是等待多年才发布新功能。这一举措使得公司能够定期和频繁地推出变化，而不是让消费者等待数年。&lt;/p>
&lt;p>这种尽快构建和频繁交付的加速周期对于消费者来说非常有利，他们可以在应用程序的主要版本之间没有长时间等待的情况下获得最新和最好的软件。然而，产品开发团队面临的压力可能导致他们采取捷径和省略步骤。在许多情况下，由于高要求导致的质量和稳定性的牺牲会导致产品存在错误，从而使客户不愿使用。&lt;/p>
&lt;p>初创公司和小公司最容易受到快速交付的负担和困扰。在这种环境下，这是一种必要的恶。当你在一个小团队中努力工作，希望将新的创新产品推向世界时，你需要频繁迭代你的产品，将其塑造成能够使你的业务生存下去的东西。通常，这意味着更加强调快速发布周期，为初创公司留下更少的时间和资源进行测试。较大的公司通常有更多的时间和资金用于质量，但以延长的开发和部署周期为代价。&lt;/p>
&lt;p>大多数组织认为他们可以通过速度或质量来应对，但这种策略只能在短期内起作用。对于初创公司来说，快速交付周期可以帮助他们更快地迭代和修复错误，但随着时间的推移，所有这些捷径将累积成巨大的技术债务，使开发变得更加缓慢和困难。较大的组织将建立稳定软件交付的流程，但缓慢的交付速度将使竞争对手超越他们。软件团队需要同时具备速度和质量，才能应对现代软件时代客户的高要求。&lt;/p>
&lt;h3 id="软件开发中速度的重要性">软件开发中速度的重要性&lt;/h3>
&lt;p>正如本文已经指出的，速度对于保持软件开发的长期业务计划至关重要。在争夺消费者有限的注意力方面，竞争异常激烈，对许多组织来说，以较慢的速度工作以确保准确性已经不再可接受。未能迅速工作和适应将不可避免地面临挑战。应用程序中过时的功能可能导致客户转投其他地方，这导致消费者和市场份额的损失，最终不可避免地以失败告终。&lt;/p>
&lt;p>多年来，我们见证了无数组织因无法跟上技术进步或对其不感兴趣而失败。例如，在 90 年代和 2000 年代初，诺基亚是手机的代名词。但是当苹果推出 iPhone 并且谷歌随后推出 Android 时，诺基亚选择坚持其现有软件平台，而不是加入消费者想要的更现代和先进的移动生态系统。该公司在接下来的几年中迅速失去了超过 90％的市值，再也没有恢复其在该市场细分中的地位。&lt;/p>
&lt;p>然而，快速前进也带来一些风险。过快发展的其中一个更危险的风险是花费数月或数年时间来构建人们不需要的东西。大多数初创公司无法生存，因为他们的创始人无法找到产品与市场的契合点，使他们的软件可持续发展。大型组织在新产品方面也面临这个问题，比如谷歌。该公司在 2013 年推出了 Google Glass，虽然许多人对该设备着迷，但谷歌未能弄清楚为什么大多数人愿意为该产品支付 1500 美元。该公司也没有花时间考虑隐私和安全问题，试图快速进入市场。结果，该产品成为谷歌最引人注目的失败之一。&lt;/p>
&lt;p>急于上市的最明显风险是可能导致产品发布时尚未完全完成。我们经常在软件和硬件产品中看到这种情况。例如，苹果在 2012 年发布了苹果地图，试图替代其设备上的谷歌地图服务。然而，该产品显然还没有准备好，存在大量错误、不准确的导航和缺失的数据。另一个例子是微软因其发布的 Windows Vista 而受到严厉批评，该系统运行缓慢且不稳定，许多项目开发人员将这些问题归咎于匆忙的发布计划。&lt;/p>
&lt;p>为了解决这些问题，组织通常需要暂停一下，审视他们如何处理产品质量的问题。&lt;/p>
&lt;h3 id="软件开发中质量的重要性">软件开发中质量的重要性&lt;/h3>
&lt;p>软件质量是软件开发生命周期中非常重要但常常被忽视的部分。每个人都希望产品具有最高的质量，但很少有人在追求速度时给予足够的重视。许多开发团队对测试和质量保证流程口头上多有讲究，但没有采取实际行动。在某些团队中，尤其是在“快速打破事物”的时代，有时会将质量视为快速开发的直接相反，但这与事实相去甚远。&lt;/p>
&lt;p>本文前面提到的一些例子讨论了为什么质量至关重要。当您加快应用程序的开发并忽略质量问题以便更快地推出产品时，随之而来的问题将不可避免地导致系统故障、安全漏洞和对您品牌的信任不足。我们经常看到新闻中的许多组织由于质量差而遇到困难和危机，从初创公司到科技巨头都有。&lt;/p>
&lt;p>专注于质量并不是解决这些问题的万能解决方案。事实上，过于专注于某一方面可能对开发过程产生不利影响。除了导致发布计划放慢并且远远超过截止日期之外，过于严格的质量保证政策还可能导致团队士气下降。我曾在一家初创公司工作过，他们将质量保证团队设为我们是否能够发布网页应用程序更新的门槛。他们的标准相对较高，即使是小问题也完全阻止了部署。开发和质量保证之间的来回反复随着每次进度滑坡而变得紧张。开发人员和测试人员都没有在公司呆很长时间，员工流动是该初创公司不存在的原因之一。&lt;/p>
&lt;p>在某些行业中，质量是绝对必要的。您不希望使用实验室没有经过彻底测试的心脏起搏器，也不希望使用没有经过安全专家审计的互联网银行。但在软件行业中，现实情况是，大多数开发人员正在构建的是没有任何巨大影响的网络或移动应用程序，即使产品不像预期那样始终正常工作。您可能会因为偶尔的小错误或可用性问题而冒失去客户的风险，但我们很少遇到在软件中会产生不利现实影响的问题。大多数时候，它们只是小小的烦恼，不会导致客户大批离开。&lt;/p>
&lt;p>作为软件开发人员和测试人员，我们不希望放慢速度或发布有错误的软件。我们需要学会如何正确平衡速度和质量。&lt;/p>
&lt;h3 id="在软件开发中平衡速度和质量">在软件开发中平衡速度和质量&lt;/h3>
&lt;p>过快进行开发和测试会导致质量问题，但过于关注质量可能会拖慢整个过程。关键是根据项目当前的状态找到合适的速度和质量平衡点。当然，安全地进行快速迭代测试和部署应用程序并不容易，但通过投入时间和精力是可以实现的。&lt;/p>
&lt;p>以下是您可以在组织中实施的五种策略，有助于在所有应用程序中实现速度和质量的正确平衡。&lt;/p>
&lt;p>1）自动化测试
大多数软件团队都有测试流程，但通常他们将这些流程视为次要任务，与构建功能和修复错误相比，它们的重要性不大。即使在 2023 年，我与许多团队合作时，他们仍然主要进行手动测试，仅在部署前进行测试，这是一个缓慢、繁琐且容易出错的过程，导致问题被忽视。自动化这些重复性任务可以显著提高效率，通过消除它们并更快地捕捉到回归问题。自动化测试并不能取代手动测试，但它确实有助于提高应用程序的速度和质量。&lt;/p>
&lt;p>尽管自动化测试可以帮助开发人员和测试人员更快地完成任务，但如果团队在整个产品生命周期中没有密切关注它，它可能成为一个障碍。我曾见过一些团队夸耀他们有成千上万个自动化测试用例，但仔细检查后发现自动化并没有像他们想象的那样帮助他们。团队可能在覆盖范围上存在巨大的空白，或者他们没有测试应用程序的高商业价值部分。测试套件运行时间也很长，并且由于不稳定性而经常失败，导致他们花费更多时间修复测试套件，从而占用了宝贵的时间。关键是确保自动化测试能够很好地平衡速度和质量，以帮助团队快速交付并尽快解决问题，以防止平衡倾斜到一侧。&lt;/p>
&lt;p>2）持续集成/持续部署
应用程序的自动化测试很好，但如果您或您的团队不经常运行这些测试，那么构建它们就是浪费时间。我在职业生涯中与数百名开发人员和测试人员合作的经验表明，很少有人在本地运行其应用程序的自动化测试套件。持续集成环境对于任何希望在快速交付产品时感到自信的组织来说都至关重要。一个良好构建的持续集成系统将尽早尽可能频繁地运行自动化测试，以便在问题变得更容易修复和更便宜时捕捉到潜在问题。&lt;/p>
&lt;p>在设置持续集成环境以自动化测试之后，下一步是研究持续部署以自动化软件发布。&amp;ldquo;持续部署&amp;quot;通常指的是当代码库中有任何新更改时自动部署应用程序。这种策略并不适用于所有人，因为它需要大量的时间、精力和信任来安全地实现。但采用这种方法的团队在其流程中构建了所需的高质量，从而在竞争中获得巨大的优势。如果一个团队出于任何原因不愿意自动化其部署，他们至少应该有改进部署流程的流程，因为这将迫使速度和质量保持良好的平衡。&lt;/p>
&lt;p>3）功能开关
功能开关是一种软件开发策略，允许团队在不更改底层代码的情况下设置可以打开或关闭的行为。通常，开发人员使用这种技术来构建和发布他们不希望在各个环境中广泛提供的功能，直到他们可以进一步开发和测试。这样，团队可以逐步以小规模发布功能，并在生产环境中进行测试，而不会造成系统中断。&lt;/p>
&lt;p>功能开关是在应用程序中平衡速度和质量的一种绝佳方式，因为它们允许团队在发布给大众之前在内部或与部分客户测试和实施新功能。测试人员可以拥有一个界面，可以在没有开发人员介入的情况下安全地切换代码库的不同部分。缺点是太多的开关可能会导致代码库混乱，并对开关背后的功能产生潜在的混淆。然而，合理使用这种方法可以使团队收集反馈，降低风险，并快速解决问题，同时增加部署高质量解决方案的信心。&lt;/p>
&lt;ol start="4">
&lt;li>金丝雀部署
许多组织通常会同时向所有用户发布新功能和功能。虽然这样可以使团队更快地部署，但可能会牺牲质量，特别是对于没有许多自动化测试或将持续集成纳入其工作流程的团队。即使有这些系统存在，由于开发、测试和部署阶段之间的差异，仍然存在出现问题的风险。开发人员可能会发现他们的应用程序在他们的计算机上运行得非常好，或者测试人员在测试环境中找不到问题，但由于微小的差异，应用程序在生产环境中出现问题。&lt;/li>
&lt;/ol>
&lt;p>金丝雀部署通过逐步将应用程序的部署提供给一部分用户，然后再向所有人提供，有助于减少这种风险。这个过程允许团队监控新功能的真实使用情况，并收集数据来确定变更的安全性。这些部署与特性标志略有相似之处，但工作在应用程序级别，因为流量在应用程序的旧版本和新版本之间进行划分。一切看起来都很好之后，团队可以将所有人重定向到最新版本的应用程序，并删除以前的版本。金丝雀部署需要一些前期工作，包括监控和可观察性系统，以跟踪部署后的系统行为。这些部署可能会使团队的速度变慢，因为他们必须等待完全部署任何变更。然而，确保质量所需的努力和时间最终会在未来提高速度。&lt;/p>
&lt;ol start="5">
&lt;li>团队之间的合作
在开发过程中，要在质量和速度之间取得平衡，并不是所有事情都需要技术解决方案。对于希望快速而安全地发布产品的任何组织来说，跨所有参与软件开发生命周期的团队（开发人员、质量保证和运维）建立协作文化至关重要。这些团队之间的相互联系将通过消除障碍和潜在的沟通问题，从头到尾创建一个流畅高效的工作流程，从而提高速度和质量。自动化测试、持续集成/持续交付、特性标志或金丝雀部署在产品的所有团队没有达成一致的情况下并不重要。
当团队紧密合作时，他们可以确保每个人都在做正确的事情，并提出比独立工作更好的解决方案。例如，测试人员通过识别开发人员在开发过程中没有考虑到的潜在弱点和边界情况来帮助开发人员，运维团队可以更轻松地为测试人员设置测试环境。这种合作努力必然会带来改进的产品，因为它们将不同的观点和专业知识融入到讨论中。当每个人共同努力时，他们可以完成比每个人单独所能做的更多的事情。&lt;/li>
&lt;/ol>
&lt;h3 id="结论">结论&lt;/h3>
&lt;p>团队可以尝试本文中讨论的一些或全部想法，努力实现速度和质量的良好平衡。您的组织可以从改进自动化测试和实施持续集成开始采取小步骤。一旦您开始注意到开发和质量保证的工作速度更快且问题更少，团队可以引入功能开关和金丝鸟部署，以帮助更顺畅、更高效地发布。即使您不实施这些策略中的大部分，紧密的跨团队合作将成为整个过程中最重要的组成部分。如果没有坚实的合作，您将无法取得很大进展。&lt;/p>
&lt;p>在追求工作速度和确保产品高质量之间找到适当的平衡并不容易。这需要组织内各个部门之间的大量努力和协调，包括开发、测试等等。当您努力实现这种平衡时，不要期望变化会立即发生。团队必须明白这是一个缓慢的过程，如果需要时间才能看到结果也是可以接受的。知道进展不会一夜之间发生是改善事物的第一步。通过努力采用本文提到的策略，您将更接近找到速度和质量的完美平衡点。&lt;/p></description></item><item><title>逃离微服务？为什么现在大家又在讨论微服务</title><link>https://itest2025.pages.dev/is-micro-service-dead_59/</link><pubDate>Fri, 08 Mar 2024 09:03:01 +0800</pubDate><guid>https://itest2025.pages.dev/is-micro-service-dead_59/</guid><description>&lt;p>最近比较热门的圈内话题，出了下云就是对微服务的讨论。&lt;/p>
&lt;p>微服务今年又一次的引起了广泛讨论可能是源自今年年初亚马逊内部的一次架构迁移。&lt;/p>
&lt;h3 id="起因">起因&lt;/h3>
&lt;p>在这个案例中，Prime Video 团队将一个监控系统从微服务架构迁移到单体架构，并避免使用昂贵的服务（如 AWS Step Functions 和 Lambda 无服务器函数），并对此举所带来的降本效果进行了评估。简单来说就是该团队通过将服务从微服务变成单体应用取得了明显的降本增效的效果。&lt;/p>
&lt;p>这个例子引发了广泛的关注和讨论，一时间人们似乎不知道该如何去评价这件事情。反倒是一直鼓吹下云和反对微服务化的 dhh（可以理解为著名网络喷子）一针见血，他指出：即使是&lt;strong>亚马逊也无法理解无服务器或微服务&lt;/strong>。&lt;/p>
&lt;h3 id="讨论">讨论&lt;/h3>
&lt;p>事件逐渐发酵，目前关于逃离微服务的讨论可以频繁的见诸报端，比如前几天有人就这样的评价微服务&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;microservices conflate logical boundaries (how code is written) with physical boundaries (how code is deployed).&amp;rdquo;
&amp;ldquo;we propose a different programming methodology&amp;rdquo;
&amp;ldquo;Our implementation reduces application latency by up to 15x and cost by up to 9x&amp;rdquo;&lt;/p>&lt;/blockquote>
&lt;ul>
&lt;li>&amp;ldquo;微服务将逻辑边界（代码编写方式）与物理边界（代码部署方式）混为一谈。&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;我们提出了一种不同的编程方法论。&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;我们的实现可以将应用程序的延迟降低多达 15 倍，成本降低多达 9 倍。&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;p>这里有一个非常有意思的点，那就是有没有一种可能单体服务比微服务其实要节省资源？&lt;/p>
&lt;p>还有人认为微服务其实是一种权衡，并给出了下列的忠告。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>谨慎地拥抱复杂性
投身于微服务不仅仅是一个技术决策，而是对复杂性的一种承诺。有时候，为了追随潮流，我们似乎在破坏一个系统。并非每个应用程序都需要一个相互连接的服务网络。正如 Sam Newman 在《构建微服务》一书中提到的那样，架构需要满足一定的前提条件，否则就可能过度设计。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>灵活性是有代价的
是的，微服务承诺了灵活性，但也伴随着沉重的代价，不仅仅是基础设施方面的代价，还包括认知负荷。每个服务都变成了自己的领域，需要专门的关注。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>没有一种大小适合所有
如果我从中获得了什么，那就是架构决策不能孤立地脱离业务需求而做出。一个灵活的初创公司的需求与传统的企业应用程序截然不同。虽然像 Netflix 那样的案例研究对我们启发很大，但我们必须认识到，适用于某个公司的解决方案未必适用于所有公司。&lt;/p></description></item><item><title>推荐：Docker备忘录</title><link>https://itest2025.pages.dev/docker-tips_57/</link><pubDate>Fri, 08 Mar 2024 09:02:58 +0800</pubDate><guid>https://itest2025.pages.dev/docker-tips_57/</guid><description>&lt;p>项目源地址：&lt;a href="https://github.com/wsargent/docker-cheat-sheet" target="_blank" rel="noopener noreffer ">https://github.com/wsargent/docker-cheat-sheet&lt;/a>&lt;/p>
&lt;h2 id="docker-的安装">docker 的安装&lt;/h2>
&lt;p>略，windows 上可以用 docker desktop，具体可以参考官方文档。&lt;a href="https://hub.docker.com/editions/community/docker-ce-desktop-windows" target="_blank" rel="noopener noreffer ">https://hub.docker.com/editions/community/docker-ce-desktop-windows&lt;/a>&lt;/p>
&lt;p>安装之后用 admin 身份打开 powershell，输入&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="c">#Display the version of docker installed:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">docker&lt;/span> &lt;span class="n">version&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">##Pull, create, and run &amp;#39;hello-world&amp;#39; all in one command:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">docker&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="nb">hello-world&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="容器相关">容器相关&lt;/h2>
&lt;h3 id="容器的生命周期">容器的生命周期&lt;/h3>
&lt;ul>
&lt;li>&lt;code>[docker create](https://docs.docker.com/engine/reference/commandline/create)&lt;/code>  创建但未启动&lt;/li>
&lt;li>&lt;code>[docker rename](https://docs.docker.com/engine/reference/commandline/rename/)&lt;/code>  重命名容器&lt;/li>
&lt;li>&lt;code>[docker run](https://docs.docker.com/engine/reference/commandline/run)&lt;/code>  一个命令创建并启动容器&lt;/li>
&lt;li>&lt;code>[docker rm](https://docs.docker.com/engine/reference/commandline/rm)&lt;/code>  删除容器&lt;/li>
&lt;li>&lt;code>[docker update](https://docs.docker.com/engine/reference/commandline/update/)&lt;/code>  更新容器的资源限制&lt;/li>
&lt;/ul>
&lt;p>几个有用的点&lt;/p>
&lt;ul>
&lt;li>一般启动容器我们用 docker run -td container_id, -t 表示分配 TTY session，-d 表示后台运行并打印容器 id&lt;/li>
&lt;li>docker run &amp;ndash;rm 用来运行容器后自动删除&lt;/li>
&lt;li>docker run -v $HOSTDIR:$DOCKERDIR 用来做 volume 的映射&lt;/li>
&lt;/ul>
&lt;h3 id="容器的启动和停止">容器的启动和停止&lt;/h3>
&lt;ul>
&lt;li>&lt;code>[docker start](https://docs.docker.com/engine/reference/commandline/start)&lt;/code>  运行容器&lt;/li>
&lt;li>&lt;code>[docker stop](https://docs.docker.com/engine/reference/commandline/stop)&lt;/code>  停止一个运行中的容器&lt;/li>
&lt;li>&lt;code>[docker restart](https://docs.docker.com/engine/reference/commandline/restart)&lt;/code>  重启容器&lt;/li>
&lt;li>&lt;code>[docker pause](https://docs.docker.com/engine/reference/commandline/pause/)&lt;/code>  暂停容器&lt;/li>
&lt;li>&lt;code>[docker unpause](https://docs.docker.com/engine/reference/commandline/unpause/)&lt;/code>  继续运行一个暂停了的容器&lt;/li>
&lt;li>&lt;code>[docker wait](https://docs.docker.com/engine/reference/commandline/wait)&lt;/code>  等待直到容器停止&lt;/li>
&lt;li>&lt;code>[docker kill](https://docs.docker.com/engine/reference/commandline/kill)&lt;/code>  发送 SIGKILL 信号给容器&lt;/li>
&lt;li>&lt;code>[docker attach](https://docs.docker.com/engine/reference/commandline/attach)&lt;/code>  连接运行中的容器&lt;/li>
&lt;/ul>
&lt;h3 id="限制-cpu-和内存">限制 cpu 和内存&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">docker&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="n">-it&lt;/span> &lt;span class="n">-c&lt;/span> &lt;span class="mf">512&lt;/span> &lt;span class="n">agileek&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="nb">cpuset-test&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>上面的命令限制了 cpu 使用率为 50%，看起来很奇怪对不对？因为 100%使用率用的参数是-c 1024，所以 50%就是 512&lt;/p></description></item><item><title>给测试开发工程师的5条建议</title><link>https://itest2025.pages.dev/5-tips-for-developers_55/</link><pubDate>Fri, 08 Mar 2024 09:02:55 +0800</pubDate><guid>https://itest2025.pages.dev/5-tips-for-developers_55/</guid><description>&lt;p>近些年可以看出测试开发工程师是热度比较高的测试职位，除了涵盖了之前自动化测试工程师的职能外，测开同学的开发能力进一步提升，可以做到开发一些测试平台和测试框架的工作，并在推广自动化测试方面也有一定的 kpi 要求，能力越大责任越大，正好看到了国外有同行写的给自动化测试工程师的几条建议，觉得还是有一定道理的，所以在这里简单的分享一下，希望对大家所有裨益。&lt;/p>
&lt;h3 id="质量心态">质量心态&lt;/h3>
&lt;p>作为测试开发工程师，我们可能专注于学习测试工具和测试框架，提升代码能力，日复一日，循环往复。学习是没问题的，不管你是测试开发还是功能测试同学，持续学习应该是整个职业生涯里必不可少的一部分。话虽如此，不过对于测试同学来说，全方位的质量心态还是很有必要的。&lt;/p>
&lt;p>那么质量心态是什么呢？作为测试开发同学，我们应该关注项目/产品质量的方方面面，而不是仅仅满足于将验收标准或者是手工用例转化成自动化脚本。&lt;/p>
&lt;p>相反，我们应该站在质量控制的层面去从用户的角度带入思考，如果我们的脚本不能为用户带来价值，那么这些脚本其实就没有价值。&lt;/p>
&lt;p>举一个例子，我们在提交一个表单的时候，比如注册用户以后，我们的 ui 自动化用例是不是需要去查数据库，看看新的用户记录已经被持久化到了用户表里？我的答案是：在开发时间有限的前提下不需要去连数据库查询，因为用户是不会连数据库直接看数据的，我们应该从用户可以感知到的方面进行断言。连数据库查询的事情可以交给其他类型的自动化用例，比如单元测试用例去实现。&lt;/p>
&lt;h3 id="获取其他测试领域的知识">获取其他测试领域的知识&lt;/h3>
&lt;p>相比于成为某种工具或者编程语言的专家，测试开发工程师可能更有必要成为一名通才，我们最核心的观念应该是帮助团队满足用户的需求和期望。&lt;/p>
&lt;p>因此除了功能测试和自动化测试之外，我们是需要学习其他领域的知识，比如性能测试，可用性测试，可测试性，安全测试，移动端测试，可视化测试和数据测试。&lt;/p>
&lt;p>技术已经发展了很多年，我们几乎每天都有许多领域和新技能需要学习。让我们探索一些可以帮助你提升自动化工程师职业生涯的领域：&lt;/p>
&lt;p>&lt;strong>探索性测试&lt;/strong>&lt;/p>
&lt;p>老生常谈的话题了，探索性测试可以叫做老司机测试，但探索性测试并不是随心所以，而是需要精心计划和设计一些测试用例。我们可以通过探索性测试来开阔用例设计的思路，从而改进我们的 e2e（也就是 ui）测试用例。《Explore It!: Reduce Risk and Increase Confidence with Exploratory Testing》阅读这本书可能是一个不错的开始，不过我好像并没找到中文版本，有点可惜了。&lt;/p>
&lt;p>&lt;strong>数据测试&lt;/strong>&lt;/p>
&lt;p>机器学习和人工智能每天都在使用数据，但数据的有效性还是要验证的。我的建议是开始学习数据模型的性能，这有助于我们弄清楚一些 ML 预测错误的具体场景。&lt;/p>
&lt;p>&lt;strong>可视化测试&lt;/strong>&lt;/p>
&lt;p>其实就是 ui 测试了，ui 是产品和用户打交道的最终途径，很多情况下也是唯一途径，生产环境中的视觉错误会危及我们的声誉并影响我们的品牌，所以 ui 的测试是非常必要的，也是需要我们去花精力学习的。&lt;/p>
&lt;p>&lt;strong>可访问性测试&lt;/strong>&lt;/p>
&lt;p>正如您可能知道的那样，许多国家/地区的法律法规要求让每个人都可以访问应用程序，并且创建一种必须适合所有人的软件应用程序文化。比如产品对盲人用户的可访问性就是一个很好的例子。我们可以从&lt;a href="https://www.w3.org/WAI/WCAG21/quickref/" target="_blank" rel="noopener noreffer ">https://www.w3.org/WAI/WCAG21/quickref/&lt;/a>开始进行学习。&lt;/p>
&lt;p>&lt;strong>安全测试&lt;/strong>&lt;/p>
&lt;p>就法律处罚和品牌声誉而言，安全漏洞往往代价高昂。我们应该在 CI 的 pipeline 中增加安全扫描的环境。我的建议是开始阅读&lt;a href="https://owasp.org/" target="_blank" rel="noopener noreffer ">OWASP&lt;/a>和学习一些安全测试工具。&lt;/p>
&lt;p>&lt;strong>混沌测试&lt;/strong>&lt;/p>
&lt;p>混沌测试/混沌工程测试是在影响客户之前发现漏洞和进行中断。简而言之，我们希望系统是可以在受控的环境中进行错误恢复的。如果你想学习混沌测试，那么《Chaos Engineering: System Resiliency in Practice》这本书将是一个不错的开始。&lt;/p>
&lt;h3 id="获得正确的帮助">&lt;strong>获得正确的帮助&lt;/strong>&lt;/h3>
&lt;p>获得他人的支持可以加速学习周期并显着改善您的职业生涯。但是，首先得找一个你信任的导师，他已经掌握了你想学习的技能，这些技能可以是技术技能或软技能。&lt;/p>
&lt;p>我的建议是你问问自己：“我公司或我的人脉网中谁会注意到我的变化并为我提供诚实的反馈？”  有时候找导师可能很困难，我们希望有最好的老师来指导我们。如果你在你的社交网络中找不到导师，你可以要求你的上级提供高质量的指导。&lt;/p>
&lt;p>“如果你想快点走，就一个人走。想走远一点，就一起走”&lt;/p>
&lt;h3 id="自动化不仅仅是执行测试脚本">自动化不仅仅是执行测试脚本&lt;/h3>
&lt;p>这个很好理解，自动化的目的是什么？大家可能有自己的答案，但答案一定不会是执行用例并使其通过。&lt;/p>
&lt;p>其实我总结自动化的目标是帮助团队从质量和效率的维度满足用户的预期。&lt;/p>
&lt;p>质量很好理解，我们不希望用户用到全是 bug 的产品；效率也不难想象，我们希望用户可以尽快的用到产品。&lt;/p>
&lt;h3 id="分享也是学习的一种途径">分享也是学习的一种途径&lt;/h3>
&lt;p>正如你可能知道的那样，教导他人可以提高你自己的学习能力。记得之前看到过一个学习的方法就是先自己学一遍，然后把自己学到的东西讲给别人去听，如果别人能弄明白，那么你自己就学会了。&lt;/p></description></item><item><title>基于k8s的测试执行工具：TestKube</title><link>https://itest2025.pages.dev/a-k8s-based-tool_53/</link><pubDate>Fri, 08 Mar 2024 09:02:52 +0800</pubDate><guid>https://itest2025.pages.dev/a-k8s-based-tool_53/</guid><description>&lt;p>之前我们自己开发过一些基于 k8s 的执行用例执行工具，原理大致是在用例执行的时候动态去 k8s 上创建容器，执行任务，上报指标，最后销毁容器，不过这些过程基本上与测试过程耦合在一起，难以平移扩展。最近发现了一款在开发早期的通用型的基于 k8s 的用例执行工具: TestKub。&lt;/p>
&lt;h3 id="有用的链接">有用的链接&lt;/h3>
&lt;ul>
&lt;li>项目主页：&lt;a href="https://kubeshop.github.io/testkube/" target="_blank" rel="noopener noreffer ">TestKube (kubeshop.github.io)&lt;/a>&lt;/li>
&lt;li>代码库：&lt;a href="https://github.com/kubeshop/testkube/" target="_blank" rel="noopener noreffer ">GitHub - kubeshop/testkube: Kubernetes-native framework for test definition and execution&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="特性">特性&lt;/h3>
&lt;ul>
&lt;li>支持执行 postman collection&lt;/li>
&lt;li>支持执行 cypress 的 ui 测试用例&lt;/li>
&lt;li>支持执行基于 curl 的简单探活，比如站点，接口有没有挂的检测之类&lt;/li>
&lt;/ul>
&lt;h3 id="工具希望解决的实际问题">工具希望解决的实际问题&lt;/h3>
&lt;ul>
&lt;li>避免 vender 锁定 CI/CD 管道中的测试编排和执行&lt;/li>
&lt;li>在集群中轻松编排和运行任何类型的测试 - 功能、负载/性能、安全性、合规性等 - 无需将它们打包成在 docker-images 或提供网络访问&lt;/li>
&lt;li>使测试执行与构建过程分离成为可能； 工程师应该能够在需要时运行特定的测试&lt;/li>
&lt;li>以一致的格式集中所有测试结果，以实现“可操作的 QA 分析”&lt;/li>
&lt;li>提供模块化架构以添加新类型的测试脚本和执行器&lt;/li>
&lt;/ul>
&lt;p>简单来说就是提供了与 ci/cd 解耦的纯测试容器编排和执行能力，并提供了统一的报告输出。&lt;/p>
&lt;h3 id="主要模块">主要模块&lt;/h3>
&lt;ul>
&lt;li>kubectl 插件&lt;/li>
&lt;li>API Server - 调度器，执行器，收集执行结果&lt;/li>
&lt;li>CRDs Operator - 观看 TestKube CR，处理与 API Server 通信的更改&lt;/li>
&lt;li>Executors - 运行为特定运行程序定义的测试，目前可用于 Postman、Cypress 和 Curl&lt;/li>
&lt;li>结果数据库 - 用于集中测试结果管理&lt;/li>
&lt;li>一个简单的基于浏览器的看板，用于监控测试结果&lt;/li>
&lt;/ul>
&lt;h3 id="总结">总结&lt;/h3>
&lt;p>这里就不列举如何安装以及简单使用该工具进行用例执行了，目前 TestKube 的版本是 0.6，还处在早起的开发阶段，不过项目的文档较为全面，而且模块化良好，有一定的扩展性，所以后面可能吸引一些使用者，有强烈需求的同学可以直接拿来就用，拿不定主意的同学建议再观察一定时间，等到 1.0 版本再入坑。&lt;/p></description></item><item><title>测试同学找工作要不要刷题</title><link>https://itest2025.pages.dev/are-testers-need-to-use-leetcode_52/</link><pubDate>Fri, 08 Mar 2024 09:02:51 +0800</pubDate><guid>https://itest2025.pages.dev/are-testers-need-to-use-leetcode_52/</guid><description>&lt;p>最近有公司裁员火到上了热搜，今年就业形式不容乐观，相信有不少同学正在努力找工作中，另外可能有一些同学被裁员的阴影所笼罩，也许在默默的为下一份工作而努力。看到一些开发同学正在刷题刷的飞起，而与之对应的是工作机会的减少，简历字面要求的提高，以及面试周期的增加，据说现在面试题难出了天际，其实也是一种变相提高门槛的表现，那么这个时间点测试同学在面试之前是否需要刷题呢？&lt;/p>
&lt;p>答应是不一定，具体情况可以具体对待。&lt;/p>
&lt;h3 id="初级测试同学">初级测试同学&lt;/h3>
&lt;p>一些公司对初级测试的同学的要求不是特别高，人聪明能干活就行，所以可能不需要频繁刷题，但一些简单的编程能力还是要有，防止被一些不太复杂的代码题被动过滤，如果时间不是很充裕的话，优先级是了解测试流程，测试方法，测试工具，各种测试种类（功能性能接口等），最后才是简单的算法和数据结构题。&lt;/p>
&lt;h3 id="中高级测试同学">中高级测试同学&lt;/h3>
&lt;p>其实中高级在技术上的要求差不多，所以放到一起讲，技术的广度上中高级的区别不大，不过深度上高级同学可能需要有一个强点可以侧重，能讲出东西来，让人信服。这两个职级都强烈建议刷题，不过优先刷简单的题先，中等难度的适当刷一点点，有些实在是看不明白的放弃也不可惜，高难度的题的就不用刷了，如果在面试中遇到的话，那这个职位可能是为某些人高度定制化的，或者根本就没有诚心招人，做不出来也不要紧。至于算法，可以了解比较简单的排序递归等，高级一点的贪心算法和动态规划可以适当的看看，大概知道概念，做不出来也问题不大。数据结构的话推荐优先了解二叉树及各种变种，更复杂的数据结构不看也可以。&lt;/p>
&lt;h3 id="管理岗位">管理岗位&lt;/h3>
&lt;p>管理岗位的话一般来说不用刷题，因为管理者可能很多年都没写过代码了，实在是霸王硬上弓的话可能会让场面一度显得比较尴尬。不过不写不意味着就可以不知道，数据结构和算法应该有所了解，比如 merge sort 写不出来具体的实现，但是其过程和原理应该是可以表述清楚的。算法的话可以了解一些简单的，数据结构也是从二叉树开始，结束在你没有时间去了解的地方。&lt;/p>
&lt;h3 id="测试开发">测试开发&lt;/h3>
&lt;p>岗位相对较少，所以可能会更卷一些。建议初中级难度的全部刷完，高级难度如果能熟练掌握的话就转开发吧，大部分的开发没有花时间准备的话中高级题都很难写出来，简单题翻车也不是没有可能。&lt;/p>
&lt;h3 id="其他测试相关岗位">其他测试相关岗位&lt;/h3>
&lt;p>比如 pmo，qa 等质量度量和流程管理类的角色，其核心竞争力与刷题无关，不刷是完全没问题的。&lt;/p></description></item><item><title>给测试人员的7条建议</title><link>https://itest2025.pages.dev/seven-suggestions-for-testers_50/</link><pubDate>Fri, 08 Mar 2024 09:02:47 +0800</pubDate><guid>https://itest2025.pages.dev/seven-suggestions-for-testers_50/</guid><description>&lt;h3 id="理解业务">理解业务&lt;/h3>
&lt;p>要深入理解业务领域和用户需求。这将有助于设计更好的测试场景,确保软件满足用户的需求。&lt;/p>
&lt;h3 id="成为问题解决者">成为问题解决者&lt;/h3>
&lt;p>在开始自动化测试之前,要充分理解问题,思考如何解决它。作为一名 SDET,您的工作不仅仅是将功能测试用例自动化,我们的工作是使测试过程更加高效,消除减慢过程并不必要消耗时间的瓶颈。&lt;/p>
&lt;h3 id="保持开放的心态并愿意学习新事物">保持开放的心态并愿意学习新事物&lt;/h3>
&lt;p>对测试人员来说,保持开放的心态和愿意学习新事物是至关重要的,因为软件测试领域不断发展。新技术,方法和工具不断涌现,测试人员需要随时了解这些发展,才能在其角色中保持效率。
开放的心态意味着接受新想法,新观点和新方法。测试人员应该对学习新测试技术,探索新工具和采用新方法持开放态度。他们也应该愿意尝试新方法和技术,看什么对他们和他们的团队最有效。
开明也意味着愿意质疑假设和先入为主的看法。测试人员不应该假定某种方法或工具始终是最佳选择。相反,他们应该愿意评估不同的选择,选择最适合情况的选择。
此外,开明意味着愿意接受反馈和批评。测试人员应该愿意接受同事和利益相关者的反馈,并用它来改进工作。他们也应该对测试方法持开放态度,如果需要的话,愿意作出改变。
总之,对测试人员来说,开明和愿意学习新事物对于在不断发展的领域保持相关性和效率至关重要。它使他们能够适应新挑战,保持创新,不断提高技能和知识&lt;/p>
&lt;h3 id="别害怕提问或表达疑虑">别害怕提问或表达疑虑。&lt;/h3>
&lt;p>在软件测试中,提问和表达疑虑是工作的重要组成部分。测试人员负责识别软件中的缺陷和问题,他们常常需要寻求澄清或就需求,设计或功能提出疑虑。
提问是一种收集信息和澄清需求的方式。测试人员应该提出一些问题,以确保他们理解软件的预期行为,并发现需求中的任何歧义或缺口。这可以帮助确保软件能够满足用户和企业的需求。
表达疑虑对测试人员也很重要。如果测试人员发现软件中的潜在问题,他们应该向相关的利益相关者提出。这可能包括开发团队,项目经理或业务相关人员。及早表达疑虑可以帮助避免更大的问题。
测试人员不害怕提问或表达疑虑是非常重要的。测试人员应该感到自在地寻求澄清,质疑假设和识别潜在问题。这样做可以帮助确保软件满足用户和企业的需求,并按时在预算范围内交付。
总之,提问和表达疑虑是软件测试的关键部分。它有助于确保软件达到必要的标准,并根据用户和企业的需求进行开发。测试人员应该有权提出澄清和表达疑虑,以确保软件质量高。&lt;/p>
&lt;h3 id="始终测试可用性这和功能测试同样重要">始终测试可用性——这和功能测试同样重要。&lt;/h3>
&lt;p>可用性测试是软件测试的关键部分,它和功能测试同样重要。可用性测试是评估产品的用户界面(UI)和用户体验(UX)的过程,以确保它对用户友好,高效和有效。
可用性测试的目的是识别任何可能阻止用户有效使用软件的可用性问题。这些问题可能包括混乱或杂乱的 UI,响应时间慢,缺乏反馈或难以理解的错误信息。
可用性测试涉及观察用户与软件的交互,并要求他们执行特定任务。测试人员将寻找任何用户难以掌握或出错的地方,并收集有关软件可用性的反馈。
可用性测试至关重要,因为即使产品具有所有必要的功能,如果使用不便,用户很可能会感到沮丧和抛弃它。这可能导致收入损失,负面评论和对公司声誉的损害。
因此,可用性测试应该从一开始就集成到软件测试过程中。它应该与功能测试一起考虑,以确保软件满足用户需求并提供积极的用户体验。
总之,可用性测试与功能测试同样重要。测试人员应该通过测试 UI 和 UX 来确保软件对用户友好,高效和有效。这样做可以帮助确保软件满足用户需求并提供积极的用户体验。&lt;/p>
&lt;h3 id="记录下每一件事情这将有助于你和你的团队在将来">记录下每一件事情——这将有助于你和你的团队在将来。&lt;/h3>
&lt;p>&amp;#x1f913;记录是软件测试的重要部分。它涉及创建和维护有关所有测试活动的记录,包括测试计划、测试案例、测试结果和缺陷。在测试过程中记录每一件事情至关重要,因为它将帮助测试人员和他们的团队在未来。
记录可以帮助测试人员跟踪他们的进度,找出潜在的问题和缺陷,并在整个测试过程中跟踪缺陷。通过记录每一件事情,测试人员可以轻松参考以前的测试活动,确保他们正在测试软件的正确区域。
记录每一件事情也有助于测试人员和他们的团队在未来。当新成员加入项目或软件需要更新或修改时,有记录可以帮助确保团队清楚了解已经测试了什么和还需要测试什么。
此外,记录有助于知识转移。当测试人员离开团队或项目时,他们创建的记录可以被替代者用于快速了解已经测试了什么和还需要测试什么。
而且,记录有助于通过提供缺陷及其解决方案的记录来提高软件质量。此信息可用于识别缺陷的模式或趋势,从而改进软件开发过程。
总之,在软件测试中记录每一件事情至关重要。它可以帮助测试人员跟踪进度,找出潜在的问题和跟踪缺陷。它也可以帮助团队在将来确保新成员了解已经测试了什么和还需要测试什么。最后,记录可以通过提供有价值的缺陷和解决方案信息来改进软件质量。&lt;/p>
&lt;h3 id="制定测试计划并坚持执行">制定测试计划并坚持执行。&lt;/h3>
&lt;p>制定测试计划是软件测试的基本步骤,它涉及定义测试目标、范围、方法和时间表。一个明确定义的测试计划为测试过程提供了清晰的路线图,并帮助测试人员专注于测试目标。
测试计划应概述测试目标和优先级以及要执行的具体测试任务。该计划还应确定测试环境、测试工具和技术以及预期结果。
通过制定测试计划,测试人员可以确保执行所有必要的测试任务,并且不遗漏任何关键区域。该计划也可以帮助测试人员更有效和高效地管理时间和资源。
然而,仅制定测试计划是不够的;坚持执行它同样重要。偏离测试计划可能导致测试不完整、错过缺陷和测试过程延迟。因此,测试人员应尽可能密切地遵循测试计划,仅在必要时和经过仔细考虑后进行调整。
坚持测试计划需要纪律、专注和注重细节。它涉及监控进度、识别与计划的任何偏差并在必要时采取纠正措施。
总之,制定测试计划对测试过程的成功至关重要。它为测试提供了路线图,并帮助测试人员专注于目标。但是,坚持计划同样重要,以确保测试所有关键区域,并有效和高效地完成测试过程。&lt;/p></description></item><item><title>测试低下论或现代测试行业所遇到的问题</title><link>https://itest2025.pages.dev/are-testers-second-class-in-development_49/</link><pubDate>Fri, 08 Mar 2024 09:02:46 +0800</pubDate><guid>https://itest2025.pages.dev/are-testers-second-class-in-development_49/</guid><description>&lt;p>前几天看到一篇 blog 讨论现代测试行业中存在的一些问题，我愿称之为测试行业的劝学篇。有些观点还是非常中肯的，翻译了一下，希望对大家有所帮助，下面是正文。&lt;/p>
&lt;p>在这篇博文中，我想强调我在测试行业看到的问题，以及我们都可以如何解决它。(你可能有不同的经历&amp;ndash;所以让我们在评论中分享吧！）。这是我在 2022 年初在阿布扎比 MoT 会议上发表的演讲的文字版。&lt;/p>
&lt;h2 id="10-年来行业发生了什么变化">10 年来行业发生了什么变化&lt;/h2>
&lt;p>在过去的十年里，我们见证了技术的大幅提升&amp;ndash;无人驾驶汽车、人工智能、AR / VR、区块链、无人机和机器人。许多测试和测试自动化从桌面转移到网络和移动设备上。&lt;/p>
&lt;p>从瀑布模型，许多企业走向了敏捷和 Scrum。&lt;/p>
&lt;p>从编写自动化测试的大型和昂贵的工具，我们已经转移到小型，灵活，最重要的是 - 免费的库和工具。现在我们有智能报告系统和 Docker 容器中的测试。现在在云端并行运行成千上万的测试用例并其实不那么昂贵。&lt;/p>
&lt;p>但与此同时，许多事情和声明仍然没有改变。&lt;/p>
&lt;p>以下是一些在测试人员中不断肆虐的无尽话题&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;手工测试已死！&amp;rdquo;
&amp;ldquo;让我们把一切都自动化吧! 通过 UI&amp;ndash;这才是最好的！&amp;rdquo;*。
&amp;ldquo;SDET 是测试工程师进化的高峰! 我想成为像谷歌一样的人！&amp;rdquo;
&amp;ldquo;我在开发领域找不到工作，所以我将做一年左右的测试，然后再尝试转行。&amp;rdquo;
&amp;ldquo;测试人员的工资都在底部！&amp;rdquo;
&amp;ldquo;测试工程是一份低技能专业人士的工作！&amp;rdquo;&lt;/p>&lt;/blockquote>
&lt;p>但为什么行业中会出现这样的情况？问题会不会是别的原因呢？我看到了哪些问题？&lt;/p>
&lt;h2 id="现代测试的问题">现代测试的问题&lt;/h2>
&lt;p>我将把问题分为两个大组。&lt;/p>
&lt;h3 id="对技术的追求">对技术的追求&lt;/h3>
&lt;ul>
&lt;li>**寻找银弹。**一些工程师为一个项目（和一个背景）创建了一个成功的框架，然后开始把它拉到所有其他的项目中作为一个 &amp;ldquo;完美的解决方案&amp;rdquo;。这不是可重用性，它只是将一把锤子怼所有的钉子和螺栓。&lt;/li>
&lt;li>**面向框架的自动化。**在一些项目中，工程师们急于编写完美的框架，而忘记了测试本身。结果是，我们有 3-6 个月的开发时间没有测试！。但是对于客户来说，没有测试是自动化应用不充分的的直接指标。&lt;/li>
&lt;li>**专注于流水线通过的崇拜者。**在这种情况下，已经成功地将测试纳入 CICD 管道的测试工程师开始迷恋于使测试一直保持绿色。这种痴迷往往导致忽略甚至删除不稳定的测试（这导致忽略了被忽略的测试背后的问题）。&lt;/li>
&lt;li>**简历驱动的开发。**许多测试人员只考虑工作和项目，作为在简历中获得一个花哨的新行的方式。没有任何关于深化技能和用测试和自动化解决业务问题的内容。&lt;/li>
&lt;li>**只专注于短期目标。**许多工程师只喜欢快速的解决方案。这种对自动化测试的态度是很普遍的。工程师们很快就写出了 &amp;ldquo;东西&amp;rdquo;，而没有考虑到可维护性，就跑去做新项目了。&lt;/li>
&lt;/ul>
&lt;h3 id="技能和知识水平不足">技能和知识水平不足&lt;/h3>
&lt;p>知识的缺乏可以有多种形式。&lt;/p>
&lt;p>&lt;strong>技术知识&lt;/strong>&lt;/p>
&lt;p>在面试中，资深候选人往往能迅速回答任何关于测试的问题。他们可以为你画一个 &amp;ldquo;测试金字塔或任何你想要的数字&amp;rdquo;，告诉你世界上所有的测试设计技术，并写出完美的测试报告。但是，当你问及基本技术方面的问题（如 HTTP 或网络如何工作），候选人很快就会失去信心。&lt;/p>
&lt;p>你可以告诉我，不是每个项目都需要特定的知识。这倒是真的。但测试工程师应该知道（或至少知道）基本的技术知识。理想情况下，比谷歌搜索中的第一个链接更深入一点。&lt;/p>
&lt;p>90%的现代系统以这种或那种方式与网络一起工作，发送消息或请求，并使用数据库或分布式存储。多层的抽象可以覆盖它&amp;ndash;但总的来说&amp;ndash;它的工作原理都是类似的。&lt;/p>
&lt;p>&lt;strong>编程和架构&lt;/strong>&lt;/p>
&lt;p>一些测试工程师仍然认为，学习编程是复杂和不必要的。让程序员来写代码吧!&lt;/p>
&lt;p>另一部分是那些已经学会了一点代码，但只集中精力于 UI 测试的人。这些测试之外的世界似乎并不存在。&lt;/p>
&lt;p>在测试工程师中，关于系统结构和它们如何工作的知识是一种稀缺的技能。所有这些都被认为是有经验的 &amp;ldquo;大胡子 &amp;ldquo;架构师的工作，或者只有有十几年经验的高级开发人员才能做。&lt;/p>
&lt;p>但是，如果不知道系统的内部和相互之间是如何工作的，就很容易错过很多关键的错误。另一方面，缺乏技术知识将使我们很难在测试报告中描述这些问题。&lt;/p>
&lt;p>我并不是说，如果不了解系统的组成部分，就不可能指出系统糟糕的不可测试性。结果是，我们继续编写脆弱的 XPath 定位器，因为没有人给元素添加 ID。&lt;/p></description></item><item><title>不做单元测试的6大借口</title><link>https://itest2025.pages.dev/no-execuses-for-not-writing-unit-tests_48/</link><pubDate>Fri, 08 Mar 2024 09:02:44 +0800</pubDate><guid>https://itest2025.pages.dev/no-execuses-for-not-writing-unit-tests_48/</guid><description>&lt;p>看到了一篇不错的关于单元测试的文章，于是就机翻加改写了一下。作者的观点是适当的，不过稍微欠缺了些数据。原文地址：&lt;a href="https://betterprogramming.pub/why-dont-we-do-unit-testing-e0bb55a38aa2" target="_blank" rel="noopener noreffer ">https://betterprogramming.pub/why-dont-we-do-unit-testing-e0bb55a38aa2&lt;/a>&lt;/p>
&lt;p>我开始打算写一篇关于单元测试及其背后的哲学和过程的文章。
我想谈谈完成对项目的一组更改并能够部署它们所带来的满足感，因为数百个单元测试正在通过，并且在生产中出现问题的可能性很小。
然后我意识到我参与过的大多数项目（在计算行业的长期职业生涯中）都没有使用任何类型的单元测试。 因此，我认为检查未使用单元测试的各种原因以及对这些项目的影响可能会有所帮助。&lt;/p>
&lt;h3 id="没有测试工具">没有测试工具&lt;/h3>
&lt;p>当我在 80 年代开始以编程为生时，这是一个很好的借口。在 pascal 中编写航空电子代码，我们确实编写了测试，但几乎不得不推出我们自己的测试框架。这里没有 fakes、mocks 或 DI。
但是，如果您今天使用 Visual Studio 在 .Net/C# 中编写代码，则没有理由不测试任何重要代码。
VS Code 有一个内置的测试运行器，其他测试运行器也可用。fake 和 mock 框架被广泛使用，另外还有非常优秀的 CI/CD 支持，可以让你在构建过程中自动化的运行单元测试。&lt;/p>
&lt;blockquote>
&lt;p>后面作者又列举了一些例子来证明一个观点：就是测试工具实际上是持续进化的，而且现在已经很强大了，因此他的核心观点应该是在如今这个年代，单元测试工具其实不应该是匮乏的，大家应该有不少的选择。&lt;/p>&lt;/blockquote>
&lt;h3 id="我们没有时间做测试">我们没有时间做测试&lt;/h3>
&lt;p>客户希望我们现在发货。写一些代码就行了。先搞出来吧，没有时间学习有关单元测试的象牙塔软件工程学士学位！
你多久听说过一次？
这种方法通常似乎在最初几周有效。然后，当您陷入莫名其妙的错误和副作用的泥潭时，生产力就会下降。加倍努力，开夜车，让更多的人进行手动测试！
不必如此。我会说你没有时间不做测试。&lt;/p>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="%E4%B8%8D%E5%81%9A%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E7%9A%846%E5%A4%A7%E5%80%9F%E5%8F%A3%200a0c298766af4bcc8f2c295a00c787df/Untitled.png"
 data-srcset="%E4%B8%8D%E5%81%9A%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E7%9A%846%E5%A4%A7%E5%80%9F%E5%8F%A3%200a0c298766af4bcc8f2c295a00c787df/Untitled.png, %E4%B8%8D%E5%81%9A%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E7%9A%846%E5%A4%A7%E5%80%9F%E5%8F%A3%200a0c298766af4bcc8f2c295a00c787df/Untitled.png 1.5x, %E4%B8%8D%E5%81%9A%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E7%9A%846%E5%A4%A7%E5%80%9F%E5%8F%A3%200a0c298766af4bcc8f2c295a00c787df/Untitled.png 2x"
 data-sizes="auto"
 alt="%E4%B8%8D%E5%81%9A%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E7%9A%846%E5%A4%A7%E5%80%9F%E5%8F%A3%200a0c298766af4bcc8f2c295a00c787df/Untitled.png"
 title="Untitled" />&lt;/p>
&lt;p>看看上面的图表。橙色线显示“没有做单元测试”项目。
起初，我们可以通过不进行任何测试来节省时间。
随着越来越多的功能被实现，（手动）回归测试的负担呈指数级增长。添加的每个新功能都会带来额外的回归测试负担。
很快，您的项目将不得不在质量、成本和实现每个新功能所需的时间之间做出妥协。
所以是的，最初编写一个好的单元测试可能比手工测试你的功能要长两到三倍。但是经过两三个循环的回归测试，你就会领先——而且只会变得更好。&lt;/p>
&lt;h3 id="我们发现写单元测试好难">我们发现写单元测试好难&lt;/h3>
&lt;p>编写测试很难，这是绝对正确的。 编写好的测试更难。但不写测试会让你的生活更加艰难（或者你真的喜欢手动测试你的前任 5 年前写的代码？？？）
如果您的开发人员不知道如何编写好的测试，那么他们可能还不是好的开发人员（目前）。 但是他们可以学习。
写测试用例是一个不断进度熟能生巧的过程， 你做的越多，它就会变得越快越容易。
让最好的开发人员编写“模范”测试。 这些可以被经验不足的开发人员用作模板来创建他们自己的测试。
创建测试基类。 我经常这样做是为了封装经常重用的功能并简化单个测试用例。&lt;/p>
&lt;h3 id="单元测试是白费力气">单元测试是白费力气！&lt;/h3>
&lt;p>当我花时间编写单元测试时，我从不认为这是浪费时间。
如果我编写代码并手动测试它，我认为这是浪费精力。
在一系列手动测试结束时我会得到什么？ 代码在我做手动测试时候是没问题可以正常工作的，但如果我更改代码，我将不得不重复这些测试（或者更有可能不打扰&amp;hellip;&amp;hellip;）
如果我创建了自动化测试，那么我就有了一个测试套件，我可以随时重新运行这些测试，以验证代码是否仍然按照我编写时预期的方式工作。
更重要的是，如果我离开并且一位同事接管了这段代码，他们就会继承这个祖传的测试套件，他们可以使用这些测试来验证他们的更改没有破坏它，并且代码仍然以我预期的方式工作。
手动测试是浪费精力——手动测试的剩余价值为零（并且通常没有记录，因此不可重复）。 自动化测试可以在未来几年内使用，以证明您的软件仍然按预期工作。&lt;/p></description></item><item><title>开发人员应该进行思路转变以提升软件质量</title><link>https://itest2025.pages.dev/mindset-shifts-for-engineers-to-achieve-higher-software-quality_47/</link><pubDate>Fri, 08 Mar 2024 09:02:43 +0800</pubDate><guid>https://itest2025.pages.dev/mindset-shifts-for-engineers-to-achieve-higher-software-quality_47/</guid><description>&lt;p>*&lt;strong>*Mindset Shifts For Engineers to Achieve Higher Software Quality**&lt;/strong>&lt;/p>
&lt;p>看到个开发小哥写的关于测试的文章，挺有意思的，翻译了一下，观点虽不新颖，但能从开发角度去思考软件质量，格局上面是值得称赞的。原文地址：&lt;a href="https://medium.com/@phdmeyildiz/mindset-shifts-for-engineers-to-achieve-higher-software-quality-8ef8ee00a041" target="_blank" rel="noopener noreffer ">https://medium.com/@phdmeyildiz/mindset-shifts-for-engineers-to-achieve-higher-software-quality-8ef8ee00a041&lt;/a>&lt;/p>
&lt;p>作为一个刚从大学毕业的初级工程师，没有什么是比掌握越来越多的工具、编程语言等更重要的事情了。当你刚进入工程领域时，你想捣鼓点新东西，然后你想看这些玩意可以正常工作。这是职业生涯早期最大的动力来源。我也是这样做的，并把大部分时间花在这上面。我一直不太理解我的前辈们，他们更关注工作方式，而不是下一个很酷的技术。现在，经过 12 年的时间，我不仅理解了他们，而且在这里，我写下了我的第一篇文章，讲述了一个简单的思维方式的转变对你的项目的影响会比几十种编程语言大。&lt;/p>
&lt;p>这篇文章是关于转变我们作为软件工程师的心态，在交付产品时获得高的视角，最终让客户的满意提升。下面的思维转变说明没有详细解释，只是用简短的句子写出来，我们可以做更深层次的思考。&lt;/p>
&lt;p>让我们来谈谈我们需要改变的思维模式。👍 表示目标思维模式，👎 表示我们需要远离的思路。&lt;/p>
&lt;h2 id="哪种工作方式">哪种工作方式？&lt;/h2>
&lt;h3 id="持续交付-">持续交付 👍&lt;/h3>
&lt;ul>
&lt;li>软件总是处于可发布的状态。&lt;/li>
&lt;li>我们可以随时进行发布。&lt;/li>
&lt;li>QA 尽早介入开发流程。&lt;/li>
&lt;li>尽早测试以避免 bug 的产生，并分析产品的质量。&lt;/li>
&lt;/ul>
&lt;h3 id="瀑布式的敏捷-">瀑布式的敏捷 👎&lt;/h3>
&lt;ul>
&lt;li>软件需要经过完善流程才能到可发布的状态。&lt;/li>
&lt;li>我们需要等到发布那的那个星期才能来进行发布。&lt;/li>
&lt;li>QA 等到发布之前才能检查软件质量。&lt;/li>
&lt;li>测试置后旨在发现已经开发的功能中的错误。&lt;/li>
&lt;/ul>
&lt;h2 id="什么时候测试">什么时候测试？&lt;/h2>
&lt;h3 id="从一开始就进行测试-">从一开始就进行测试 👍&lt;/h3>
&lt;ul>
&lt;li>从一开始就进行测试，以增加最终结果的可预测性。&lt;/li>
&lt;li>在开发过程中做探索性测试。&lt;/li>
&lt;li>开发和测试同时结束。&lt;/li>
&lt;/ul>
&lt;h3 id="最后测试-">最后测试 👎&lt;/h3>
&lt;ul>
&lt;li>以部署前的例行测试作为开发流程的结束。&lt;/li>
&lt;li>在开发过程中可能已经引入了错误，我们的作用是找到它们。&lt;/li>
&lt;li>首先开发，最后测试。&lt;/li>
&lt;/ul>
&lt;h2 id="谁为质量负责">谁为质量负责？&lt;/h2>
&lt;h3 id="每个人都要对质量负责-">每个人都要对质量负责 👍&lt;/h3>
&lt;ul>
&lt;li>把质量放在前面和中心，而不是放在 pipeline 的末端。整个团队都要关注质量。&lt;/li>
&lt;li>bug 的检测是在开发过程中同步进行的，不存在 QA 资源是瓶颈的说法!&lt;/li>
&lt;li>更少的资源可以做得更多。&lt;/li>
&lt;/ul>
&lt;h3 id="只有-qa-团队对质量负责-">只有 QA 团队对质量负责 👎&lt;/h3>
&lt;ul>
&lt;li>把质量作为最后一道防线。&lt;/li>
&lt;li>QA 成为瓶颈的可能性很大。&lt;/li>
&lt;li>需要越来越多的资源来战胜质量问题。&lt;/li>
&lt;/ul>
&lt;h2 id="何时交付">何时交付？&lt;/h2>
&lt;h3 id="更快更小的交付-">更快、更小的交付 👍&lt;/h3>
&lt;ul>
&lt;li>尽量高频率的交付少量的代码&lt;/li>
&lt;li>高质量来自于小的、快速的、明确的工作步骤，我们可以在创造变化的过程中进行监控。&lt;/li>
&lt;/ul>
&lt;h3 id="岁月静好我憋大招-">岁月静好，我憋大招 👎&lt;/h3>
&lt;ul>
&lt;li>一次性交付大量的代码。&lt;/li>
&lt;li>当软件很复杂的时候，要验证其质量是比较困难的。&lt;/li>
&lt;/ul>
&lt;h2 id="质量保证qa还是质量控制">质量保证（QA）还是质量控制？&lt;/h2>
&lt;h3 id="qa-高于质量控制-">QA 高于质量控制 👍&lt;/h3>
&lt;ul>
&lt;li>主要关注点是高效的工作方式和建立团队间的和谐。&lt;/li>
&lt;li>在从事任何工作时，都要考虑到质量问题。&lt;/li>
&lt;li>不断的反馈机制来改善 SDLC（软件开发生命周期）的 pipeline。&lt;/li>
&lt;/ul>
&lt;h3 id="只有质量控制-">只有质量控制 👎&lt;/h3>
&lt;ul>
&lt;li>主要关注的是检查最终结果。&lt;/li>
&lt;li>当出现问题时，质量成为关注点。&lt;/li>
&lt;li>在最后的测试中收到反馈。&lt;/li>
&lt;/ul>
&lt;h2 id="什么是-qa">什么是 QA？&lt;/h2>
&lt;h3 id="qa-是指质量协助-q">QA 是指质量协助 👍Q&lt;/h3>
&lt;ul>
&lt;li>QA 团队从开发的开始到结束都处于顾问的位置。&lt;/li>
&lt;li>重点是放在质量上，而不是寻找错误。&lt;/li>
&lt;li>错误更容易修复，也更便宜。&lt;/li>
&lt;li>QA 不应该发现 bug。当发现一个 bug 时，它不仅应该被修复，而且应该调查其根本原因。&lt;/li>
&lt;li>错误意味着 SDLC 过程中存在问题，这个过程需要被修正。&lt;/li>
&lt;/ul>
&lt;h3 id="qa-意味着发布守门员-">QA 意味着发布守门员 👎&lt;/h3>
&lt;ul>
&lt;li>QA 团队是一个测试者和 bug 寻找者。&lt;/li>
&lt;li>没有改善质量的空间。&lt;/li>
&lt;li>修复问题的成本和风险都比较大。&lt;/li>
&lt;li>QA 应该找到 bug，然后 bug 应该被修复而不需要进一步挖掘其根本原因。&lt;/li>
&lt;li>错误只是一个软件问题。&lt;/li>
&lt;/ul>
&lt;h2 id="观点">观点&lt;/h2>
&lt;p>你对上面的一些思路转变有什么样的看法呢？&lt;/p></description></item><item><title>web自动化测试的智能革命:AI如何推动软件质量保证的未来</title><link>https://itest2025.pages.dev/ai-for-web-automation-test_46/</link><pubDate>Fri, 08 Mar 2024 09:02:42 +0800</pubDate><guid>https://itest2025.pages.dev/ai-for-web-automation-test_46/</guid><description>&lt;p>首先这个标题不是我取的，是我喂了关键字让 AI 给取的，果然非常的标题党，让人印象深刻。&lt;/p>
&lt;p>先简单回顾一下 web 自动化测试的一些发展阶段&lt;/p>
&lt;h3 id="qtp-时代">QTP 时代&lt;/h3>
&lt;p>很多年前 QTP 横空出世的时候，没有人会怀疑这种工具将是自动化测试的未来形态。使用者理论上不需要掌握任何编程语言就可以通过录制回放的方式实现 web 端的自动化测试。后来事实证明 QTP 向人们证明了自动化测试的可行性，然而其自身的一些限制让其渐渐被主流市场所抛弃。看看 bing 是怎么说的&lt;/p>
&lt;p>您好，这是 Bing。QTP 是 QuickTest Professional 的简称，是一种自动化软件测试工具，主要用于功能测试和回归测试 。QTP 的优点有：&lt;/p>
&lt;ul>
&lt;li>支持多种编程语言和平台&lt;/li>
&lt;li>基于关键字驱动的测试方法，易于编写和维护测试脚本&lt;/li>
&lt;li>提供丰富的测试对象库和检查点，方便识别和验证被测系统的元素&lt;/li>
&lt;li>可以与其他 HP 测试工具集成，实现全面的测试管理&lt;/li>
&lt;/ul>
&lt;p>QTP 的缺点有：&lt;/p>
&lt;ul>
&lt;li>商业软件，价格昂贵&lt;/li>
&lt;li>对于一些复杂的界面或控件，可能无法正确识别或操作&lt;/li>
&lt;li>对于一些新技术或框架，可能需要安装额外的插件或补丁&lt;/li>
&lt;li>对计算机资源的消耗较大，可能影响被测系统的性能&lt;/li>
&lt;/ul>
&lt;p>请注意，QTP 在官网已被改名为 UFT（Unified Functional Testing）统一功能测试 。&lt;/p>
&lt;h3 id="selenium-rc-和-waitir-时代">selenium RC 和 waitir 时代&lt;/h3>
&lt;p>后来的一段时间 selenium 和 watir 渐渐流行起来，这一次一切似乎是发生了倒退，相较于 qtp 提供的足够简单的录制回放模式，selenium rc 和 watir 只能自己写代码去实现自动化里的每一步操作和断言，炫酷的未来似乎渐行渐远。看看 chatgpt 对 selenium rc 的评价，先声明这些评价内容不够准确，大家仅作为参考。&lt;/p>
&lt;p>Selenium RC（Remote Control）是一个自动化测试工具，以下是 Selenium RC 的优缺点：&lt;/p>
&lt;p>优点：&lt;/p></description></item><item><title>线上发现了bug该如何处理</title><link>https://itest2025.pages.dev/how-to-deal-with-live-issues_45/</link><pubDate>Fri, 08 Mar 2024 09:02:40 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-deal-with-live-issues_45/</guid><description>&lt;p>今天在国外论坛看到了个很有意思的发帖，有人提问：线上发现了 bug 该如何处理。&lt;/p>
&lt;blockquote>
&lt;p>我知道大家已经问过很多次类似的问题了，不过工作还是很让我失望。我在生产环境上漏掉了 1 个很明显的 bug 没测出来，我想知道你们是怎么处理这种情况的。我的项目经理发现了这个 bug。&lt;/p>&lt;/blockquote>
&lt;p>大家的回答其实很暖心的。下面是一些高赞回答。&lt;/p>
&lt;blockquote>
&lt;p>这就意味着你们在单元测试，集成测试和功能测试以及自动化测试的阶段就已经漏掉了这个问题，希望你们有做这些。其实开发跟你们的感受一下，我就是个开发，我一直为我写的 bug 感到抱歉，我认为责任应该由整个 team 承担。&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>事情都发生了，先承认错误。确定是否真的有测试遗漏，并在未来的发布中加入这个用例。&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>好的 devops team 需要有零责备文化（&lt;a href="https://www.releaseteam.com/the-importance-of-a-zero-blame-culture-in-devops/" target="_blank" rel="noopener noreffer ">https://www.releaseteam.com/the-importance-of-a-zero-blame-culture-in-devops/&lt;/a>），好的软件质量交付需要团队的努力。&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>这就是 QA 当下生存现状。如果你能忍受压力持续进步，那么你会成为一个好的 QA 的。&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>你并没有搞砸，是流程的锅。扔给你点资料去学习吧&lt;a href="https://www.amazon.com/Antifragile-Things-That-Disorder-Incerto/dp/0812979680" target="_blank" rel="noopener noreffer ">https://www.amazon.com/Antifragile-Things-That-Disorder-Incerto/dp/0812979680&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;p>总的看来大家的观点还是开明的，比较包容，不过事实上大家的看法是一回事，但最后处理问题的方式又是另外一回事了。假如发生了一个很严重的线上事故，还好有别人背锅，你是安全的，这时候你心里肯定是同情和理解，觉得 qa 其实有点无辜，是流程的锅，觉得需要整个团队努力一下以便改进交付质量，但是如果你是直接或相关责任人，处罚落到了你头上，你大概率就不会这么宽容的看问题了。&lt;/p>
&lt;p>我的观点是发现线上问题不可怕，能不能迅速发现问题和迅速修复问题才是关键，而这两点光靠 qa 负重前行是无法得到根本上的改善的。&lt;/p></description></item><item><title>不要害怕在周五部署</title><link>https://itest2025.pages.dev/feel-free-to-deploy-on-friday_44/</link><pubDate>Fri, 08 Mar 2024 09:02:39 +0800</pubDate><guid>https://itest2025.pages.dev/feel-free-to-deploy-on-friday_44/</guid><description>&lt;p>一般情况下我们都会避免在周五进行发布，毕竟大家都不喜欢&lt;/p>
&lt;ul>
&lt;li>周五留下来加班发布，周五还是早点回去比较好&lt;/li>
&lt;li>万一发出问题来了周六周日还要想办法去修复，休息的时间无形中被占用了&lt;/li>
&lt;/ul>
&lt;p>不过最近看到一篇文章观点很有意思，作者认为只要做的够好，在周五发布其实也不是什么大不了的事情。原文地址在这里，&lt;a href="https://dev-tester.com/feel-free-to-deploy-on-friday/?utm_campaign=Software%2BTesting%2BWeekly&amp;amp;utm_medium=email&amp;amp;utm_source=Software_Testing_Weekly_147" target="_blank" rel="noopener noreffer ">https://dev-tester.com/feel-free-to-deploy-on-friday/?utm_campaign=Software%2BTesting%2BWeekly&amp;amp;utm_medium=email&amp;amp;utm_source=Software_Testing_Weekly_147&lt;/a>。 我稍微翻译了一下大意，也许它山之石可以攻玉。&lt;/p>
&lt;p>省略前面若干字。&lt;/p>
&lt;p>然而，我坚信，如果你想创建一个高质量的应用程序，大多数开发和测试团队的 &amp;ldquo;看在上帝的份上，不要在星期五部署 &amp;ldquo;的态度是不可取的。&lt;/p>
&lt;h3 id="为什么避免周五部署是一种不健康的质量控制习惯">为什么避免周五部署是一种不健康的质量控制习惯？&lt;/h3>
&lt;p>当一个团队像躲避瘟疫一样躲避周五部署时，通常可以认为他们对自己的的产品、流程或两者缺乏信心。这种不情愿通常源于组织需要知道或信任他们的应用程序已经准备好让客户使用。任何时候有人说 &amp;ldquo;我们不会在周五部署&amp;rdquo;，他们真正说的是 &amp;ldquo;我们不相信我们的应用程序或流程会在周五部署&amp;rdquo;。&lt;/p>
&lt;p>从长远来看，这种想法并不能带来高质量的系统。团队并不把这些不确定性的作为一个表明他们需要专注于改进的信号。无论缺乏信任和信心是一个现实的问题还是一个想象中的问题，大多数团队都采取简单的方法来掩盖问题，而不是花时间去寻找改进问题的方法。他们想出了一些规则，比如周五不进行部署。&lt;/p>
&lt;p>与其避免在周末进行部署，不如正面解决这些问题，加强现有的流程，让团队在任何需要的时候都能自由地进行部署。 作为一个开发人员或测试人员，如果不用担心因为在下班前要更新你的应用程序而使你的周末被打断，你会有什么感觉？&lt;/p>
&lt;p>任何认真推动整个团队和产品的质量的组织必须在他们的公司中建立这种信任。虽然对你的系统完全有信心需要时间，但好消息是任何人都可以做到这点，无论他们认为他们的应用程序和流程在他们的组织中是多么的混乱不堪。下面的三步计划可以让你比你想象中的更快达到目的。&lt;/p>
&lt;h3 id="第一步实现一个稳定的自动化测试集">第一步：实现一个稳定的自动化测试集&lt;/h3>
&lt;p>自动化测试用例集合是任何想要快速构建和发布而不需要经常担心他们的应用程序的软件开发团队的必备工具。如果没有自动化测试套件，你就会严重削弱你的团队工作和部署的能力。如果你还没有给自己或你的团队一个空间来增加自动化测试覆盖率，你就会让自己处于不利的位置。&lt;/p>
&lt;p>提升质量的第一步是用自动化测试去覆盖所有的关键路径，所以如果你是从头开始，请关注这一步。然而，比覆盖率更重要的东西是稳定性。即使有很高的覆盖率，如果你的测试自动化不断失败，也不会有什么帮助。如果你的测试用例很不稳定，你在任何时候都不会感到舒服，更不用说在星期五部署了。在测试覆盖率方面的工作，重点是建立一个强大的、可维护的测试框架，以保持你的团队长期的高信心。&lt;/p>
&lt;p>另一个重点领域是你应该做什么类型的测试。根据你的应用程序的需求，你可能需要各种自动化测试的混合。有些团队写了几个单元测试就结束了，但今天的现代应用程序有很多变化的地方，你需要检查 E2E、验证性能、确保安全性和可访问性等这些都只是冰山一角，提前计划哪些类型的自动化测试可以使你的组织受益。&lt;/p>
&lt;p>知道你需要什么类型的测试和你想覆盖的领域。拥有一个稳定的自动化测试用例集和正确的测试组合将加强你的团队对你的应用程序和部署的信心。&lt;/p>
&lt;h3 id="第二步不要忘记手动探索性测试">第二步：不要忘记手动探索性测试&lt;/h3>
&lt;p>当团队养成了为他们的应用程序建立稳定的自动化测试的习惯，他们可能会对他们的测试套件在部署前捕捉问题的能力过于自信。通常，一种错误的安全感导致组织内的人认为他们不需要用测试自动化实践来执行额外的测试活动。这种想法与事实相差甚远。不管你的测试自动化习惯有多好，或者你的测试覆盖率有多高，你都不能忽视手动、探索性测试的重要性。&lt;/p>
&lt;p>自动化测试只能做他们被告知要做的事情，在快速开发周期中很好进行功能回归。另一方面，手动和探索性测试将允许你观察自动测试的盲点，并在没有人想到的地方发现问题&amp;ndash;所谓的 &amp;ldquo;未知的未知&amp;rdquo;。忽视测试的这一重要部分而依赖自动化，不可避免地会导致产品质量下降。&lt;/p>
&lt;p>人工测试需要时间和精力来完成。一些团队，特别是没有专门的 QA 团队的小团队，往往在最后一刻才进行这些测试。与我合作的大多数初创企业只在重大部署前几天甚至几小时做探索性测试。有时这很有效，但你有可能匆忙完成这个过程，让 bug 遗漏到了线上。&lt;/p>
&lt;p>理想情况下，手动探索性测试应该在整个团队的开发周期中持续进行并伴随着新的构建在 staging 环境上进行部署。让你的组织有时间做这种测试，你会发现自己在任何时候都不会担心部署的问题。&lt;/p>
&lt;h3 id="第-3-步建立一个自动化的构建和部署流程">第 3 步：建立一个自动化的构建和部署流程&lt;/h3>
&lt;p>在构建软件的组织中，开发和发布周期的一个部分经常被遗忘，那就是构建和部署过程。在我工作过的一些地方，构建和部署一个新的版本几乎感觉像是一个神秘的过程，只有少数被选中的人才能完成。他们的部署通常由一个漫长的步骤组成，按照精确的顺序和几乎完美的时机进行。如果执行这种仪式性行为的人在途中寄掉，这个过程的脆弱性可能会在瞬间使整个应用程序崩溃。&lt;/p>
&lt;p>我描述这个过程的方式可能略显夸张，但却非常接近事实。我合作过的许多公司都有不必要的复杂部署程序。当被问及原因时，一些团队试图证明为什么需要这样做，但他们回答背后的潜台词其实是 &amp;ldquo;我们一直是这样做的，所以我们从未改变过&amp;rdquo;。常见的原因是，很久以前有人创造了这个复杂的过程。既然它是有效的&amp;ndash;不管这个过程变得多么微妙或曲折&amp;ndash;他们从来没有费心去改善现状。&lt;/p>
&lt;p>如果你的团队必须执行多个步骤来构建和部署你的应用程序的新版本，那么你就对你的组织造成了巨大的伤害。无论你的整个系统包含多少模块，你都可以将几乎所有的发布过程自动化&amp;ndash;甚至将其浓缩为一个命令。如今，我们有大量优秀的工具，从 Jenkins 到 AWS CodePipeline 到 CircleCI 和无数其他工具，使构建和部署过程自动化变得非常简单。你的组织没有任何借口可以避免自动化部署。&lt;/p>
&lt;p>部署过程中一个同样重要但被遗忘的部分是回滚失败的部署。尽管有一个经过良好测试的自动化过程，它仍然可能由于许多不同的原因而失败，从糟糕的代码被合并到你的应用程序的基础设施的故障。大多数团队发现他们没有任何回滚策略，当部署失败时，他们的应用程序就会停机。在这种情况发生之前，制定一个适合你的情况的回滚策略，并经常测试，以确保当墨菲定律发生时，你不会感到惊讶。&lt;/p>
&lt;h3 id="总结">总结&lt;/h3>
&lt;p>这篇文章的目的不是让你的团队每周五进行部署，而是让他们在需要时可以毫无顾虑地进行部署。自信部署而不用担心某些幺蛾子会发生的能力，会让每个开发人员和测试人员在他们的项目周期中不用担心太多。如果我们幸运的话，也许我们会看到更少的 memes 在我们的 LinkedIn feeds 上乱窜。&lt;/p>
&lt;p>总之作者的观点是如果你够强，什么时候部署都没问题。然而时间总是自己的，为了不必要的节外生枝，还是建议大家在周五下班前或者下午不要进行部署，多一事不如少一事，不在周五部署不是真理，而是生活。&lt;/p></description></item><item><title>假如我被裁员了</title><link>https://itest2025.pages.dev/if-i-was-laid-off_43/</link><pubDate>Fri, 08 Mar 2024 09:02:37 +0800</pubDate><guid>https://itest2025.pages.dev/if-i-was-laid-off_43/</guid><description>&lt;p>最近在一些微信群里看到某互联网大厂裁员的消息，跟老同事确认了一下，他们部门的指标是 20%，5 个人里就要走 1 个，冰冷的数字背后是一个个鲜活的身影，一段段故事以及一声声的叹息和一阵阵的无奈，作为从业者，也不免有鸟尽弓藏兔死狐悲之感。&lt;/p>
&lt;p>作为一个 35 岁以上，可能会被大部分用人企业所婉拒的老互联网人，老测试人，我今天一直在思考，假如我被裁员了那应该怎么办？&lt;/p>
&lt;h3 id="还完房贷">还完房贷&lt;/h3>
&lt;p>我想第一步可能是想尽办法还清房贷。这可能有点反直觉，丢了工作难道不应该先开源节流吗？房贷等与是长期定投，而且相对来说利率是较低的，失业之后把现金流丢到房贷里去岂不是没有任何的抗风险的空间了吗？其实我们可以反过来想，如果现在手上有些资金不去还房贷，那么很可能会去想办法创业或换个赛道做些自己不擅长的事情，这样一来如果投资或者创业失败，就目前的经济情况来看市场普遍缺乏信心，投资获益的可能性是比较低的，假如一不小心蚀了本金，那么房贷就难以为继，最后断供法拍，征信受损，大家可以了解一下失信的惨重代价。所以还清房贷，降低未来几十年的利息支出反而是更理智的选择。另外没有了房贷，每个月的生活成本其实是要低不少的，这样生存的压力也会相应降低，一石二鸟，何乐而不为。&lt;/p>
&lt;h3 id="节流节流">节流节流&lt;/h3>
&lt;p>今年居民负债率没有提升，可能是因为大家手上真的没钱了吧。没有富余的资金，杠杆也撬不动空气。如果在这个节骨眼上没有了收入，那么我会选择尽量的降低生活成本，先活下去熬过这段时间，如果整个社会对经济都没有期待，那么经济的复苏反而指日可待。节流的方式有很多种，比如每天老老实实买菜在家里做饭，尽量少出行，不仅省钱还能预防新冠，卖掉一切不需要的东西，比如汽车，这样可以省油费，停车费和保险费用；停掉一切可能产生账单的服务，比如注销掉之前因工作需要而办理的手机号，停掉有线电视等等。这样算下来一天 100 块的话一家三口就可以生活下去，一年只需要 3.6 万，加上其他的一些成本和支出，一年最低的生活成本大概在 5 万，如果稳健理财每年的年化收益是 4%的话，那么有 125 万的本金就可以完全靠利息苟延残喘下去，寒冬总会过去，现在步入黑暗，但我仍然心向光明。&lt;/p>
&lt;h3 id="坚持交社保和保险">坚持交社保和保险&lt;/h3>
&lt;p>社保是一种对未来的投资，坚持交其实就是对未来不确定性的对冲，不过现在社保和商业保险的成本还是比较高的，应该是苟活阶段家庭年度支出的大头。&lt;/p>
&lt;h3 id="锻炼身体">锻炼身体&lt;/h3>
&lt;p>前几天看到一个数据，大约在 2050 年，全球 65 岁以上人口的数量将是 5 岁以下人口数量的两倍。所以等我们这代人老去的时候，将是全球步入人口老龄化的时候。掐指一算，还有 28 年，按照中国人口结构和医疗水平推算，这个时间点可能会来的更早一些。最近一年见过一些生老病死，经历了一些世事无常，强烈的感觉到等我们步入老年之后&lt;/p>
&lt;ul>
&lt;li>身体的健康和神智的清晰是最大的福报&lt;/li>
&lt;li>社会上应该有不少岗位会向高龄人群开放&lt;/li>
&lt;/ul>
&lt;p>假如未来真是这样的话，为什么不在社会对 35 岁左右群体横眉冷对的时候蛰伏一段时间，锻炼好身体，不让年轻时候的工作透支年老时的健康，积极学习，跟上时代的脚步，过些年之后，当最后一波人口生育高峰的红利过去，也许届时会有更好的复出和发展的机会。总之健康生活的越久，在未来的社会可能会越有竞争力。&lt;/p>
&lt;h3 id="开源开源">开源开源&lt;/h3>
&lt;p>做一些低成本的小生意，能赚就赚到，赚不到就当交了学费，当成是提升自我的必要投资。如果恩格尔系数过高，那么想办法种点菜补贴家用，灵活就业，赚不了大钱但心态好些就当是体验了生活。&lt;/p>
&lt;h3 id="读万卷书或行万里路">读万卷书或行万里路&lt;/h3>
&lt;p>合格勤勉的打工人是没有太多时间读书充实自己的。没有工作了可以多读书，一来可以了解这个世界是如何运转的，二来可以通过不同人群的视角去展望未来，也许以后的机会就在我们对未来的憧憬中慢慢萌芽。低成本的行万里路真正让我们认识到这个世界，尽管我们是农耕民族，但迁移的基因也许从我们的祖先由非洲出走时就扎根在了我们的基因里，我们大部分人还是希望在有生之年可以亲眼看一看更大的世界。另外时不我待，前几天我去爬了座山，强烈的感受到如果年纪再稍微大一些，那么这项运动可能会跟我割席断交，失之交臂，当时就感叹，有些事情应该趁年轻去做。工可以留着年纪大了再打，毕竟从趋势上看，未来是属于老年人的，到时候孩子也长大了，每天睡的时间也少了，吃的也不多了，心态也更加平和了，模范的打工人就这么完美的炼成了。读书的经济成本不高，不过难在坚持，旅行与节流冲突，需要权衡和取舍。&lt;/p>
&lt;h3 id="培养一门爱好">培养一门爱好&lt;/h3>
&lt;p>有一门爱好可以全身心投入的话，那么人可能就不会那么焦虑吧。不过爱好除了投入时间之外还需要持续投入金钱，在经济压力比较大的情况下，一些烧钱的爱好可能就不太方便参与了。&lt;/p>
&lt;h3 id="多陪陪家人">多陪陪家人&lt;/h3>
&lt;p>打工的时候春节假期往往令人矛盾，想回老家跟家人团聚，奈何时间紧票难买；想多请几天假，奈何卷王当道，难以启齿。没有工作了终于可以多陪陪家人了，亲情也是一种治愈，能让人不那么焦虑。&lt;/p>
&lt;h3 id="总之">总之&lt;/h3>
&lt;p>如果在这个时间节点我没有了工作，在有选择的情况下，我会跟家里人沟通，告诉他们我想先退一步海阔天空，调整好身体状态和心态，尽可能长的苟延残喘一段时间，然后充实自己，等待合适的机会复出；如果没有选择的话，我会默默的找出之前的简历，然后在开头加上一段话：“xx 年工作经验，xx 年管理经验，沟通能力强，有很强的抗压能力，能够适应高强度的工作”，加粗保存，发给之前有联系的一众猎头。最后盘算着离职补偿可以撑多久，从撑不住的那天倒推，给自己的求职行为立项，定目标，编 okr，最后跟家人对齐 deadline。好一似食尽鸟投林，落了片白茫茫大地真干净！&lt;/p></description></item><item><title>接口测试进阶：在接口测试中框架中使用json schema</title><link>https://itest2025.pages.dev/json-schema-for-api-test_42/</link><pubDate>Fri, 08 Mar 2024 09:02:36 +0800</pubDate><guid>https://itest2025.pages.dev/json-schema-for-api-test_42/</guid><description>&lt;p>当今接口测试越来越重要，一般情况下我们总是会对接口的返回的 json 字符串进行验证，看返回是否跟我们的预期相符。不过很多情况下我们会遇到下面的问题&lt;/p>
&lt;ul>
&lt;li>响应结果在测试中不停的发生变动，比如昨天还是 3 个字段，今天可能返回值里只有 2 个字段了，测试这边没有比较好的方式感受到后端的变化&lt;/li>
&lt;li>我们需要对 json 的返回值进行一些校验，需要写很多的断言，大部分时候这些断言都是相似的，或者是重复的，比如说校验某个字段的长度必须小于 10 之类的&lt;/li>
&lt;/ul>
&lt;p>那如何解决呢？&lt;/p>
&lt;ul>
&lt;li>与前后端沟通好返回值的字段，类型以及校验规则，最好有前后端+测试端统一一份合约，大家都按照合约来进行数据的处理&lt;/li>
&lt;li>测试的时候通过合约里定义好的校验规则进行数据校验&lt;/li>
&lt;/ul>
&lt;p>这时候 json schema 就派上用场了。&lt;/p>
&lt;h3 id="json-schema">json schema&lt;/h3>
&lt;p>JSON Schema 是一种 JSON 媒体类型，用于定义 JSON 数据的结构。 JSON 模式旨在定义 JSON 数据的验证，可用于验证响应和请求 JSON。 在 JSON Schema 中，我们可以验证数据类型、字段是否为必填、最小长度或最大长度等。&lt;/p>
&lt;h3 id="举例">举例&lt;/h3>
&lt;p>下面的数据代表了一个员工的信息&lt;/p>
&lt;ul>
&lt;li>id: &lt;code>employeeId&lt;/code>&lt;/li>
&lt;li>员工名称: &lt;code>employeeName&lt;/code>&lt;/li>
&lt;li>年龄: &lt;code>employeeAge&lt;/code>&lt;/li>
&lt;li>职称: &lt;code>jobTitle&lt;/code>&lt;/li>
&lt;li>爱好: &lt;code>hobby&lt;/code>&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;employeeId&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;employeeName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Fulan&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;employeeAge&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">23&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;jobTitle&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;SDET&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;hobby&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;watch movies&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;play football&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>上面的定义其实是有一些疑问的，比如&lt;/p>
&lt;ul>
&lt;li>id 是什么意思&lt;/li>
&lt;li>employeeName 的最大长度是多少&lt;/li>
&lt;li>employeeAge 的最小值是什么&lt;/li>
&lt;li>jobTitle 是必填吗&lt;/li>
&lt;li>hobby 可以填几个&lt;/li>
&lt;/ul>
&lt;p>我们可以通过生成 JSON schema 来回答上面的问题&lt;/p></description></item><item><title>如何编写测试计划</title><link>https://itest2025.pages.dev/test-plan-sample_41/</link><pubDate>Fri, 08 Mar 2024 09:02:34 +0800</pubDate><guid>https://itest2025.pages.dev/test-plan-sample_41/</guid><description>&lt;p>测试计划在国内其实不是很流行。之前在外企工作的时候，每一次的测试工作基本上都是以编写测试计划开始的。好的测试计划可以让团队成员对测试整体进行和测试策略以及方法有一个大体的认识，在一定程度上可以节约沟通成本。最近正好在 github 上看到一份测试计划文档，我们就一起来学习一下其中的精华吧。项目地址：&lt;a href="https://github.com/fityanos/awesome-quality-assurance-roadmap#test-plan-sample" target="_blank" rel="noopener noreffer ">https://github.com/fityanos/awesome-quality-assurance-roadmap#test-plan-sample&lt;/a>&lt;/p>
&lt;h2 id="1-介绍">1 介绍&lt;/h2>
&lt;h3 id="11-项目介绍">1.1 项目介绍&lt;/h3>
&lt;p>介绍项目上下文&lt;/p>
&lt;h3 id="12-读者">1.2 读者&lt;/h3>
&lt;p>描述一下读者人群，比如来自 A 团队的 alice 以及来自 B 团队的 bob&lt;/p>
&lt;h2 id="2-测试策略">2 测试策略&lt;/h2>
&lt;h3 id="21-测试目标">2.1 测试目标&lt;/h3>
&lt;p>比如完整的回归测试，还是增量的功能验证等；&lt;/p>
&lt;h3 id="22-测试假定">2.2 测试假定&lt;/h3>
&lt;p>假设某些东西不需要考虑或者满足某些条件，比如测试环境上可能没有配置负载均衡，这时就可以假定大家都理解这个点，并可以进行适当的忽略&lt;/p>
&lt;h3 id="23-测试原则">2.3 测试原则&lt;/h3>
&lt;p>比如所有 bug 必须 fix 之类的原则性的东西&lt;/p>
&lt;h3 id="24-测试范围和级别">2.4 测试范围和级别&lt;/h3>
&lt;p>这里可以定义测试的范围，该做的就做，不该做的就先约定清楚。比如可以定义&lt;/p>
&lt;ul>
&lt;li>功能测试范围和时间以及交付物&lt;/li>
&lt;li>性能测试范围和时间以及交付物&lt;/li>
&lt;li>回归测试范围和时间以及交付物&lt;/li>
&lt;li>UAT 测试的范围和时间以及交付物&lt;/li>
&lt;/ul>
&lt;h3 id="25-loelevel-of-effort">2.5 LOE(level of effort)&lt;/h3>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="%E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E6%B5%8B%E8%AF%95%E8%AE%A1%E5%88%92%20fb24f300af014335945a795002909ca8/Untitled.png"
 data-srcset="%E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E6%B5%8B%E8%AF%95%E8%AE%A1%E5%88%92%20fb24f300af014335945a795002909ca8/Untitled.png, %E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E6%B5%8B%E8%AF%95%E8%AE%A1%E5%88%92%20fb24f300af014335945a795002909ca8/Untitled.png 1.5x, %E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E6%B5%8B%E8%AF%95%E8%AE%A1%E5%88%92%20fb24f300af014335945a795002909ca8/Untitled.png 2x"
 data-sizes="auto"
 alt="%E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E6%B5%8B%E8%AF%95%E8%AE%A1%E5%88%92%20fb24f300af014335945a795002909ca8/Untitled.png"
 title="%E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E6%B5%8B%E8%AF%95%E8%AE%A1%E5%88%92%20fb24f300af014335945a795002909ca8/Untitled.png" />&lt;/p>
&lt;p>这里就是工作量的分解了，越清晰越好。&lt;/p>
&lt;h2 id="3-执行策略">3 执行策略&lt;/h2>
&lt;h3 id="31-开始和退出条件">3.1 开始和退出条件&lt;/h3>
&lt;p>比如功能测试的开始条件是产品体验通过和开发自测通过，结束的条件是所有的 bug 都被 fix&lt;/p>
&lt;h3 id="32-测试轮次">3.2 测试轮次&lt;/h3>
&lt;ul>
&lt;li>第一轮&lt;/li>
&lt;li>第二轮&lt;/li>
&lt;li>&amp;hellip;&amp;hellip;&lt;/li>
&lt;/ul>
&lt;p>对于敏捷测试团队来说，其实可以忽略测试轮次的概念。对于瀑布流开发团队来说则需要定义清楚，因为每一轮的时间都很宝贵。&lt;/p></description></item><item><title>先收藏 测试同学的成长之道—from github</title><link>https://itest2025.pages.dev/roadmap-for-testers_40/</link><pubDate>Fri, 08 Mar 2024 09:02:33 +0800</pubDate><guid>https://itest2025.pages.dev/roadmap-for-testers_40/</guid><description>&lt;p>今天在 github 上看到一份测试同学的成长之路图谱，不敢独享，在这里给大家汇报一下。&lt;/p>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="%E5%85%88%E6%94%B6%E8%97%8F%20%E6%B5%8B%E8%AF%95%E5%90%8C%E5%AD%A6%E7%9A%84%E6%88%90%E9%95%BF%E4%B9%8B%E9%81%93%E2%80%94from%20github%2047ddeb01df294765bd908d165758d602/roadmap.png"
 data-srcset="%E5%85%88%E6%94%B6%E8%97%8F%20%E6%B5%8B%E8%AF%95%E5%90%8C%E5%AD%A6%E7%9A%84%E6%88%90%E9%95%BF%E4%B9%8B%E9%81%93%E2%80%94from%20github%2047ddeb01df294765bd908d165758d602/roadmap.png, %E5%85%88%E6%94%B6%E8%97%8F%20%E6%B5%8B%E8%AF%95%E5%90%8C%E5%AD%A6%E7%9A%84%E6%88%90%E9%95%BF%E4%B9%8B%E9%81%93%E2%80%94from%20github%2047ddeb01df294765bd908d165758d602/roadmap.png 1.5x, %E5%85%88%E6%94%B6%E8%97%8F%20%E6%B5%8B%E8%AF%95%E5%90%8C%E5%AD%A6%E7%9A%84%E6%88%90%E9%95%BF%E4%B9%8B%E9%81%93%E2%80%94from%20github%2047ddeb01df294765bd908d165758d602/roadmap.png 2x"
 data-sizes="auto"
 alt="%E5%85%88%E6%94%B6%E8%97%8F%20%E6%B5%8B%E8%AF%95%E5%90%8C%E5%AD%A6%E7%9A%84%E6%88%90%E9%95%BF%E4%B9%8B%E9%81%93%E2%80%94from%20github%2047ddeb01df294765bd908d165758d602/roadmap.png"
 title="%E5%85%88%E6%94%B6%E8%97%8F%20%E6%B5%8B%E8%AF%95%E5%90%8C%E5%AD%A6%E7%9A%84%E6%88%90%E9%95%BF%E4%B9%8B%E9%81%93%E2%80%94from%20github%2047ddeb01df294765bd908d165758d602/roadmap.png" />&lt;/p>
&lt;h2 id="基本概念">基本概念&lt;/h2>
&lt;p>首先明确几个基本概念&lt;/p>
&lt;ul>
&lt;li>STLT: Software Testing Life Cycle 软件测试生命周期&lt;/li>
&lt;li>SDLC:Software Develop Life Cycle 软件开发生命周期&lt;/li>
&lt;li>TDD: Test Driven Development 测试驱动开发&lt;/li>
&lt;li>RPA: Robotic Process Automation 过程自动化&lt;/li>
&lt;/ul>
&lt;h2 id="必须掌握的基础能力">必须掌握的基础能力&lt;/h2>
&lt;h3 id="测试策略">测试策略&lt;/h3>
&lt;ul>
&lt;li>白盒测试&lt;/li>
&lt;li>灰盒测试&lt;/li>
&lt;li>黑盒测试&lt;/li>
&lt;/ul>
&lt;h3 id="测试类型">测试类型&lt;/h3>
&lt;ul>
&lt;li>功能测试
&lt;ul>
&lt;li>单元测试&lt;/li>
&lt;li>冒烟测试&lt;/li>
&lt;li>集成测试&lt;/li>
&lt;li>回归测试&lt;/li>
&lt;li>健全性测试(sanity testing)&lt;/li>
&lt;li>用户接受性测试(uat)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>非功能性测试
&lt;ul>
&lt;li>负载测试&lt;/li>
&lt;li>压力测试&lt;/li>
&lt;li>安全测试&lt;/li>
&lt;li>性能测试&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>上面的负载，压力以及性能测试在国内其实不怎么分，可以统一看作是性能测试，只不过测试策略不同而已。&lt;/p>
&lt;h3 id="stlc-管理工具">STLC 管理工具&lt;/h3>
&lt;ul>
&lt;li>qTest&lt;/li>
&lt;li>TestLink（常用）&lt;/li>
&lt;li>Zephyr&lt;/li>
&lt;li>Testrail&lt;/li>
&lt;/ul>
&lt;h3 id="项目管理工具">项目管理工具&lt;/h3>
&lt;ul>
&lt;li>Jira(常用)&lt;/li>
&lt;li>Youtrack&lt;/li>
&lt;li>Trello&lt;/li>
&lt;li>Assembla&lt;/li>
&lt;/ul>
&lt;h3 id="软件研发模型">软件研发模型&lt;/h3>
&lt;ul>
&lt;li>V 模型&lt;/li>
&lt;li>敏捷模型：迭代式，较为常用&lt;/li>
&lt;li>瀑布模型&lt;/li>
&lt;/ul>
&lt;h2 id="深度手工测试技能">深度手工测试技能&lt;/h2>
&lt;p>手工测试目前看来还是软件测试工程师的核心能力，优秀的手工测试能力可以让你的职业生涯发展更加平滑。&lt;/p></description></item><item><title>为什么探索测试和敏捷项目八字不合</title><link>https://itest2025.pages.dev/exploratory-testing-why-it-not-ideal-agile-projects_39/</link><pubDate>Fri, 08 Mar 2024 09:02:31 +0800</pubDate><guid>https://itest2025.pages.dev/exploratory-testing-why-it-not-ideal-agile-projects_39/</guid><description>&lt;p>原文地址: &lt;a href="https://www.stickyminds.com/article/exploratory-testing-why-it-not-ideal-agile-projects" target="_blank" rel="noopener noreffer ">https://www.stickyminds.com/article/exploratory-testing-why-it-not-ideal-agile-projects&lt;/a>&lt;/p>
&lt;p>下面是 gpt4 的总结。&lt;/p>
&lt;p>探索性测试是一种灵活的测试方法，它不需要预先定义测试用例，而是让测试人员根据自己的判断和发现来设计和执行测试。探索性测试有以下优点：&lt;/p>
&lt;ul>
&lt;li>它可以适应需求不明确或变化频繁的场景，提供及时的反馈和建议。&lt;/li>
&lt;li>它可以发挥测试人员的创造力和直觉，发现一些预期之外的缺陷或风险。&lt;/li>
&lt;li>它可以节省测试文档的编写和维护的时间和成本，提高测试效率。&lt;/li>
&lt;/ul>
&lt;p>然而，在敏捷软件开发中，探索性测试并不是最佳选择。原因有以下几点：&lt;/p>
&lt;ul>
&lt;li>探索性测试缺乏可追溯性和可重复性，难以评估测试覆盖率和质量。它也不利于与其他团队成员或利益相关者分享测试结果和经验。&lt;/li>
&lt;li>探索性测试依赖于测试人员的经验和技能，可能导致测试结果不一致或遗漏重要的缺陷。它也不利于培养新手测试人员或提高团队的测试能力。&lt;/li>
&lt;li>探索性测试不利于自动化和持续集成，无法实现敏捷开发的快速反馈和交付。它也不利于与开发人员协作，实现测试驱动开发或行为驱动开发等敏捷实践。&lt;/li>
&lt;/ul>
&lt;p>因此，作者建议在敏捷项目中使用结构化的测试方法，结合需求分析、测试设计、测试执行和测试评估等步骤，提高测试效率和质量。&lt;/p>
&lt;p>同时，也可以在&lt;strong>适当的时机&lt;/strong>进行探索性测试，以补充结构化测试的不足。例如，在需求分析阶段，可以使用探索性测试来理解用户需求和业务流程；在回归测试阶段，可以使用探索性测试来检查系统的稳定性和完整性；在发布前阶段，可以使用探索性测试来模拟用户场景和操作。&lt;/p></description></item><item><title>有没有人从 QA 转到开发？</title><link>https://itest2025.pages.dev/qa-to-deleloper_38/</link><pubDate>Fri, 08 Mar 2024 09:02:30 +0800</pubDate><guid>https://itest2025.pages.dev/qa-to-deleloper_38/</guid><description>&lt;h3 id="有没有人从-qa-转到开发">有没有人从 QA 转到开发？&lt;/h3>
&lt;p>前几天在国外论坛上看到个帖子: 有没有人从 QA 转到开发？提问者表示:&lt;/p>
&lt;p>我和我的经理谈了谈职业发展，这是我得到的其中一个选择。尝试转为开发人员工作。&lt;/p>
&lt;p>在寻找新的 QA 工作时，外面没有那么多 QA 工作，相比之下，开发工作就很多了。&lt;/p>
&lt;p>因此，这让我想，由于有更多的机会，是否应该从 QA 转为开发？&lt;/p>
&lt;p>个人认为如果有能力转的话还是转一下比较好，毕竟开发的机会更多一点，而且天花板相对也比较高，不过高赞回答提供了另一种视角，我觉得也很有道理。&lt;/p>
&lt;p>下面是高赞回答的内容。&lt;/p>
&lt;p>我是一名软件测试开发工程师（SDET），拥有计算机科学学位，对高级编程原理有扎实的理解。在我的整个职业生涯（5 年以上）中，我一直在考虑转行。以下是我要说的话：&lt;/p>
&lt;p>是的，对开发人员的需求更高，但该行业的开发人员数量也比 SDET 数量多得多。成为最优秀的 SDET 候选人比成为最优秀的开发人员候选人更容易（竞争更弱）。&lt;/p>
&lt;p>开发(SWE) 通常薪酬更高，因为他们是构建产品的必需品。他们是新项目的第一个被雇佣的人，也是最后一个被解雇的人。如果没有开发人员，就没有必要有 QA。然而，在许多情况下，SDET 的薪水几乎和他们一样高。我自己在低收入地区: 蒙特利尔，加拿大，我可以赚到超过 150,000 美元的年薪。&lt;/p>
&lt;p>这也意味着开发人员通常被视为更重要。很多人看不起 QA，尽管 SDET 经常对编程原理有同样好的理解，我们只是不经常使用它，因为我们遇到的编码问题没有那么难。&lt;/p>
&lt;p>做开发人员的坏处是您可能需要工作更多，并承受更大的压力。作为开发人员，您必须遵守严格的交付时间表，同时还必须始终处于新技术和工具栈的最前沿。在工作与生活平衡（WLB）与薪酬方面，我 100%认为 SDET 更胜一筹。我比开发人员工作少 50%，但收入却是他们的 90-100%。这实际上可以让我接一些兼职合同并将我的收入翻倍。&lt;/p>
&lt;p>归根结底，这取决于您更优先考虑什么。您是否想留在编码世界并晋升为首席开发人员？还是想拥有更好的工作生活平衡、获得更多产品知识并过渡到 PO/PM 角色？这完全取决于你。&lt;/p>
&lt;p>简单来说高赞回答觉得测试开发比开发更好，因为竞争小，收入高，也不需要经常更新技术栈，不过这也意味着市场需求不旺盛，坑比较少，总之是有利有弊吧。&lt;/p>
&lt;p>不知道大家对这个问题有什么样的看法，欢迎留言讨论。&lt;/p></description></item><item><title>简陋的全自动化测试实践</title><link>https://itest2025.pages.dev/automation-test-practice-using-web-crawler_37/</link><pubDate>Fri, 08 Mar 2024 09:02:28 +0800</pubDate><guid>https://itest2025.pages.dev/automation-test-practice-using-web-crawler_37/</guid><description>&lt;p>最近在搜广推项目里做了一些小小的基于数据驱动的测试尝试。&lt;/p>
&lt;p>因为项目的特殊性，大部分的测试场景其实相对类似，大体可以分为下面几步&lt;/p>
&lt;ul>
&lt;li>收集数据，整合成 1 个类似宽表的东西。比如在美团上给外卖投放一个关键字广告，那么投放之前需要收集这个商家的门店信息，比如门店名，经纬度；菜品信息，这个商家有哪些菜品，名字分别是什么；品牌信息，这个商家属于哪个品牌，品牌有哪些特殊属性；这些信息其实都分别在不同的业务表里，开发那边有离线数据流去打宽表，但是对于测试来说，如果不使用自动化的方式的话则需要用 sql 去查询，遇到有分表的情况则比较麻烦；&lt;/li>
&lt;li>构造数据。还是外卖投关键字广告的例子，因为召回的规则和其他一些业务规则，我们投的关键字最好有相关性，比如商家的店名叫做“永久炸鸡”，那么投“炸鸡”这个关键字可能会比较好。所以通过上一步构造好的数据，从里面自动筛选出一些相关数据并进行构造也是必要的；&lt;/li>
&lt;li>执行业务并进行断言。以搜索为例，收集数据阶段我们收集了很多的门店信息和菜品信息，那么使用门店名和菜品名进行搜索的话是需要有结果出来的，由于排序规则是算法团队去实现和测试的，所以我这边只需要数据可以搜出来就好，至于排序是什么样子，测试用例里面是不需要去关注的。&lt;/li>
&lt;/ul>
&lt;p>综合考虑了一下，我有一个大胆的想法。&lt;/p>
&lt;p>上面这些步骤是不是都可以完全由自动化来实现？&lt;/p>
&lt;h3 id="使用爬虫进行数据收集">使用爬虫进行数据收集&lt;/h3>
&lt;p>测试环境和体验环境我是有数据库权限的，所以可以通过 sql 来进行数据查询，逻辑其实很简单，从现有的数据里查一些拥有有效菜品的有效商家就好了，所以问题就简化成了使用脚本去连接数据库，然后进行一系列的连表查询就好了。不过因为菜品进行了分表，所以需要一些额外的逻辑去处理一下，不过总的来说还是不难的。&lt;/p>
&lt;p>一切都很顺利，直到业务上线了以后，我发现我需要在线上去收集这些数据。因为安全策略的缘故，开发和测试都是没有线上数据库的访问权限的，因此我在非生产环境里通用的自动化脚本就成了一堆废纸。&lt;/p>
&lt;p>后来有一天，我发现项目的本地测试人员都是在业务后台的各个功能模块里做数据的查询和准备，我突然有了使用爬虫去爬取数据的想法。&lt;/p>
&lt;p>原理其实很简单，模拟业务后台的请求，通过后端暴露的 api 进行查询，最后想办法把数据组织好存储下来就行了。&lt;/p>
&lt;p>后来用代码简单的实现了一下，效果其实还是不错的，而且完全不需要去顾虑分库分表问题，因为管理后台的 api 把这些都实现好了。这也让我开阔了一些思路，其实爬虫除了爬外部数据之外，还可以爬内部其他团队的数据，只要业务有管理后台，一切都好说。&lt;/p>
&lt;h3 id="使用-redis-进行数据存储">使用 redis 进行数据存储&lt;/h3>
&lt;p>爬虫拉到数据之后，就需要对数据进行一些处理和存储。得益于业务 api 的杰出工作，之前查 sql 时候需要进行的数据处理现在就变得特别简单，因为业务逻辑都帮我都处理好了，下一步只需要把数据保存起来就好了。&lt;/p>
&lt;p>因为数据需要反复使用和在多个场景下使用，所以存储对于我来说是必须的，但对于另一些场景，存储中间数据可能不是必选项。&lt;/p>
&lt;p>因为对 redis 相对比较熟悉，所以我这里用 redis 实现了一些类似索引的东西。&lt;/p>
&lt;ul>
&lt;li>用 set 去存储一些实体的主键，比如门店 id 和菜品 id 之类；&lt;/li>
&lt;li>用 string 去存储详细信息，比如门店名，菜品的详细信息等；其中门店菜品之类的就直接用的 python dict 序列化成 json string，查询时候通过门店的 id 就可以直接拿到 json string，然后重新反序列化成 python 字典就好了&lt;/li>
&lt;/ul>
&lt;h3 id="使用后端-api-进行数据创建">使用后端 api 进行数据创建&lt;/h3>
&lt;p>对于搜索来说组装好数据基本就可以测了，不过对于广告来说还需要进行广告的创建。这里我是直接调用 api 做创建，效率高而且不用去页面点。&lt;/p>
&lt;h3 id="使用-pytest-进行自动化测试">使用 pytest 进行自动化测试&lt;/h3>
&lt;p>最后就是用例编写了，这里我直接用 pytest 实现了一些用例，贴一个具体例子。&lt;/p></description></item><item><title>10大流行的关于软件测试误解</title><link>https://itest2025.pages.dev/10-misunderstanding-for-test_35/</link><pubDate>Fri, 08 Mar 2024 09:02:25 +0800</pubDate><guid>https://itest2025.pages.dev/10-misunderstanding-for-test_35/</guid><description>&lt;p>软件测试实际上不像看上去那么容易。为了了解 web 产品测试可能包含的隐藏和意外，我们将分析与此类活动相关的十大最流行的误解。&lt;/p>
&lt;h3 id="误解-1测试很容易">误解 1：测试很容易&lt;/h3>
&lt;p>很多 IT 界人士（但不是测试人员）认为，软件测试并不难。它只是在一个图形界面中点击按钮而已。但实际上，一切并不那么简单。首先，QA 工程师必须全面研究一个产品，收集有关它的信息，提出并反驳假设，等等。
仅仅发现缺陷并不能使你成为一名测试人员。要成为一流的 QA 工程师，你必须能够理解软件和测试理论，提出正确的问题，并有效地找到相关信息。&lt;/p>
&lt;h3 id="误区-2软件测试很无聊">误区 2：软件测试很无聊&lt;/h3>
&lt;p>有人可能认为测试人员的日常工作很枯燥&amp;ndash;点击按钮，将设计与布局相比较。但如果这么简单，就不会有 QA 工程师了&amp;ndash;所有这些工作都会由机器完成。
测试人员每天都在与业务和客户的实际需求互动。他们看的是软件内部的工作方式。而且，测试的类型相当多样&amp;ndash;从可用性测试到性能测试和网络安全一应俱全，而且值得深入。&lt;/p>
&lt;h3 id="误区-3qa-工程师想黑掉一切">误区 3：QA 工程师想黑掉一切&lt;/h3>
&lt;p>事实上，测试人员不是黑掉程序，而是黑掉开发人员的幻觉。他们不想破坏任何东西；他们只是试图看看一切是如何工作的。有时测试结果与大家的期望完全不一致。&lt;/p>
&lt;h3 id="误区-4完美主义是测试员工作成功的关键">误区 4：完美主义是测试员工作成功的关键&lt;/h3>
&lt;p>事实上，情况恰恰相反。过度的完美主义只会阻碍正确的测试（就像在任何其他活动领域一样）。一个典型的完美主义者不能准确地意识到何时停止测试。而且，他也很难接受这样一个事实：永远不会有一个没有缺陷的完美的 web 产品。&lt;/p>
&lt;h3 id="误区-5测试人员不需要了解软件的内部实现">误区 5：测试人员不需要了解软件的内部实现&lt;/h3>
&lt;p>实际上，一流的测试人员应该能够理解现代技术和分析软件结构。编程语言的基础知识有助于此。你不必创建你自己的程序代码，只要至少了解一切是如何设置和工作的基础知识。&lt;/p>
&lt;h3 id="误区-6一切都有自动化人工测试将消失">误区 6：一切都有自动化，人工测试将消失&lt;/h3>
&lt;p>在任何情况下，你都不应该把 QA 的工作分为自动化和手动测试。自动化和手动测试人员都用他们的头脑工作，他们的工具并不那么重要。当然，你可以（也应该）使用先进的技术，但不要忘记，你不可能完全实现测试自动化，就像你不可能实现研究过程自动化。&lt;/p>
&lt;h3 id="误区-7测试拖慢了开发过程">误区 7：测试拖慢了开发过程&lt;/h3>
&lt;p>一些产品公司的员工相当认真地认为测试过程是一个简单的活动。而且他们确信，程序代码中要么没有缺陷，要么其数量微不足道。因此，当开发人员完成他们的前端工作时，他们认为实现 web 产品的大任务已经基本完成。但有时，在这个 &amp;ldquo;差不多 &amp;ldquo;的背后隐藏着大量的额外工作。软件测试和其他许多工作一样，是一个创造的过程。这完全取决于要完成的任务和要克服的风险。&lt;/p>
&lt;h3 id="误区-8qa-工程师和开发人员总是缠斗在一起">误区 8：QA 工程师和开发人员总是缠斗在一起&lt;/h3>
&lt;p>互联网上有很多关于开发和测试在对方车轮上装上辐条的有趣故事。但在实践中，这并不那么相同。只有当开发部门认为测试人员在控制他们时才会出现问题，或者，当我们用发现的缺陷列表来影响开发部门的绩效时。&lt;/p>
&lt;h3 id="误区-9测试人员对他们发现的每个-bug-都很兴奋">误区 9：测试人员对他们发现的每个 bug 都很兴奋&lt;/h3>
&lt;p>发现错误的兴奋感可能只发生在初级测试人员身上。 但随着时间的推移，它就会过去。熟练的员工会更加沮丧，因为这意味着他们将不得不做额外的工作。而且，这也推迟了任务的开始（部署网站，上架移动应用程序，等等）。质量保证的有效性并不取决于发现的错误的数量。他们工作的结果是一个经过质量测试的产品，一般来说，它能满足感兴趣的用户的需求。&lt;/p>
&lt;h3 id="误区-10如果你写了好的代码你就不需要测试人员了">误区 10：如果你写了好的代码，你就不需要测试人员了&lt;/h3>
&lt;p>这种观点在产品公司中非常普遍，那里盛行写自动测试的理念。但是，软件发展得越快，周围环境的变化越快，测试过程就越有意义。&lt;/p>
&lt;p>而这个名单还可以继续下去。但最主要的是，除了测试人员本身，没有人可以成为这个领域的专家。相应地，只有 QA 工程师可以自信地说出什么是事实，什么又不是。&lt;/p></description></item><item><title>软件测试中7个令人震惊的真相</title><link>https://itest2025.pages.dev/seven-unspoken-truths-about-software-testsing_34/</link><pubDate>Fri, 08 Mar 2024 09:02:24 +0800</pubDate><guid>https://itest2025.pages.dev/seven-unspoken-truths-about-software-testsing_34/</guid><description>&lt;p>这是最近看到的一篇比较有意思的文章，原文在这里：&lt;a href="https://medium.com/geekculture/seven-unspoken-truths-about-software-tests-4bcf0f720a04" target="_blank" rel="noopener noreffer ">https://medium.com/geekculture/seven-unspoken-truths-about-software-tests-4bcf0f720a04&lt;/a>，简单的加工翻译了一下，其中（）里的内容是我为了帮助大家理解夹带的私货，希望这篇文章会对大家有所启示。&lt;/p>
&lt;p>1，当你是一个项目的的测试负责人的时候，你有没有过质问过项目成员为什么没测试出某个具体的 bug，或者因为某人没有测出 bug 而直接责备他？&lt;/p>
&lt;p>2，当你提升了测试覆盖率的时候你有没有发现产品的 bug 数量其实没有发生变化？&lt;/p>
&lt;p>3，你有没有在发布之前花费了大量的时间去进行测试却最终发现一无所获，而发布之后 bug 却不期而至？&lt;/p>
&lt;p>4，开发可以测试他们自己的代码吗？&lt;/p>
&lt;p>5，bug 真的是发现的越晚修复成本就越高吗？&lt;/p>
&lt;p>6，你有没有过以不按套路出牌的方式来进行软件的测试，并称之为“探索性测试”？&lt;/p>
&lt;p>7，你是否需要通过 QA 活动来提升产品质量？&lt;/p>
&lt;h2 id="真相-1测试并不能找出所有的-bug">真相 1：测试并不能找出所有的 bug&lt;/h2>
&lt;p>很不幸这是真的，没有任何一种测试方式可以保证找出所有的 bug。&lt;/p>
&lt;p>一些测试活动跟直接上手点点点相比确实效率要低一些，所以我们可以不用那么关注测试的类型，相反我们要做的是选择合适的测试类型并综合使用，从而以最少的工作量打到较好的效果。（比如 ui 的自动化测试如果要做到非常复杂那么将花费相当大的开发和维护成本，但没有 ui 的自动化，每轮测试都靠人肉点来点去也不现实，所以比较合适的做法是一些稳定的核心路径可以用 ui 自动化去实现，平衡投入产出比，用较少的工作量达到效率最大化）&lt;/p>
&lt;p>当有人抱怨为什么测试没有发现某些问题的时候麻烦提醒他们：测试确实没有办法保证一定会发现某些特定的缺陷。&lt;/p>
&lt;p>我们会经常复盘测试的漏测情况，很不幸，这是落后的想法，这就像是在魔术揭秘了之后再马后炮的说其实这个戏法很简单，很容易被识破一样。事后做漏测复盘并不是一个有效的分析手段。&lt;/p>
&lt;p>永远不要责备测试工程师，他们并没有写出 bug，相反，他们一直在努力找出开发过程中引入的缺陷。没有什么是完美的，测试同学在接受现实的同时也需要记住千万别立 flag，因为测试不可能发现所有的 bug。&lt;/p>
&lt;h2 id="真相-2测试覆盖率与测试的效果几乎没有相关性">真相 2：测试覆盖率与测试的效果几乎没有相关性&lt;/h2>
&lt;p>是的，你没有看错。我们已经有足够的科学证据表明，增加单元测试覆盖率不一定会提高我们测试套件发现 bug 的效率！也许是时候关注与测试相关的内容而不是正在测试的代码量了。（这里作者的原话是 We already have enough scientific evidence to say that increasing unit test coverage may not necessarily increase your test suite effectiveness in finding defects!直译过来就是单元覆盖率的提升并不会提升测试套件发现缺陷的效率，说实话，我觉得单元测试覆盖率跟测试中发现 bug 的效率本来就没有什么关系，覆盖率代表的是代码被测试的程度，而发现 bug 的效率指的是时间和产出的关系，发现 bug 的效率高并不代表着产品的质量就好，反之亦然。不过看下文引用资料时的描述，我们可以看到作者的举证基本上都透露了一个信息，那就是单元测试覆盖率与 bug 的数量之前没有太多的关联，换句话说就是并不是单元测试覆盖率越高，产品的质量就越好，因为产品的质量好一般意味着可被观察到的 bug 相对少，而 bug 又跟单元测试覆盖率无关。这里为了严谨，作者的引用我就不做翻译了。）&lt;/p></description></item><item><title>Selenium 4.9.0 发布</title><link>https://itest2025.pages.dev/selenium-49-released_33/</link><pubDate>Fri, 08 Mar 2024 09:02:22 +0800</pubDate><guid>https://itest2025.pages.dev/selenium-49-released_33/</guid><description>&lt;p>我们非常高兴地宣布支持 Java、.NET、Ruby、Python 和 Javascript 及 Grid 和 Internet Explorer Driver 的 Selenium 4.9.0 版本发布。所有内容的链接可以在我们的下载页面找到。&lt;/p>
&lt;h3 id="重点如下">重点如下:&lt;/h3>
&lt;ul>
&lt;li>Chrome DevTools 支持现在是:v110、v111 和 v112(Firefox 仍然对所有版本使用 v85)&lt;/li>
&lt;li>Java 绑定的 Maven BOM。&lt;/li>
&lt;li>通过 Selenium Grid 现在可以远程下载文件。&lt;/li>
&lt;li>首先， Firefox 开始逐步废除 CDP，并用双向实现 (BiDi implementation) 替代它。&lt;/li>
&lt;li>InvalidSelectorException 现在继承自 WebDriverException 而不是 NoSuchElementException。&lt;/li>
&lt;li>发布了 Selenium Manager, 这个工具可以使用浏览器选项中设置的信息来获取正确的浏览器 driver。&lt;/li>
&lt;li>在 Selenium Grid 中可以设置子路径,以获得自定义的 Grid URL。&lt;/li>
&lt;li>在 Java 版本和 Grid 中完全移除 Json Wire Protocol 支持。&lt;/li>
&lt;/ul></description></item><item><title>情感驱动测试</title><link>https://itest2025.pages.dev/the-power-of-emotion-driven-testing_32/</link><pubDate>Fri, 08 Mar 2024 09:02:20 +0800</pubDate><guid>https://itest2025.pages.dev/the-power-of-emotion-driven-testing_32/</guid><description>&lt;p>看到一篇非常有意思的女性测试从业者的技术分享，忍不住翻译了一下，角度非常感性，引人深思，测试的世界其实特别多元，也希望以后有机会能遇到各种有意思的观点。&lt;/p>
&lt;p>原文地址: &lt;a href="https://fishouthebox.medium.com/the-power-of-emotion-driven-testing-280a944d352b" target="_blank" rel="noopener noreffer ">https://fishouthebox.medium.com/the-power-of-emotion-driven-testing-280a944d352b&lt;/a>&lt;/p>
&lt;p>昨晚，我在 Ministry of Testing 的 TestBash World 做了一个演讲，其中有一部分是关于情感的。其中有一部分我分享了 10 条情感路径，你可以用来指导你的测试。来源于 Gojko Adzi, David Evans 和 Tom Roden 的《改善我们测试的五十个快速想法》一书。&lt;/p>
&lt;p>我把这个写在博客上是出于 Gwen Diagram 的一个建议，以文字的形式来分享这个内容会很有用。因此，我们开始吧。&lt;/p>
&lt;ul>
&lt;li>可怕的路径&amp;ndash;对你的利益相关者来说，什么会真正烧毁你的房子？可能是品牌效应？安全风险？&lt;/li>
&lt;li>快乐的路径&amp;ndash;每次都能通过的道路。&lt;/li>
&lt;li>愤怒的路径 - 试图让应用程序做出糟糕的反应。如验证错误、不良输入和逻辑区域。&lt;/li>
&lt;li>疏忽的路径 - 考虑需要测试的安全风险，如认证、授权、权限、数据保密性。&lt;/li>
&lt;li>尴尬的路径&amp;ndash;会造成巨大尴尬的事情，如主页上的拼写错误。&lt;/li>
&lt;li>荒凉的路径&amp;ndash;为应用程序或组件提供暗淡的东西。例如 nulls、blanks 或缺失的数据。&lt;/li>
&lt;li>遗忘之路&amp;ndash;填满内存和 CPU 容量，使应用程序没有地方可以存储任何东西。看看它变得多么健忘，是否开始丢失数据，要么是已经存储的东西，要么是它正在持有的东西。&lt;/li>
&lt;li>犹豫不决的路径&amp;ndash;打开和关闭一些东西，点击浏览器上的返回按钮等。&lt;/li>
&lt;li>贪婪的路径&amp;ndash;选择一切，勾选每一个方框，选择每一个选项。&lt;/li>
&lt;li>紧张的路径&amp;ndash;找到功能和组件的突破点。负载/性能测试的考虑。&lt;/li>
&lt;/ul>
&lt;p>by 乙醇：情绪相关的分类方式更加感性一些，相比于我们理性的测试用例和测试场景来说，这里没有对错之分，只要考虑的全面，用任何分类方式都是合理的。&lt;/p></description></item><item><title>2024年如何学习python</title><link>https://itest2025.pages.dev/how-to-learn-python-2024_31/</link><pubDate>Fri, 08 Mar 2024 09:02:19 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-learn-python-2024_31/</guid><description>&lt;p>这几天 hack news 上比较热烈的讨论是这一套 python 教程：https://github.com/dabeaz-course/python-mastery。&lt;/p>
&lt;p>作者是 Dabeaz, David Beazley 的别名，他是一位计算机科学家、教育家和研究员，拥有超过 35 年的经验。Dave 在 Python 社区中非常活跃，他创建了多个软件包，参加了会议演讲和教程，并且以《Python Distilled》（Addison-Wesley）、《Python Essential Reference》（Addison-Wesley）和《Python Cookbook》（O&amp;rsquo;Reilly Media）的作者而闻名。他通过提供各种高级计算机科学和编程课程来支持这些工作。&lt;/p>
&lt;p>来头很大的，毕竟是《Python Cookbook》 的作者，所以专业性上是有背书的。&lt;/p>
&lt;p>简单的把项目的 readme 文件翻译了一下，这个项目的核心学习理念就是手动操作，完成课程练习，多写代码总是有收获的。&lt;/p>
&lt;h2 id="简介">简介&lt;/h2>
&lt;p>这是一门高级 Python 编程的练习驱动课程，经过十多年的企业培训实战验证数百次。由 David Beazley 撰写，他是《Python Cookbook, 3rd Edition》（O&amp;rsquo;Reilly）和《Python Distilled》（Addison-Wesley）的作者。课程采用了知识共享许可协议，无广告、无追踪、无弹窗、无新闻通讯和 AI。&lt;/p>
&lt;h2 id="目标受众">目标受众&lt;/h2>
&lt;p>这门课程适合希望进一步提高 Python 编程水平，从编写简短脚本转向编写更复杂程序的 Python 程序员。课程主要关注常用库和框架中使用的编程技巧。主要目标是更好地理解 Python 语言本身，以便理解他人的代码，并将新学到的知识应用于自己的项目中。&lt;/p>
&lt;h2 id="先决条件">先决条件&lt;/h2>
&lt;p>你已经掌握一些 Python 知识。这不是一门面向初学者的课程。如果你需要更多入门材料，你可以考虑参加 Practical Python Programming(&lt;a href="https://dabeaz-course.github.io/practical-python" target="_blank" rel="noopener noreffer ">https://dabeaz-course.github.io/practical-python&lt;/a>)课程。&lt;/p>
&lt;h2 id="如何参加课程">如何参加课程&lt;/h2>
&lt;p>首先，你应该将 GitHub 仓库 fork 或克隆到自己的机器上。&lt;/p>
&lt;p>我们假设你在本地有一个合适的 Python 开发环境。这意味着你已经正确安装了 Python，有一个编辑器/集成开发环境和其他常用于 Python 开发的工具。由于课程使用了多个文件和模块导入，不推荐使用 Notebooks。&lt;/p>
&lt;p>&lt;a href="PythonMastery.pdf" rel="">&lt;code>PythonMastery.pdf&lt;/code>&lt;/a> 文件包含了详细的演示幻灯片。课程练习和建议的时间安排都有清楚的标示。你应该将它放在身边（我建议你下载并在本地 PDF 阅读器中查看）。从这里开始吧！&lt;/p></description></item><item><title>没有QA就没有Bug</title><link>https://itest2025.pages.dev/no-qa-no-bugs_30/</link><pubDate>Fri, 08 Mar 2024 09:02:17 +0800</pubDate><guid>https://itest2025.pages.dev/no-qa-no-bugs_30/</guid><description>&lt;p>关于测试左移 shift left 的讨论已经持续了很长一段时间了，前几天刚好看到有外国友人亲身参与了这个过程，结果有点出人意料，所以翻译出来分享了一下。&lt;/p>
&lt;p>在 2017 年，成为一名 QA 是一个有趣的时间。但不是搞笑哈哈，是有点点诡异了。每个人都想着测试左移（在合理范围内），但不是为了提高他们的质量，他们会以减少传统的质量保障人员的瓶颈的心态来做，那就是在迭代结束时，测试所有的东西。&lt;/p>
&lt;p>我记得几年前，我是一个由 5 个开发人员组成的团队中唯一的 QA，我们没有大量的自动化测试，但左移的思维方式得到了应用，至少是部分应用。我在开发人员做需求时，尽早地帮助他们，这在迭代结束后会得到回报，测试阶段的时间是最少的。充其量，我们会在测试阶段发现一两个问题，并迅速修复它们（或假设该问题到下一个版本）。&lt;/p>
&lt;p>公司内部的一个新项目已经开始了，他们想在一开始就应用左移的思维方式，但他们不需要 QA。所有的工作都可以通过自动化测试完成，开发人员将完全承担和拥有这些测试。作为一个 QA 人员，我很好奇，我想看看他们是怎么做的，他们是怎么涵盖这个用例的，或者其他用例的，我想学习并帮助他们。但是我被推到了一边，因为自动化才是王道，不需要 &amp;ldquo;手动 QA&amp;rdquo;。&lt;/p>
&lt;p>当他们在几个月后找到我的时候，我遇到了 &amp;ldquo;告诉你 &amp;ldquo;的时刻，他们在生产中出现了大量的问题。那是一个混乱的局面。这时他们意识到，即使他们写了大量的测试，他们也没有考虑到他们的测试策略。解决方案是在团队中加入一个 &amp;ldquo;手动 QA&amp;rdquo;，来处理应用程序的问题，防止生产中出现更多的错误。&lt;/p>
&lt;p>快到今天，我意识到他们已经非常接近了。他们拥有自动化测试策略。团队中的任何新开发人员都经历了严格的代码覆盖率、配对编程和细致的提交代码审核过程。他们唯一缺少的是一个好的测试策略，按照测试金字塔的描述进行工作（我知道你们中的一些人鄙视这个，但有时，它真的可以帮助团队分类并专注于正确的事情！），以及在用户体验上多想一点。&lt;/p>
&lt;h3 id="那么你的-qa-团队成员可以在左移的转型或项目中扮演什么角色呢">那么，你的 QA 团队成员可以在左移的转型或项目中扮演什么角色呢？&lt;/h3>
&lt;ul>
&lt;li>在开始 &amp;ldquo;左移 &amp;ldquo;时，文档被证明是有用的。通过确保针对现有功能和新功能中最关键的点，它可以更容易地从传统的方法过渡。通常把重点放在用户的关键功能和你的应用程序中常见的脆弱点上。&lt;/li>
&lt;li>参加研发的启动会议。帮助提前考虑潜在的问题，用例子来引导这些会议是最好的做法。就我个人而言，我更喜欢在开发人员开始一个需求时以非正式的方式召开这些会议，但你也可以把这些会议变成一个定期会议，类似于你的 scrum 计划和复盘会议。这种做法也被称为 3 amigos（读起来不错）。&lt;/li>
&lt;li>学会阅读代码，看看 PR 评论，学习自动化，你的 QA 团队成员越是多才多艺，开发人员就越有信心作为一个团队来交付他们的功能，而不是依赖一个 QA 警察。&lt;/li>
&lt;/ul>
&lt;p>如果你的团队没有一个已经在职的 QA，你的团队已经可以开始应用同样的准则。停下来，呼吸，花 15 分钟思考可能的测试用例。不要在功能上走得太深，但思考所有可能的用户行为有很大的帮助。把它们写下来 👏。这将帮助你防止很多 &amp;ldquo;我没有想到的 &amp;ldquo;情况。&lt;/p>
&lt;p>虽然这不是一个完美的指南，但这是很好的第一步，我称之为 &amp;ldquo;在 3 天周末前的星期五下午 4 点部署的信心&amp;rdquo;。但这是另一个故事了。&lt;/p></description></item><item><title>Long Live the Test Pyramid</title><link>https://itest2025.pages.dev/long-live-test-pyramid_29/</link><pubDate>Fri, 08 Mar 2024 09:02:16 +0800</pubDate><guid>https://itest2025.pages.dev/long-live-test-pyramid_29/</guid><description>&lt;p>最近看到一篇关于测试金字塔的讨论，觉得非常有道理，原文在这里: &lt;a href="https://www.smashingmagazine.com/2023/09/long-live-test-pyramid/" target="_blank" rel="noopener noreffer ">https://www.smashingmagazine.com/2023/09/long-live-test-pyramid/&lt;/a>&lt;/p>
&lt;p>顺便翻译一下，希望对大家有所帮助。&lt;/p>
&lt;p>我亲爱的同事 Jan Philip Pietrczyk 曾经对开发者在编写功能代码方面的责任发表过评论：&lt;/p>
&lt;blockquote>
&lt;p>“我们的日常工作最终会交到信任我们的人手中，他们不仅信任我们已经尽力而为，还信任我们的代码能够正常运行。”
— Jan Philip Pietrczyk&lt;/p>&lt;/blockquote>
&lt;p>他的这些话一直深深地印在了我的脑海中，因为它让我们的代码置于依赖它的人们的背景之中。在这个快节奏的世界里，用户信任我们编写最优秀的代码，并且我们的软件“简单地”可以运行。确实，要达到这种信任水平是一个挑战，这就是为什么测试是任何开发堆栈中如此重要的一部分。测试过程评估了我们工作的质量，通过对不同情况进行验证，帮助在问题变得严重之前识别问题。&lt;/p>
&lt;p>测试金字塔是众多测试策略之一。尽管自 2012 年引入以来，它可能已经成为了主要的测试模型，在过去的十年中占据主导地位，但我发现现在提及它的次数并不像过去那么多。它是否仍然是测试的“首选”方法？与此同时，许多其他方法也已经涌现，因此是否可能是测试金字塔被更适合当今开发的更现代模型所淹没和遮盖？&lt;/p>
&lt;p>这就是我想要探讨的问题。&lt;/p>
&lt;h3 id="测试策略的关键点">测试策略的关键点&lt;/h3>
&lt;p>与用户建立信任需要一个强大的测试策略，以确保我们编写的代码使产品按照他们的期望工作。我们应该从哪里开始编写好的测试？我们需要多少个测试？许多人一直在思考这个问题。但是 Kent C. Dodds 发表的一个简短评论让我恍然大悟。&lt;/p>
&lt;blockquote>
&lt;p>“最大的挑战是知道要测试什么，以及如何进行测试，以获得真正的信心，而不是测试实现细节所带来的虚假信心。”&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>-Kent C. Dodds
这就是起点！确定测试的目标是测试策略中最关键的任务。互联网上充斥着描绘糟糕决策的迷因，其中许多是因为根本不知道特定测试的目的以及我们需要多少测试来确保信心。在测试方面，有一个“正确的比例”，以确保代码经过适当的测试并且按照预期工作。&lt;/p>&lt;/blockquote>
&lt;p>问题在于许多开发人员只关注一种类型的测试 - 通常是单元测试覆盖率 - 而不是对各种单元如何协同工作制定策略。例如，当测试一个水槽时，我们可以分别测试水龙头和下水道，但它们是否协同工作呢？如果下水道堵塞，而水龙头继续放水，那么事情就不正常了，即使单元测试表明水龙头正常工作。&lt;/p>
&lt;p>关于测试的方法通常用不同的形状来描述，就像我们已经看到金字塔模型一样。在这篇文章中，我想分享一些我观察到的形状，以及它们在现实场景中的应用，最后总结出哪种测试策略符合我对当今开发实践中良好测试覆盖的个人标准。&lt;/p>
&lt;h3 id="回顾基础知识">回顾基础知识&lt;/h3>
&lt;p>在此之前，让我们回顾一些不同测试类型的常见定义，以刷新我们的记忆：&lt;/p>
&lt;h4 id="手动测试">手动测试&lt;/h4>
&lt;p>这是由实际人员执行的测试。这意味着测试将要求真实用户按照脚本化的使用案例进行点击应用程序，以及未经脚本化的尝试在不可预见的情况下“破坏”应用程序。这通常是通过与由产品团队观察的用户进行面对面或远程面试来完成的。&lt;/p>
&lt;h4 id="单元测试">单元测试&lt;/h4>
&lt;p>这种类型的测试是将应用程序分解为小的、隔离的、可测试的部分，或者称为“单元”，通过分别和独立地测试每个单元以确保其正常运行来提供覆盖。&lt;/p>
&lt;h4 id="集成测试">集成测试&lt;/h4>
&lt;p>这些测试关注组件或系统之间的交互。它们一起观察单元测试，以检查它们在集成在一起作为整体工作时是否良好。&lt;/p>
&lt;h4 id="端到端e2e测试">端到端（E2E）测试&lt;/h4>
&lt;p>在这种类型的测试中，计算机模拟实际用户的交互。将 E2E 视为验证用户故事的一种方式：用户是否可以完成需要一系列步骤的特定任务，结果是否符合预期？这是从用户体验的一端到另一端进行测试，确保输入产生正确的输出。&lt;/p>
&lt;p>那么，这些测试类型应该如何互相交互呢？测试金字塔是我们传统依赖的隐喻，用来将这些不同类型的测试整合到一个完整的测试套件中，适用于任何应用程序。&lt;/p>
&lt;h3 id="伟大的测试金字塔">伟大的测试金字塔&lt;/h3>
&lt;p>测试金字塔是由 Mike Cohn 在他的书《成功的敏捷》中首次引入，并由 Martin Fowler 在他的“实用测试金字塔”文章中进一步发展的，根据测试的性能和成本对测试进行了优先排序。它建议编写具有不同粒度级别的测试，包括较少的高级别测试以及更多快速、经济、可靠的单元测试。推荐的测试顺序是从快速和经济到慢和昂贵，从底部开始有很多单元测试，然后是服务，即中间的集成测试。然后是位于顶部的较少但更具体的 UI 测试，包括端到端测试。&lt;/p>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="https://files.smashing.media/articles/long-live-test-pyramid/test-pyramid.jpg"
 data-srcset="https://files.smashing.media/articles/long-live-test-pyramid/test-pyramid.jpg, https://files.smashing.media/articles/long-live-test-pyramid/test-pyramid.jpg 1.5x, https://files.smashing.media/articles/long-live-test-pyramid/test-pyramid.jpg 2x"
 data-sizes="auto"
 alt="https://files.smashing.media/articles/long-live-test-pyramid/test-pyramid.jpg"
 title="测试金字塔的手绘示意图分为三个层次" />&lt;/p></description></item><item><title>微软的office365给软件测试带来的变革</title><link>https://itest2025.pages.dev/office-365-broke-up-testing_28/</link><pubDate>Fri, 08 Mar 2024 09:02:14 +0800</pubDate><guid>https://itest2025.pages.dev/office-365-broke-up-testing_28/</guid><description>&lt;p>微软今天发布了集成了 GPT-4 模型的 office 套件，从演示视频看，大概可以做这样一些事情&lt;/p>
&lt;ul>
&lt;li>输入指令自动做表&lt;/li>
&lt;li>输入指令写邮件&lt;/li>
&lt;li>输入指定自动做 ppt，而且一做就是好多页，挺震撼的&lt;/li>
&lt;/ul>
&lt;p>稍微了解了一下原理，大概流程是&lt;/p>
&lt;ul>
&lt;li>用户发送 prompt 到 office&lt;/li>
&lt;li>office 获得用户授权访问用户的核心数据（email，聊天记录，会议信息，日程，联系人列表等）&lt;/li>
&lt;li>office 整合用户信息修改 prompt&lt;/li>
&lt;li>office 将修改后的 prompt 发送到语言模型&lt;/li>
&lt;li>office 拿到语言模型的返回，并结合用户数据进行信息整合&lt;/li>
&lt;li>office 拿到整合后的信息和 app 的命令列表，进行自动化和信息展示&lt;/li>
&lt;/ul>
&lt;p>从原理可以看出 office 基本上把语言模型，目前也就是 GPT-4 当成了黑盒，这样应用软件层面其实不需要了解太多模型的细节和实现，只需要把模型当成语言理解器，内容生成器就好了。语言模型只负责理解用户的 prompt 和生成内容，office 负责整合数据，调整 prompt，以及我们今天讨论的话题，执行 ui 自动化。&lt;/p>
&lt;h3 id="未来办公软件的形态">未来办公软件的形态&lt;/h3>
&lt;p>未来办公软件有很大概率会跟 AI 结合起来，他们大概会是&lt;/p>
&lt;ul>
&lt;li>有自己的主要形态和业务领域，比如邮件客户端，文字处理软件客户端，也就是有 UI，有交互，有一些逻辑，跟现在的办公软件差不多&lt;/li>
&lt;li>有 AI 辅助的能力，可以接受用户的 prompt 并进行修改和吟唱，然后调用大语言模型&lt;/li>
&lt;li>有完善的 UI 自动化能力，根据大语言模型返回的内容自动化的进行操作，并展现给用户&lt;/li>
&lt;/ul>
&lt;p>所以简单来说，未来的办公软件将会调用 AI，并执行自动化。&lt;/p>
&lt;h3 id="那么未来的软件都会有-ui-自动化的接口">那么未来的软件都会有 ui 自动化的接口&lt;/h3>
&lt;p>这个结论是水到渠成了，有 ui 自动化接口，那么就需要做 ui 自动化测试。所以对于一些泛化的办公软件开发团队来说，测试人员不仅要负责传统的功能测试，还需要调用 ui 自动化接口，保证接口的正确性，甚至是 ui 自动化的测试代码都可以成为大语言模型的无监督学习物料。&lt;/p>
&lt;h3 id="更泛化的办公软件实现">更泛化的办公软件实现&lt;/h3>
&lt;p>也许未来会出现更加泛化办公软件或者是办公流程软件，可能包含这些部分&lt;/p></description></item><item><title>Whatsup是怎么做测试的</title><link>https://itest2025.pages.dev/how-whatsapp-tests-software_27/</link><pubDate>Fri, 08 Mar 2024 09:02:13 +0800</pubDate><guid>https://itest2025.pages.dev/how-whatsapp-tests-software_27/</guid><description>&lt;p>看到了一篇关于 whatsup 是如何做测试的文章，尽管里面有一些拍领导彩虹屁的嫌疑，不过还是可以让我们了解到 meta(whatsup 属于 meta)内部整体质量保障架构的情况。翻译了一下，供大家参考。原文地址: &lt;a href="https://automationhacks.io/2023-10-18-how-whatsapp-tests-software" target="_blank" rel="noopener noreffer ">https://automationhacks.io/2023-10-18-how-whatsapp-tests-software&lt;/a>&lt;/p>
&lt;p>我正在思考 WhatsApp 团队如何测试其应用程序以及全球其他团队可以从他们的测试工程实践中学到什么。这也是很多人问我的一个经常性问题，所以让我们来揭开一些面纱，好吗？&lt;/p>
&lt;h3 id="测试方法">测试方法&lt;/h3>
&lt;p>我在 2021 年底到 2023 年中期的一年半时间里，作为 WhatsApp 测试自动化团队的成员，在他们伦敦办公室工作，我有幸近距离观察了一个拥有 20 亿多用户的应用程序是如何进行测试的。&lt;/p>
&lt;p>WhatsApp 测试自动化团队负责开发测试基础设施、工具和框架，以便让开发人员为 WhatsApp 客户端（尤其是 Android、iOS、Web 和桌面应用程序）编写高效的测试。这是整个组织测试策略的关键部分。&lt;/p>
&lt;p>在某些方面，这些方法与其他面向消费者的移动应用程序相似，具有测试金字塔的各个典型层次。WhatsApp 团队倾向于使用许多开源工具来编写自动化测试，但也使用许多内部元工具和基础设施来加强整个测试策略。WA 的测试方法也与元应用程序有很大的不同。&lt;/p>
&lt;p>在本文中，我将讨论两个关键因素，这些因素促成了自动化的强大文化。&lt;/p>
&lt;ul>
&lt;li>框架和基础设施&lt;/li>
&lt;li>支持这一文化的团队和角色&lt;/li>
&lt;/ul>
&lt;p>让我们深入探讨一下吧 🏊&lt;/p>
&lt;h3 id="测试框架">测试框架&lt;/h3>
&lt;p>首先，让我们看看测试金字塔的不同层次，以及使用了哪些不同的工具和框架。&lt;/p>
&lt;p>对于移动框架，自动化测试在测试金字塔的各个层次上进行编写，使用了大量的 Espresso 和 XCUITests，相对较少的 E2E 测试。&lt;/p>
&lt;p>&lt;strong>Android&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Unit tests: JUnit, Roboelectric&lt;/li>
&lt;li>UI tests: Espresso&lt;/li>
&lt;li>Screenshot tests: Screenshot tests for Android (read more about this here 🔗)&lt;/li>
&lt;li>E2E tests: Jest-based internal E2E testing framework&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>iOS&lt;/strong>&lt;/p></description></item><item><title>可靠的预发布环境(staging环境)对测试的重要性</title><link>https://itest2025.pages.dev/the-importance-of-testing-in-a-reliable-staging-environment_25/</link><pubDate>Fri, 08 Mar 2024 09:02:10 +0800</pubDate><guid>https://itest2025.pages.dev/the-importance-of-testing-in-a-reliable-staging-environment_25/</guid><description>&lt;p>很少看到有人讨论预发布环境的问题。今天正好看到一篇文章&lt;a href="https://www.ministryoftesting.com/articles/b1d3d371" target="_blank" rel="noopener noreffer ">The Importance Of Testing In A Reliable Staging Environment&lt;/a>，里面提到了一个很有意思概念&lt;strong>反质量文化&lt;/strong>。个人认为文中讨论的预发布环境可能更接近于国内认知中的测试环境和预发布环境的结合体。尽管如此，文章还是非常有借鉴意义的，随便用 ai 翻译了一下，供大家参考。&lt;/p>
&lt;p>许多软件开发组织会选择容忍缺陷丛生的预发布环境:很少有人认为这是个大问题。如果你从事软件开发,那么你很可能见过类似普通人的第一辆车那样状态的预发布环境。我们大多数人承诺在最初买下车辆时会修复问题,但实际上车辆保持的状态跟买来时一模一样!车门镜碎了,尾灯闪烁不定,勉强还能开。我们说服自己这些小问题不重要,也太贵了不值得修复。&lt;/p>
&lt;p>我想精确地解释为什么这些“小”问题会给你的工程团队带来重大问题。事实上,它们很可能已经造成麻烦了。然后,我将给你一些建议,如何说服你的公司投入一些资源从根本上解决这些问题。最后,我将为你提供一些想法,以确保你成功完成清理预发布环境的任务——并保持其清洁!&lt;/p>
&lt;h3 id="预发布环境一些基本事实和现实">预发布环境:一些基本事实和现实&lt;/h3>
&lt;p>当我谈到“预发布环境”时,这意味着任何工程师被期望用来执行其日常职责的开发或测试环境。这包括开发新特性、回归测试、运行自动化测试等。你的公司可能使用不同的术语。&lt;/p>
&lt;p>质量低下、不可靠的预发布环境以及对生产环境的糟糕复制会向你的软件开发生命周期中引入细微的缺陷。它们很可能会被你的开发团队遇到,可能有人会在回顾会上提出几次。但从未采取行动解决它们。你有更高的优先事项;也许知道如何解决问题的人不再为公司工作了。无论如何,总有某种原因导致现在无法处理它。毕竟,它并没有阻塞任何人。然后,使用预发布环境的这种环境缺陷就被接受为一个事实。&lt;/p>
&lt;p>“嘿,伙计们,我注意到这个页面在我们的预发布环境中无法加载&amp;hellip;&amp;hellip;但这在生产环境中却不会发生。我们知道这是为什么吗?🤔”
“哦,天呐,它就是这样的 😂 别担心!”
这就是&lt;strong>反质量文化&lt;/strong>开始渗入你的流程和团队心态的地方。&lt;/p>
&lt;p>这将造成三个问题:&lt;/p>
&lt;ul>
&lt;li>反质量文化&lt;/li>
&lt;li>更高的生产问题风险&lt;/li>
&lt;li>你的开发流程中的隐藏成本&lt;/li>
&lt;/ul>
&lt;h3 id="反质量文化">反质量文化&lt;/h3>
&lt;h4 id="什么是反质量文化">什么是反质量文化?&lt;/h4>
&lt;p>反质量文化是团队范围内对低质量工作方式的接受。&lt;/p>
&lt;p>如果说质量文化是为了质量本身而努力,那么反质量文化就是其反面。你的团队会慢慢对预发布环境中的意外行为变得麻木。他们开始假定(或怀疑)所有出现的意外行为都是由于环境被忽略而不是真正的问题引起的。工程师已经习惯了看到这些问题,学会了规避它们,结果也更加疏忽大意。这是一种反质量文化,直接导致缺陷疲劳。当我们习惯看到缺陷时,缺陷疲劳就会发生,我们会 develop 认知偏见并认为不相关的缺陷是环境问题,并因此改变我们的行为,可能会带来负面结果。&lt;/p>
&lt;p>在缺陷疲劳出现之前,优秀的工程师会勤奋地花时间调查这些问题。你可能会注意到这体现在团队聊天消息、会议投诉或回顾会议中的问题上。你的负责人或管理人员可能最初不认为它们足够重要到需要修复的程度。但不太明显的是,在团队将其归类为环境问题之前,调查这个问题所浪费的时间有多少。&lt;/p>
&lt;p>考虑这些场景。它们现在可能正在你的工程部门发生:&lt;/p>
&lt;ol>
&lt;li>一位工程师正在代码库的一个不熟悉的部分工作,当他们看到页面的一部分突然不像应该的那样加载时。他们想独立工作,并决定在麻烦别人之前自己调查这个问题。他们花了一个小时调试自己的代码,假定他们的更改导致了这个问题,然后代码突然又开始正常工作了。后来他们询问并发现这是一种他们不知道的间歇性问题。&lt;/li>
&lt;li>一位工程师完成了一个变更的工作,在发送给 QA 之前,他们快速检查了这个分支。他们在这个领域很有经验,完全了解软件中的所有怪癖。他们注意到一些奇怪的 UI 缺陷,但很快就得出结论,不太可能是他们的更改导致的&amp;hellip;&amp;hellip;因为环境一贯有缺陷。他们将任务发送给 QA,很快就被打回,因为 QA 确认这确实是一个真正的问题。&lt;/li>
&lt;/ol>
&lt;p>随着时间的推移,第一种情况下的工程师可能会厌倦调查这些环境缺陷,无论是有意识地还是无意识地。最终,第一种情况中勤奋的工程师会变成第二种情况中经验丰富但误解的工程师!&lt;/p>
&lt;p>当你的工程师反复查 bug 或意外行为时,缺陷疲劳就会发生,他们会感到精神被掏空。过了一段时间后,重复的调查完全停止。如果很有可能只是在浪费时间,那么工程师需要有很强的自我意识和自我控制才能一遍又一遍地查这些缺陷。&lt;/p>
&lt;p>现实情况是:我们大多数人没有时间调查环境缺陷,所以我们依赖这些快速的认知偏见来节省时间。这可能导致错误地总结它们是环境问题&amp;hellip;&amp;hellip;或者错误地总结它们不是环境问题。&lt;/p>
&lt;h4 id="反质量文化如何影响-qa-工程师">反质量文化如何影响 QA 工程师&lt;/h4>
&lt;p>反质量文化会导致 QA 工程师士气低落。他们字面上就是质量保证,如果他们周围都是缺陷并且需要规避工作,这会让工作变得非常困难,坦白地说,这是令人沮丧的。&lt;/p>
&lt;p>缺陷疲劳也会影响 QA 工程师,在主要职责是查找缺陷的角色中,这是一种很危险的习惯。我将在下一节中进一步探讨这一点。&lt;/p>
&lt;p>QA 工程师可能是预发布环境中最频繁的用户:这是我们运行自动化测试和执行探索性测试的地方。如果你的公司不投入时间确保 QA 能够在不断遇到环境问题的情况下履行其角色,他们可能会另谋高就。或者,在另一常见的情况下,你剩下的 QA 工程师的生产力较低,没有发挥他们的全部潜力,这确实很可惜。&lt;/p>
&lt;h4 id="更高的生产问题风险">更高的生产问题风险&lt;/h4>
&lt;p>如果你的预发布环境充满缺陷,在测试期间可能会发生以下情况。一个 QA 工程师可能会经常遇到一个小的环境缺陷。尽管这些缺陷很小,但它们确实会影响你的用户,虽然是间接的,如果这些环境缺陷的副作用影响了你的运营底线。&lt;/p>
&lt;p>首先,工程师必须弄清楚这个缺陷是由于预发布环境存在问题,还是确实存在于代码中。这需要宝贵的时间。&lt;/p>
&lt;p>或者,如果他们决定不调查疑似的环境问题,这可能会有后果。现实情况是:QA 并不总是正确的,这可以理解!如果一个 QA 工程师有一个截止期限,让他们感到压力,并遇到一个“可能”是环境问题的东西,他们很有可能允许更改继续前进,因为他们认为这不是一个真正的问题,或者他们已经那么疲劳,以至于没有将其注册为一个问题。 QA 工程师也不是万能的。&lt;/p></description></item><item><title>度量自动化测试效果的10个指标</title><link>https://itest2025.pages.dev/10-metric-for-automation-test_24/</link><pubDate>Fri, 08 Mar 2024 09:02:09 +0800</pubDate><guid>https://itest2025.pages.dev/10-metric-for-automation-test_24/</guid><description>&lt;p>低代码自动化测试平台的普及，测试同学代码能力的提升使得目前自动化测试已经成了测试工作中非常重要的组成部分，不管是由于 kpi 压力也好，还是因为需要消除其他方面的质疑声音也罢，我们经常需要在工作中制定一些可以度量的指标来描述某项工作或者改进的实际效果。那么有哪些指标可以去度量自动化测试的最终效果呢，这里给大家一些常见指标作为参考。&lt;/p>
&lt;h3 id="可自动化率">可自动化率&lt;/h3>
&lt;p>这个指标主要是描述所有的测试用例中有多少的用例可以被用自动化的方式进行回归，毕竟有一些用例可能还是要手动去执行的，这个值越高那么进行自动化的边际成本可能就越低。&lt;/p>
&lt;p>计算公式：可自动化率 = （可以实现自动化的用例数量 / 用例总数）* 100&lt;/p>
&lt;h3 id="自动化脚本-bug-发现率">自动化脚本 bug 发现率&lt;/h3>
&lt;p>这个指标可以直观的描述自动化测试的效果，你可以通过这个指标来评估当前的投入产出比是否可以接受，另外在不同的测试环境中的效果应该是不一样的，举例来说在测试环境中我们可能主要是进行新功能的测试，那么发现的大多数问题可以都是来自手工测试，但是在 staging 环境或者说是预发布环境，我们应该主要通过自动化脚本来发现 bug，所以这个效果值应该相对高一些。&lt;/p>
&lt;p>计算公式：（自动化测试发现的 bug 数 / 有效 bug 数）* 100&lt;/p>
&lt;h3 id="用例通过率">用例通过率&lt;/h3>
&lt;p>这个指标用来衡量用例的稳定性和自动化测试的实际效率，毕竟如果通过率低的话就意味着我们需要花费大量的时间去定位运行失败的原因。在多次测试中，通过率如果明显下降那么可能意味着：要么是我们的用例不稳定，不值得信赖；要么是本次的发布中包含了太多的 bug。&lt;/p>
&lt;p>计算公式：（通过的用例数 / 执行的用例数）* 100&lt;/p>
&lt;h3 id="用例执行时间">用例执行时间&lt;/h3>
&lt;p>天下武功，唯快不破。如果用例执行的速度太慢那么我们就没有办法在代码部署后迅速的给开发人员以反馈，浪费时间就是浪费生命。&lt;/p>
&lt;p>计算公式：用例结束时间 - 用例开始时间&lt;/p>
&lt;h3 id="自动化测试用例覆盖率">自动化测试用例覆盖率&lt;/h3>
&lt;p>耳熟能详的概念，也是很多团队都会追求的一个指标，覆盖率越高就证明测试回归的效率越高，这是一个需要长期追踪的指标，kpi 里寻常见，okr 前几度闻。&lt;/p>
&lt;p>计算公式：（自动化用例数 / 用例总数）* 100&lt;/p>
&lt;h3 id="自动化用例异常率">自动化用例异常率&lt;/h3>
&lt;p>顾名思义的指标，值得去长期关注的指标，其实就是通过率的另一种体现，失败率高可能意味着系统或产品的行为发生了更新，用例也需要进行相应的修改了。&lt;/p>
&lt;p>计算公式：每次用例执行（失败用例数 / 执行的用例数）* 100&lt;/p>
&lt;h3 id="构建异常率">构建异常率&lt;/h3>
&lt;p>用例追踪 cicd 中构建质量的指标，异常率越高可能就意味着代码质量相对较差。&lt;/p>
&lt;p>计算公式：（构建失败次数 / 构建总数）* 100&lt;/p>
&lt;h3 id="迭代中的自动化测试创建率">迭代中的自动化测试创建率&lt;/h3>
&lt;p>理论上说我们这个迭代开发的功能就应该在该迭代中被自动化用例所覆盖，迭代完成之后补作业的行为是不推荐的。这个指标可以告诉我们我们离该目标还有多少距离。&lt;/p>
&lt;p>计算公式：（本迭代中创建的用例数 / 非本迭代中创建的用例数）* 100&lt;/p>
&lt;h3 id="自动化进度">自动化进度&lt;/h3>
&lt;p>我们的目标是将所有可以自动化的用例都进行自动化，这个指标告诉我们距离这个小目标实现有还有多少路要走。&lt;/p>
&lt;p>计算公式：（当前自动化用例总数 / 可以被自动化的用例数）* 100&lt;/p></description></item><item><title>最恶心的测试用例--现实生活中的反模式测试行为</title><link>https://itest2025.pages.dev/the-worst-test-suite-testing-anti-patterns-experienced-in-real-life_23/</link><pubDate>Fri, 08 Mar 2024 09:02:07 +0800</pubDate><guid>https://itest2025.pages.dev/the-worst-test-suite-testing-anti-patterns-experienced-in-real-life_23/</guid><description>&lt;p>看到一篇吐槽测试用例的文章https://itnext.io/the-worst-test-suite-testing-anti-patterns-experienced-in-real-life-24fd13ee3ddd，觉得挺有意思的，没啥新意，不过也确实是我们会遇到的问题，或者说是我们生活的一部分。顺手翻译了一下，下面是文章内容。&lt;/p>
&lt;p>我认为这里不需要介绍，我将告诉您我遇到的最糟糕的测试套件(testsuit)是什么样子，以及它是如何运作的。&lt;/p>
&lt;p>我提前警告您，这篇文章会让您感到恶心，皮肤发痒，工程技能受挫。&lt;/p>
&lt;p>我将介绍的反模式将以恶心程度进行排名，从 🤮 表示糟糕到 🤮🤮🤮 表示完全不可原谅。
所有的反模式都很糟糕，但其中一些我从未在其他地方见过，我认为对它们进行排名将强调它们的隐蔽性。&lt;/p>
&lt;p>在阅读本文时，整个套件大约有 10 个测试文件。其中 9 个是“合理的”，第十个则是一场噩梦：它有大约 3000 行代码，总共组合了约 80 个测试用例。测试套件的约 95%都在这个文件中。&lt;/p>
&lt;p>除了测试用例之外，测试使用的所有工具都散落在这个文件中。&lt;/p>
&lt;h3 id="混合测试类型">混合测试类型&lt;/h3>
&lt;p>恶心程度: 🤮&lt;/p>
&lt;p>该文件包含了许多不同类型的测试——从单元测试到组件测试，再到端到端测试。有些测试非常难以确定测试类型——有些单元测试通过运行系统的主要部分来测试特定的小功能，而在其他情况下，通过调用系统的内部功能序列来执行主要的端到端测试。&lt;/p>
&lt;h3 id="为什么测试被编号">为什么测试被编号？&lt;/h3>
&lt;p>恶心程度: 🤮🤮🤮&lt;/p>
&lt;p>所有的测试名称都被编号了！所有的测试都采用了 TestXX_what-the-test-checks 的形式。&lt;/p>
&lt;p>这是一个重要的警示信号，当我加入这个项目时，我没有完全意识到它的重要性。大约工作了两个月后，显然这些测试之间的顺序是有问题的！这些数字是为了强制排序而存在的。这些测试是相互依赖的。&lt;/p>
&lt;p>不用说，当我移动一些测试以使那个庞大的文件变得更清晰时，我是以艰难的方式学到了这一点。&lt;/p>
&lt;p>如果我想添加一个测试，它必须与其他测试正确排序。这创造了一种荒谬的情况，我必须正确命名测试以便按正确的顺序运行。因此，如果我想让一个测试在 Test34_XXX 和 Test34_YYY 之间运行，我必须将我的测试命名为 Test341_ZZZ，以确保字典顺序正确 🤯&lt;/p>
&lt;h3 id="匿名测试">匿名测试&lt;/h3>
&lt;p>恶心程度: 🤮&lt;/p>
&lt;p>关于测试名称，还有一件事——其中一些是匿名的——这些测试并没有说明它们实际覆盖的内容，例如：test19_requirement_59_passes 或者最受欢迎的 test87_process_works。&lt;/p>
&lt;p>有些测试只有在我引入了使它们失败的更改后，我才知道它们测试的是什么，这迫使我进行调查工作以弄清楚它们在做什么。&lt;/p>
&lt;p>(这不是原文，这只是我的补充：这种情况就是测试用例的名字没有任何的意义，没人知道这个用例在做什么)&lt;/p>
&lt;h3 id="断言它们只是建议">断言？它们只是建议&lt;/h3>
&lt;p>恶心程度: 🤮🤮🤮&lt;/p>
&lt;p>一些测试没有以断言结束。您当然会问一个没有断言的测试在做什么？&lt;/p>
&lt;p>在这些测试中，测试的顶部有一条注释，指示“用户”去做某事。通常是像“转到日志文件并检查是否存在格式为 X - Y - Z 的日志消息”。&lt;/p>
&lt;p>不用说，这并没有说明日志文件在哪里，如果有多个文件（由于日志轮换配置）该怎么办。而且，在某些情况下，这些说明已经过时，日志消息自“测试”和其说明编写以来已经发生了变化。&lt;/p>
&lt;p>这些测试显然总是通过的，项目移交期间没有人告诉我这些事情，我是在添加一个功能时偶然发现有一个测试，其名称表明它测试的是我实现的过时功能的相反面。它显然通过了（因为它没有断言）。&lt;/p>
&lt;p>我删除了所有这些测试，再也没有回头看过。&lt;/p>
&lt;p>(这不是原文，这只是我的补充：这种情况就是只执行动作不做断言，基本上大部分同学入门的时候都会经历这个阶段。)&lt;/p>
&lt;h3 id="复杂和隐晦的输入">复杂和隐晦的输入&lt;/h3>
&lt;p>恶心程度: 🤮&lt;/p>
&lt;p>系统的测试输入相当复杂，大多数测试都基于一个单独的输入文件。除了“刚好足够让测试通过”的输入之外，它包含的内容相当隐晦。没有人真正记得这个测试文件是如何创建的。&lt;/p>
&lt;p>为了将每个测试带到相关状态，散布在 3000 行代码中，有一些实用方法来操作输入文件。它们都没有解释它们的作用，通常被称为 prepare_for_test78 之类的名称。&lt;/p>
&lt;p>每当我们需要更改输入时，我们都会有点儿心痛 🥲&lt;/p>
&lt;p>(这不是原文，这只是我的补充：这种情况就是准备的数据不具备可读性，没人知道这些数据是做什么的。)&lt;/p>
&lt;h3 id="拖累的共享状态">拖累的共享状态&lt;/h3>
&lt;p>🤮 恶心程度: 🤮🤮&lt;/p></description></item><item><title>QA Leader的职责是什么</title><link>https://itest2025.pages.dev/what-do-qa-leaders-do-2bbf97982ffc_22/</link><pubDate>Fri, 08 Mar 2024 09:02:05 +0800</pubDate><guid>https://itest2025.pages.dev/what-do-qa-leaders-do-2bbf97982ffc_22/</guid><description>&lt;p>很少看到有人讨论 qa leader 的职责，最近正好看到&lt;a href="https://jayateerthk.medium.com/what-does-a-qa-lead-do-2bbf97982ffc" target="_blank" rel="noopener noreffer ">一篇&lt;/a>，随手翻译了一下，供大家参考。&lt;/p>
&lt;p>在这篇文章中，我将分享我作为测试负责人的经验。&lt;/p>
&lt;p>负责人可以是测试负责人、QA 负责人、测试负责人，等等。&lt;/p>
&lt;p>虽然职位名称可能不同，但我负责领导一个测试团队。&lt;/p>
&lt;p>根据我大约 20 年的经验，我列出了负责人 QA 工程师的任务。&lt;/p>
&lt;p>负责人扮演着双重角色。&lt;/p>
&lt;p>他/她既是实际参与者，也是管理者。&lt;/p>
&lt;p>实际参与者意味着他编写测试用例，运行它们，报告错误，进行缺陷审查等等。&lt;/p>
&lt;p>这些任务都是由个人贡献者完成的。对吧？&lt;/p>
&lt;p>除此之外，负责人还承担其他任务。&lt;/p>
&lt;p>这比个人贡献者更多。他/她是产品/项目的其他利益相关者眼中的团队代表。&lt;/p>
&lt;p>现在，让我们详细了解这些任务。&lt;/p>
&lt;h3 id="作为个人贡献者的活动">作为个人贡献者的活动&lt;/h3>
&lt;p>QA 负责人的主要任务是测试。&lt;/p>
&lt;p>也就是实际参与者。通常，负责人的工作分配大约是 70-30%的时间。&lt;/p>
&lt;p>也就是 70%作为个人测试人员，30%作为负责人的活动。&lt;/p>
&lt;p>这可能因项目而异。&lt;/p>
&lt;p>好了，现在作为手动测试人员，负责人会做以下工作：&lt;/p>
&lt;ul>
&lt;li>实际测试&lt;/li>
&lt;li>编写测试用例，&lt;/li>
&lt;li>执行测试用例。&lt;/li>
&lt;li>编写自动化脚本，&lt;/li>
&lt;li>审查测试用例/自动化代码&lt;/li>
&lt;li>执行脚本&lt;/li>
&lt;li>提 bug&lt;/li>
&lt;li>文档编写&lt;/li>
&lt;/ul>
&lt;p>你可能会说这些都是由每个团队成员完成的。对吧？&lt;/p>
&lt;h3 id="作为负责人的活动">作为负责人的活动&lt;/h3>
&lt;p>现在，让我们看看负责人特定的任务。&lt;/p>
&lt;p>我还列出了负责人的责任。&lt;/p>
&lt;ul>
&lt;li>分配任务。&lt;/li>
&lt;li>进行工作量估计。&lt;/li>
&lt;li>对测试负责。&lt;/li>
&lt;li>通过支持他们来保护测试人员。&lt;/li>
&lt;li>对估算的任何延误负责。&lt;/li>
&lt;li>确保分配的任务完成。&lt;/li>
&lt;li>有点像夹心饼干 - 位于管理层和团队之间！&lt;/li>
&lt;li>准备不同的文档，如测试策略、测试计划。&lt;/li>
&lt;li>还准备测试报告、状态报告。&lt;/li>
&lt;li>与开发、IT、产品、客户支持团队进行沟通。&lt;/li>
&lt;li>帮助招聘和建立自己团队以及其他团队。&lt;/li>
&lt;li>在公司和客户层面为测试工具/项目或自动化开展多个概念验证（POC）。&lt;/li>
&lt;li>培训初级人员。&lt;/li>
&lt;li>在需要专家知识的其他项目中做出贡献，或者只是帮助他们进行指导/审查他们的自动化。因此，这是负责人提供的额外咨询服务。&lt;/li>
&lt;li>在其他人不计划休假时支持团队交付工作。也就是说，当需要时，负责人（他/她）会介入并执行任务。&lt;/li>
&lt;li>寻找改进测试流程的方法，并为团队创造心理安全环境，使他们能够做出最好的工作。&lt;/li>
&lt;li>发布计划和估算。在发布计划和估算中发表意见是 QA 负责人的主要技能和责任。&lt;/li>
&lt;li>与支持团队一起解决生产问题。&lt;/li>
&lt;li>例如，重现问题，确定延误的根本原因等等。&lt;/li>
&lt;li>设计/增强自动化框架&lt;/li>
&lt;/ul>
&lt;p>总而言之，QA 负责人领导、指导和监控测试分析、设计、实施和执行，测试程序和测试套件。&lt;/p></description></item><item><title>常用的adb命令</title><link>https://itest2025.pages.dev/adb-commands-2022_21/</link><pubDate>Fri, 08 Mar 2024 09:02:04 +0800</pubDate><guid>https://itest2025.pages.dev/adb-commands-2022_21/</guid><description>&lt;p>Android Debug Bridge 是我们比较常用的命令行工具，该工具可以在 Android 设备上执行不同的操作，例如安装或卸载应用程序、输入文本、捕获屏幕截图等，下面是一些常用的 adb 命令及使用场景。&lt;/p>
&lt;h3 id="列出连接的设备">*&lt;strong>*列出连接的设备**&lt;/strong>&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">adb devices
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>上面的命令可以列出设备的序列号和状态，如果连接了许多设备并且我们想将它们区分开来，我们可以运行带有&lt;code>-l&lt;/code>选项的命令以列出有关设备的更多详细信息。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">adb devices -l
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>此命令向我们显示设备、型号等。&lt;/p>
&lt;h3 id="安装应用程序">*&lt;strong>*安装应用程序**&lt;/strong>&lt;/h3>
&lt;p>通过提供&lt;code>.apk&lt;/code>文件的路径，可以在设备上安装应用程序&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">adb install &amp;lt;path_to_apk&amp;gt;.apk
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果连接了多个设备，直接运行上面的命令是会报错的，这时应使用&lt;code>-s&lt;/code>选项指定目标设备的序列号。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">adb -s &amp;lt;serial_number&amp;gt; install &amp;lt;path_to_apk&amp;gt;.apk
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里的&lt;code>-s&lt;/code>是一个常用选项，用于指定具体的设备，会在 adb 命令中大量使用到。&lt;/p>
&lt;h3 id="列出-package">*&lt;strong>*列出 package**&lt;/strong>&lt;/h3>
&lt;p>&lt;code>adb shell&lt;/code> 中的包管理器(package manager)工具可用于列出设备上安装的所有包&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">adb shell pm list packages
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>后面接&lt;code>grep&lt;/code>命令可以实现更为精确的过滤&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">adb shell pm list package &lt;span class="p">|&lt;/span> grep what_you_want
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="启动activity">启动*&lt;strong>*Activity**&lt;/strong>&lt;/h3>
&lt;p>可以用下面的命令来找到指定的 package 中的某个 activity&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">adb shell dumpsys package &lt;span class="p">|&lt;/span> grep &amp;lt;package_name&amp;gt; &lt;span class="p">|&lt;/span> grep Activity
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后下面的命令可以启动 activity&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">adb shell am start &amp;lt;package_name&amp;gt;/&amp;lt;activity_name&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>比如下面的命令可以启动 google map&lt;/p></description></item><item><title>亲历裁员竞赛</title><link>https://itest2025.pages.dev/job-cut-competition_20/</link><pubDate>Fri, 08 Mar 2024 09:02:02 +0800</pubDate><guid>https://itest2025.pages.dev/job-cut-competition_20/</guid><description>&lt;p>今年的年度关键词里大概率会出现裁员这个选项。国内互联网公司风风火火的进行了一整年，最近硅谷也在跟进，根据已经放出风来的口径统计的话，硅谷头部公司大概会裁员 120k 左右，超过了当年互联网泡沫时期的 107k，有分析人士指出，这只是开始而已，这个冬天，裁员的寒风会更加凛冽。&lt;/p>
&lt;h3 id="裁员的逻辑">裁员的逻辑&lt;/h3>
&lt;p>逻辑其实很简单，全球经济不好，互联网增长不及预期，而且找不到新的增长点，这样之前为增长而储备的人力就需要释放，裁员明显是一个很好的选项，所以华尔街出现了一些公司越裁员股价越涨的情况。另外一般以增长为目标的项目或产品，往往投入高收益少，也就是所谓不赚钱的业务，后面裁员的目标将非常聚焦和具体，只要不赚钱，那么走人的可能性就非常大。&lt;/p>
&lt;h3 id="被裁之后">被裁之后&lt;/h3>
&lt;p>首先被裁不是员工自身的问题，所谓的时势造英雄，整个大环境不好让所有人都很难独善其身。被裁之后的心理压力一定是巨大的，管理好情绪开始下一段旅程，这个世界还是充满着无限的可能，不要因为别人的错误而自怨自艾，记住，在这个大的时代背景和叙事框架下，你的被裁大概率是在为别人的失误买单。&lt;/p>
&lt;h3 id="风暴中心">风暴中心&lt;/h3>
&lt;p>这次我其实处在裁员的风暴中心，分享几个事情。&lt;/p>
&lt;p>我们部门这次裁员了 75%，之前坐满了一层楼，而且裁员两天后就已经冷冷清清，士气凋零。&lt;/p>
&lt;p>跟同事聊天，他说有些毕业生被裁后拿到的赔偿金额够他们生活 1 到 2 年，完全有时间去考公上岸，也算是提前离场，开启下一段人生。&lt;/p>
&lt;p>其他部门很多同事人心惶惶，下一个是不是自己无法确定，下一波什么时候到来也让人惴惴不安。&lt;/p>
&lt;p>跟国外的同事聊天，他们目前有 11 个人，下个月就只有 10 个人，因为其中 1 人收到了 np，notice period，有兴趣的同学可以查查是什么意思。他们国家今年经济其实发展的不错，但是互联网行业整体不行，其他大厂也在裁员，相当惨烈。安慰了他几句，很默契的互相留了联系方式，因为大家都不知道哪天会突然离开。&lt;/p>
&lt;p>配合另一地区的同事进行上线之前的验收，那位同学早上还在提 bug，下午的时候就忽然告诉我他要走了，刚刚被裁，last day 就是今天下班。我无话可说，安慰了几句，互相留了联系方式。感觉像是在玩狼人杀，每天都有人被刀的样子，期待明天会是一个平安夜。他走的时候问我：你觉得今年会不会有年终奖？我的回答是: I doubt。&lt;/p>
&lt;p>有的人被裁后很快就找到了工作，也有的人两个月了没什么进展，找工作这个事情因人而异，但总体看来市场上的需求明显不如几年前那么旺盛。&lt;/p>
&lt;p>有人觉得压抑，想主动离开，我总是劝留不劝走，从行业内来看，外面的世界无奈大过于精彩。&lt;/p>
&lt;p>经常有滚动裁员，量不大，比如前两天又离开了 10%左右，工作的安全感非常欠缺。&lt;/p>
&lt;h3 id="总结">总结&lt;/h3>
&lt;p>其实无话可说，安全第一，共克时艰。&lt;/p></description></item><item><title>用python实现简单的版本号生成工具</title><link>https://itest2025.pages.dev/how-to-use-python-generate-a-version-number-2023_19/</link><pubDate>Fri, 08 Mar 2024 09:02:01 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-use-python-generate-a-version-number-2023_19/</guid><description>&lt;p>我们发布比较频繁，每次发布都需要从 release 分支打 1 个 tag，不过可能是因为年纪大了的缘故吧，尽管借鉴了一些版本号的制定规则，不过每次我都记不太住，需要翻文档去重新复习，不如把这步自动化一下，为未来的自己节约一点时间，另外有了工具就好统一规则，后面所有相关的项目都可以用同一套规则来生成版本号，去掉了人工对齐的成本，变相提升了效率。&lt;/p>
&lt;h3 id="版本号规则">版本号规则&lt;/h3>
&lt;p>我们的版本号大概长这个样子&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">v1.00.0-hotfix-20221111-1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">版本号 发布类型 发布时间 第几次发布
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>版本号规则: 大版本 1 位 + 小版本 2 位 + patch 版本号 1，比如 v1.00.0 表示大版本是 1，小版本是 0，patch 版本是 0&lt;/li>
&lt;li>发布类型：普通发布/hotfix/adhoc(临时版本)&lt;/li>
&lt;/ul>
&lt;h3 id="设计">设计&lt;/h3>
&lt;p>首先不考虑写页面做系统，其实用 vue 随便写个单页应用是可以很快搞定的，不过没那个必要，用命令行工具就好了，简单快速，而且生成了版本号之后可以调用 githlab(我们公司用私有化部署的版本)api 来自动打 tag，在微服务化当道的今天，手动为每次发布的所有 repo 打版本号本身就不是一个很好的体验。命令行工具在这方面比单页应用更具优势一些。&lt;/p>
&lt;p>另外交互式的命令行可以省去很多参数 validation 的工作量，这也应该是考虑的。随便搜索了一下，发现了 1 个名为 inquirer 的库可以很好的满足我的需求。&lt;/p>
&lt;h3 id="代码实现">代码实现&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">inquirer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">datetime&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">datetime&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">STR_MAP&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s1">&amp;#39;regular&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s1">&amp;#39;hotfix&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;-hotfix&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s1">&amp;#39;adhoc&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;-adhoc&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">build_version&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">options&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">release_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">STR_MAP&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">options&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;release_type&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">date&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">now&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strftime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;%Y%m&lt;/span>&lt;span class="si">%d&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;v&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">options&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;version&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}{&lt;/span>&lt;span class="n">release_type&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">-&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">date&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">-&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">options&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;seq&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">questions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">inquirer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;version&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Please enter a version, for sample 1.00.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;2.00.0&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">inquirer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Checkbox&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="s2">&amp;#34;release_type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Please select a release type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="n">choices&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;regular&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;hotfix&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;adhoc&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;regular&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">inquirer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;seq&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;please enter the sequence&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">inquirer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Confirm&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="s2">&amp;#34;correct&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;This will generate a tag name. Continue?&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="n">answers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">inquirer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">prompt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">questions&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">if&lt;/span> &lt;span class="n">answers&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;correct&amp;#39;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="n">tag_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">build_version&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">answers&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tag_name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;nothing to do&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>整体流程非常的简单&lt;/p></description></item><item><title>python requests的替代者？httpx初体验</title><link>https://itest2025.pages.dev/how-to-use-httpx_17/</link><pubDate>Fri, 08 Mar 2024 09:01:58 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-use-httpx_17/</guid><description>&lt;p>python 的 requests 库由于其使用简单，文档丰富成为了很多人在发送 http 请求时候的优选选择。前几天看到了一个类似的实现 httpx，在这里简单使用体验一下，顺便简单分享一下体验心得。&lt;/p>
&lt;p>相比较 requests，httpx 支持 sync 和 async 的 API，支持 http1.1 和 http2。httpx 尽最大努力兼容 requests 的 API，这样一来用户从 requests 转换到 httpx 的成本就相对较为低廉了。&lt;/p>
&lt;h3 id="基本-api">基本 API&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="nn">httpx&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">httpx&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;https://www.example.org/&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Response&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">200&lt;/span> &lt;span class="n">OK&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="mi">200&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;content-type&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">&amp;#39;text/html; charset=UTF-8&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">&amp;#39;&amp;lt;!doctype html&amp;gt;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s1">&amp;lt;html&amp;gt;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s1">&amp;lt;head&amp;gt;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s1">&amp;lt;title&amp;gt;Example Domain&amp;lt;/title&amp;gt;...&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>简单扫一圈，满眼都是 requests 当年的样子。下面是 requests 的 API，大家来找茬，看看哪里不一样。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;https://api.github.com/user&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">auth&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;user&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;pass&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="mi">200&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">headers&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;content-type&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">&amp;#39;application/json; charset=utf8&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encoding&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">&amp;#39;{&amp;#34;type&amp;#34;:&amp;#34;User&amp;#34;...&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;private_gists&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">419&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;total_private_repos&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">77&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">...&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>不能说非常相似，只能说是一模一样。&lt;/p>
&lt;h3 id="httpx-client">httpx client&lt;/h3>
&lt;p>requests 为一组 http 请求提供了 session 对象来进行统一设置和管理，httpx 则相应的提供了 client 对象。我们来对比一下使用方式先。&lt;/p></description></item><item><title>如何优化selenium webdriver的执行速度</title><link>https://itest2025.pages.dev/how-to-speed-up-selenium-execuation-time_16/</link><pubDate>Fri, 08 Mar 2024 09:01:57 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-speed-up-selenium-execuation-time_16/</guid><description>&lt;p>让自动化测试脚本正常工作只是自动化测试的第一步，由于自动化脚本会经常执行并更新，因此测试脚本需要&lt;/p>
&lt;ul>
&lt;li>可以快速执行&lt;/li>
&lt;li>容易维护&lt;/li>
&lt;li>容易阅读&lt;/li>
&lt;/ul>
&lt;p>本文会提供一些让 selenium 自动化脚本运行的更快的技巧。&lt;/p>
&lt;h3 id="在-page_source-中断言-text-比直接使用-text-属性断言要快">在 page_source 中断言 text 比直接使用 text 属性断言要快&lt;/h3>
&lt;p>我们经常会需要断言页面中的某个部分包含一些具体的文本，下面的语句的输出结果是相同的&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">page_source&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_element&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:tag_name&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="err">‘&lt;/span>&lt;span class="n">body&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>不过对于第二条语句来说，selenium 需要去分析页面的结构，最后再找到对应的元素并输入结果，这显然是需要花费时间的。如果页面比较小的化，那么二者的差距可能不大，不过对于大的页面来说，第一条语句速度明显会更快一些。&lt;/p>
&lt;p>使用 page text 的情况&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_element&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:tag_name&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;body&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to&lt;/span> &lt;span class="kp">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;platform- and language-neutral wire protocol&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>使用 page source 的情况&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">page_source&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to&lt;/span> &lt;span class="kp">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;platform- and language-neutral wire protocol&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>来看一下差距&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="no">Method&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="no">Search&lt;/span> &lt;span class="n">whole&lt;/span> &lt;span class="n">document&lt;/span> &lt;span class="n">text&lt;/span> &lt;span class="n">took&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mi">823076&lt;/span> &lt;span class="n">seconds&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="no">Method&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="no">Search&lt;/span> &lt;span class="n">whole&lt;/span> &lt;span class="n">document&lt;/span> &lt;span class="no">HTML&lt;/span> &lt;span class="n">took&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="mo">03&lt;/span>&lt;span class="mi">9573&lt;/span> &lt;span class="n">seconds&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>当然两者的使用场景是不太相同的，不过我们这里只关注性能，显然 page source 要更快速一些。&lt;/p>
&lt;h3 id="元素越具体获取-text-的速度越快">元素越具体，获取 text 的速度越快&lt;/h3>
&lt;p>根据经验，我们可以通过缩小更具体的 Web 控件的范围来节省执行时间。下面的两个断言语句在很大程度上实现了相同的功能，但在执行时间上有很大的不同。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_element&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:tag_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="err">“&lt;/span>&lt;span class="n">body&lt;/span>&lt;span class="err">”&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to&lt;/span> &lt;span class="kp">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="err">“&lt;/span>&lt;span class="n">language&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">neutral&lt;/span> &lt;span class="n">wire&lt;/span>&lt;span class="err">”&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这条语句的执行时间是 0.93s&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_element&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="err">“&lt;/span>&lt;span class="n">abstract&lt;/span>&lt;span class="err">”&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to&lt;/span> &lt;span class="kp">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="err">“&lt;/span>&lt;span class="n">language&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">neutral&lt;/span> &lt;span class="n">wire&lt;/span>&lt;span class="err">”&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个断言只执行了 0.02s&lt;/p>
&lt;p>很明显，第 2 个断言除了在执行速度上更快之外，断言也更加精确，更容易理解。&lt;/p>
&lt;h3 id="使用变量去缓存没有变化的元素">使用变量去缓存没有变化的元素&lt;/h3>
&lt;p>我经常看到有人编写如下测试来检查页面上的多个文本。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">navigate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">site_url&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;/WebDriverStandard.html&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_element&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:tag_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;body&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to&lt;/span> &lt;span class="kp">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Firefox&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_element&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ss">:tag_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;body&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="s2">&amp;#34;).text).to include(&amp;#34;&lt;/span>&lt;span class="n">chrome&lt;/span>&lt;span class="s2">&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">expect(driver.find_element(:tag_name, &amp;#34;&lt;/span>&lt;span class="n">body&lt;/span>&lt;span class="s2">&amp;#34;).text).to include(&amp;#34;&lt;/span>&lt;span class="no">W3C&lt;/span>&lt;span class="s2">&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>执行时间 2.35s&lt;/p>
&lt;p>上述三个测试语句非常低效，因为每个测试语句都调用&lt;code>driver.find_element(:tag_name, 'body').text&lt;/code>，当网页很大时，这可能是费时费力的工作。&lt;/p></description></item><item><title>自动化测试的9大规则</title><link>https://itest2025.pages.dev/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E7%9A%849%E5%A4%A7%E8%A7%84%E5%88%99_15/</link><pubDate>Fri, 08 Mar 2024 09:01:55 +0800</pubDate><guid>https://itest2025.pages.dev/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E7%9A%849%E5%A4%A7%E8%A7%84%E5%88%99_15/</guid><description>&lt;p>看到了一篇不错的关于自动化测试文章，简单翻译了一下。&lt;/p>
&lt;p>在我的软件测试生涯中，我听到了许多说法，如 &amp;ldquo;我们不需要团队中的测试人员！&amp;ldquo;到 &amp;ldquo;我们需要用 100%的覆盖率来自动化一切！&amp;quot;。每当我听到这些时，我都会摇头。我打赌你知道为什么。这两个极端都是错误的，因为我认为每个软件开发团队都应该有一个测试人员在其中。当然，100%的测试自动化覆盖率也是不可能的，也是没有效率和要求的。&lt;/p>
&lt;p>在这篇文章中，我将分享我在测试自动化方面学到的经验，这些经验是我在软件测试行业工作了 13 年以上的艰苦历程中总结出来的。我将分享我在过去几年中对测试自动化和要避免的陷阱的详细看法。我将分享我在测试自动化方面的注意事项，以帮助你不犯同样的错误。&lt;/p>
&lt;h2 id="应该去做的事情">应该去做的事情&lt;/h2>
&lt;h3 id="雇用合适的人">雇用合适的人&lt;/h3>
&lt;p>雇用具有软件工程技能的人是必须的。没有正确的技能，你的测试自动化会失败。因此，需要在招聘过程中投入大量时间，以确定适合你的需求的人。我建议你写下你想通过新员工实现的目标。一旦目标明确，就从中引申出具体的技能。&lt;/p>
&lt;p>现在是时候写一份朗朗上口、不太长的职位描述，并说明所需的核心技能。不要在长长的技能清单中过度使用技术术语和要求。这可能会对潜在的雇员产生负面的影响。&lt;/p>
&lt;p>在面试过程中与人交谈时，技术能力是必须的。你可以在编码挑战或临时进行的代码审查中验证它们。然而，不要只看技术能力。检查是否适合团队或公司，以及在与人合作、沟通和解决问题时的正确心态也非常重要。&lt;/p>
&lt;p>一旦你雇用了合适的人从事你的自动化工作，相信他们会做正确的事情。给他们尽可能多的上下文，让他们与公司的其他角色一起，如开发人员、设计师和产品人员。在最好的情况下，每个开发团队都有一个测试工程师加入。&lt;/p>
&lt;h3 id="在寻找正确的测试自动化工具方面花点时间">在寻找正确的测试自动化工具方面花点时间&lt;/h3>
&lt;p>在公司拥有正确的测试自动化人员是一个很大的优势。下一个挑战是找到并选择正确的测试自动化工具。而这需要时间。在选择工具之前，与所有参与方坐下来，定义测试自动化的目标。在这个过程中，尽量提出尽可能多的关于工具、自身环境和将要建立的产品的问题。在这些问题的帮助下，你将创建一个长长的选择标准列表，以帮助你找到适合你需要的测试自动化工具。&lt;/p>
&lt;p>该列表可以有以下标准。&lt;/p>
&lt;ul>
&lt;li>该工具是否支持不同的编程语言？&lt;/li>
&lt;li>该工具是否能够针对不同的操作系统进行自动化？&lt;/li>
&lt;li>该工具是否支持 CI/CD？&lt;/li>
&lt;li>该工具是否提供报告功能？&lt;/li>
&lt;li>该工具是否有很好的文档，是否提供支持或有一个伟大的社区或干爹？&lt;/li>
&lt;li>该工具是否满足预算要求？&lt;/li>
&lt;li>配置该工具的复杂程度如何？&lt;/li>
&lt;li>该工具是否提供灵活的测试执行能力，例如在不同的操作系统或环境下？&lt;/li>
&lt;/ul>
&lt;p>上面的列表只包含了一些可能的问题，以找到合适的工具。因此，需要投入足够的时间来找到适合你需求和环境的工具。也可能是你需要一个以上的自动化工具用于你的技术架构中的不同层，或者是除了测试自动化工具之外，还需要一个测试自动化管理平台。那么你应该分别对每个工具进行选择。&lt;/p>
&lt;h3 id="轻装上阵">轻装上阵&lt;/h3>
&lt;p>一旦你确定了适合你的需求的自动化工具，现在是在你的团队或公司内进行配置和实施的时候了。在最初的几天或几周内，试着学习所有关于这个工具和它的功能。一旦你和你的团队熟悉了它，就开始进行第一个自动化方案的工作。但开始时一定要简单! 看看那些容易自动化的产品功能，并帮助团队专注于质量的其他部分。当这个测试是强大的并提供可靠的结果时，就可以进入下一个自动化阶段。开始在更复杂的情况下工作，看看结果。我们也建议在产品的某些部分进行测试，这些部分每天或每周都不会改变。通常一个产品的核心功能是自动化的完美起点。&lt;/p>
&lt;h3 id="让开发人员参与到自动化过程中来">让开发人员参与到自动化过程中来&lt;/h3>
&lt;p>编写测试自动化代码并不容易。**它的复杂性与编写生产代码的复杂性相同，这一点永远不应该被低估。**通常一个软件开发团队有 3-5 个软件开发人员和至少 1 个软件测试工程师。这个比例是可以的。然而，测试工程师很难赶上所有对产品进行的代码修改。他/她不仅需要手动测试软件，而且还需要编写复杂的自动化。这一切对团队中的一个人来说是不可能做到的。因此，软件开发人员也必须负责编写测试自动化代码。不仅在单元测试层面，其他层次的自动化测试也可以涉猎。&lt;/p>
&lt;p>每个团队都应该坐在一起讨论自动化过程。每个人都必须清楚，产品的质量是每个人的责任。这种心态要求团队中的每个人都支持自动化。如果不是这样，你的自动化过程很可能会失败。&lt;/p>
&lt;h3 id="在-cicd-上投资时间">在 ci/cd 上投资时间&lt;/h3>
&lt;p>与工具选择的时间投资类似。一个软件开发团队必须在 cicd 的配置上投入足够的时间。pipeline 的配置方式必须支持开发团队的需求。例如，在每次提交时运行，在拉代码时运行或在夜间执行整个测试套件。&lt;/p>
&lt;p>同样重要的是，定义哪一层的哪些测试应该在什么时候被自动化。在最好的情况下，团队正在制定一个 cicd 策略。一旦 pipeline 策略被实施，重要的是结果对团队中的每个人都是透明的，甚至更好的是对公司中的每个人都是透明的，例如用一个 dashboard。&lt;/p>
&lt;p>cicd 工作和投入的时间不应该被低估。根据项目或产品的规模，这可能是一个人甚至整个团队的全职工作。&lt;/p>
&lt;h2 id="不应该做的事情">不应该做的事情&lt;/h2>
&lt;h3 id="不要因为一个工具被追捧就选择它">不要因为一个工具被追捧就选择它&lt;/h3>
&lt;p>我的不做清单的第一点是，当选择一个新的测试自动化工具时，不要因为它在软件开发或测试社区中被炒作而盲目地选择一个工具。如果你真的很幸运，这个工具可能对你有用，但在大多数情况下，你会用它失败。正如我在前面提到的，选择一个工具需要时间。如果没有在选择过程中的这种投资，从长远来看，你可能会损失更多的钱，因为在产品开发过程中的某个时刻，你会发现这个工具不支持某个特殊功能，或者不能按照你需要的方式进行扩展。&lt;/p>
&lt;p>在选择工具时，最好检查一下目前什么工具被炒得很热，为什么？也许这种追捧有一个合理的理由，你可能会从中受益。跟上新的工具，并与社区保持联系，获得最新的消息，这总是好的。&lt;/p>
&lt;h3 id="不要试图将一切都自动化">不要试图将一切都自动化&lt;/h3>
&lt;p>我在前面已经提到了这一点。每当你的团队或公司里有人告诉你要把所有事情都自动化的时候，你就装聋作哑好了。这个人很可能是一个从未从事过软件开发的人，或者对测试自动化毫无概念。我见过一些没有经验的人，他们是自动化的新手，试图把所有的东西都自动化，但他们很快就发现，这是不可能的。当你看到有这种观点的人时，和他们谈谈，并解释其消极的一面。&lt;/p>
&lt;p>你应该做的是看一下你的产品和代码架构，并确定你的应用程序的关键部分。然后花时间为这些部分创建一个自动化战略。&lt;/p>
&lt;p>有了明确的重点和策略，少量的自动化方案可以对产品的整体质量产生更大的影响。&lt;/p>
&lt;h3 id="不要太早实现自动化">不要太早实现自动化&lt;/h3>
&lt;p>在产品开发团队中开始使用自动化时，一个典型的错误是开始的太早了。如果团队正在开发一个全新的功能或产品，真的很有可能功能会改变。在这种情况下，开始实施自动化是没有意义的。投入的时间可能会被浪费掉。相反，团队应该从基础开始。例如，建立 CI/CD pipeline，考虑功能所需的数据结构。从中得出一个测试数据策略，也许准备测试数据生成的脚本。&lt;/p>
&lt;p>一旦功能越来越成熟，就慢慢开始自动化。我建议与产品所有者保持密切的关系，以了解即将到来的产品功能和变化。使用这些信息来创建你的自动化策略。&lt;/p>
&lt;h3 id="永远不要用自动化来取代人工测试">永远不要用自动化来取代人工测试&lt;/h3>
&lt;p>最后但并非最不重要的是，&lt;strong>不要用自动化来取代人工测试&lt;/strong>！人工测试是自动化的重要组成部分。在产品的开发过程中，手工测试是如此强大的一部分，它永远不应该被机器取代。为什么呢？嗯，自动化测试将由机器/计算机执行。机器所做的正是测试自动化代码中的内容，仅此而已。这并不坏，我们都知道这是有用的信息，但软件同学进行手工测试时，会发现更多的问题。首先，测试人员的行为就像一个用户。使用产品的用户会使用它，例如用鼠标、键盘或用手指在使用产品时使用他/她的所有感官。在大多数情况下，手工测试会发现没有人想到会发生的问题。&lt;/p>
&lt;p>因此，测试自动化是人工测试的重要补充。&lt;/p>
&lt;h3 id="总结">总结&lt;/h3>
&lt;p>正如你在这篇文章中所看到的，一个人在从事测试自动化时可能会犯很多错误。在雇佣自动化专家、寻找合适的工具和寻找合适的时机开始自动化时，有许多话题需要记住。&lt;/p></description></item><item><title>逃离云服务？为什么大家在讨论避免使用云服务</title><link>https://itest2025.pages.dev/why-you-should-not-use-cloud-service-in-2023_14/</link><pubDate>Fri, 08 Mar 2024 09:01:54 +0800</pubDate><guid>https://itest2025.pages.dev/why-you-should-not-use-cloud-service-in-2023_14/</guid><description>&lt;p>之前 ruby on rails 的创始人 dhh 在 twitter 上宣布由于云服务的价格过于昂贵，他们决定自己买服务器自建服务以节约成本。在今年 9 月 15 号的&lt;a href="https://world.hey.com/dhh/our-cloud-exit-has-already-yielded-1m-year-in-savings-db358dea" target="_blank" rel="noopener noreffer ">Our cloud exit has already yielded $1m/year in savings&lt;/a>这篇博客中,dhh 宣布他们公司&lt;a href="https://37signals.com/" target="_blank" rel="noopener noreffer ">37 signals&lt;/a>目前云服务的月花费从 18 万美金降到了 8 万美金，也就是 1 年可以节约 100 万美金。10 月 27 号，twitter 也宣布他们的去云化项目为公司降低了 60%的成本。在这之后关于去云化的讨论逐渐热门起来，目前看来一些公司确实在认真思考从云端逃离的可能性。&lt;/p>
&lt;h3 id="为什么大家都在逃离云服务">为什么大家都在逃离云服务&lt;/h3>
&lt;p>我觉得核心问题还是全球经济衰退，除了裁员节省成本之外，少用或者甚至不用云服务可以带来可观的成本降低。讽刺的是当初云服务的兴起就跟成本节约相关，如今屠龙的少年却成了恶龙，在被人们慢慢抛弃。&lt;/p>
&lt;p>在很多年前，互联网应用运维难度大，机器带宽等成本相对较高，资源整合型的云服务可以显著为小型团队节约成本，降低了构建产品的门槛。但这么多年过去了，硬件成本逐步走低，而云服务的定价却变化不大，高溢价就降低了云服务的性价比，从而会影响大家的理性选择。&lt;/p>
&lt;p>初创企业往往是云服务利润的贡献者，这几年大量的初创企业倒闭导致云服务商失去了一大块利润来源，这也在一定程度上降低了云服务厂商降价的积极性。&lt;/p>
&lt;p>简单来说云服务的存量用户嫌贵，增量用户变少，因此整个市场就显得萧条。&lt;/p>
&lt;h3 id="我们是否真的需要云服务">我们是否真的需要云服务&lt;/h3>
&lt;p>对于大公司来说，云服务确实是必须的，因为规模化带来的巨大成本优势是非常有吸引力的。比如大公司扩张的过程中可以不需要去某些海外市场建机房攒带宽，直接买云服务就可以了，这些服务对于大公司业务来说是一个积极的补充，因此大公司对云的使用其实是混合云为主，自建机房加上云服务辅助，这样就可以尽可能的降低边际成本。&lt;/p>
&lt;p>对于初创公司来说，用云服务其实是最快产品化的选择，很多时候如果你拿了投资人的钱的话，你是没得选的，必须上云，然后快速进行产品迭代。如果你产品的最初版本就在云端运行而且运行的很好，那么你是没有动力去买服务器然后托管的。不过随着业务的发展，从成本角度考虑，把服务从纯云端切回混合部署的方式可能会是一个不错的选择，首先可以降低成本，其次可以把核心的数据资产攥在自己手上，从经济上和安全性上考量，混合云的方式都有存在的价值。&lt;/p>
&lt;p>所以我认为云服务是必须的，这是一种资源整合带来的规模化优势的体现，很多场景下确实可以降低成本，但完全依赖云服务则不一定可取。&lt;/p>
&lt;h3 id="云服务的未来会怎么样">云服务的未来会怎么样&lt;/h3>
&lt;p>既然大家都在逃离，那么是不是云服务和云计算的前景就变得暗淡了呢？&lt;/p>
&lt;p>不一定，我觉得云服务可能跟经济一样有其周期性，如果云服务持续的改进和创新，那么这个周期性可能就会显得很明显。比如经济好的时候，大家都选择创业，大公司选择开拓市场，那么云服务厂商会跟着一同繁荣。但如果经济下行，创业潮过去，大公司选择降本增效，那么云服务自然就会受到冷遇。&lt;/p>
&lt;p>云服务的本质决定了这是一种相对来说高效的做法，理论上讲长远看还是有市场的，不过云服务还是需要逐步演进的，比如为解决气候变化问题逐步淘汰低能效的机器，使用清洁能源来替换化石能源等。&lt;/p>
&lt;p>逃离可能只是经济发展放缓带来的暂时性问题，一旦云服务迭代跟上了，降低了成本和定价，提升了服务的稳定性，流失的用户是有回归的可能性的。&lt;/p></description></item><item><title>2023年你还能看到希望么</title><link>https://itest2025.pages.dev/2023-sucks-for-testers_13/</link><pubDate>Fri, 08 Mar 2024 09:01:52 +0800</pubDate><guid>https://itest2025.pages.dev/2023-sucks-for-testers_13/</guid><description>&lt;p>对很多人来说 2022 年可能是非常糟心的一年，我们有理由去期待 2023 年会触底反弹，经济环境变好，收入提高，内卷的情况得以缓解。不过从目前得到的消息来看，一切可能没有那么乐观。&lt;/p>
&lt;h3 id="关于裁员">关于裁员&lt;/h3>
&lt;p>最近跟字节的朋友聊天，消息可能不是特别准确，不过字节大概已经在开始裁员了，飞书的目标是 10%，其他业务不清楚。根据字节的体量估计的话，有可能会涉及到万人级别。&lt;/p>
&lt;p>跟腾讯的朋友聊天，他们表示已经收到消息，明年裁员会继续，不过手段会多样化，基本上会通过不续约和低绩效的方式进行淘汰。&lt;/p>
&lt;p>我们公司是二线厂，今年下半年一直在裁员，年底的时候消停了一些，不过估计明年还是会继续的。&lt;/p>
&lt;p>国外的话亚马逊开始裁员，估计会回到疫情之前的人员规模。&lt;/p>
&lt;p>可以看到 2023 年互联网的企业的主旋律还是裁员，保住饭碗苟下去应该是大多数人的明智之举。&lt;/p>
&lt;h3 id="关于换工作">关于换工作&lt;/h3>
&lt;p>跟猎头聊了一下，字节国内的 hc 应该基本上冻结了，我们公司只裁人不招人，腾讯那边不清楚，少量的 hc 进行血液流通还是会有的。其他厂没有了解过，不过根据之前被裁的同事反馈 2022 下半年还是能找到工作的，机会不多，但不是完全没有。很多人去了字节，算是一个稳定的退路，但 2023 年估计字节也不好进去，除非降低预期往二三线厂看看，不然退路方面的可选择面还是比较少的。&lt;/p>
&lt;p>2023 也不是不能换工作，比如换个行业之类的也是比较不错的思路，不过还是不建议裸辞，找到了再换可能更好一点。&lt;/p>
&lt;p>另外这几年创业的热潮似乎已经冷却，新的工作机会很难创造出来，去初创企业搏一搏的机会可能都会很少了。&lt;/p>
&lt;h3 id="关于年终奖">关于年终奖&lt;/h3>
&lt;p>最近腾讯的朋友们表示已经在吹风了，说今年的年终奖会比之前少很多，其他厂不知道怎么样，不过整个互联网行业的年终收益估计都会有所折扣吧。我们公司也是年终奖大幅减少，并且取消了年度涨薪。&lt;/p>
&lt;p>如果是合同规定的年终奖，比如合同规定了 15 薪，那么年底就是 3 薪，这是毋庸置疑的；不过如果合同规定的是浮动年终奖，那么可以讨论的空间可能就不大了。&lt;/p>
&lt;h3 id="关于希望">关于希望&lt;/h3>
&lt;p>经济滞胀，收入锐减，消费降级，预期降低可能会是 2023 年的主旋律，按道理说在这种大环境下，大家退一步躺平，心平气和，看破红尘反而最有可能海阔天空。但现实的情况是，大家都不想失业，于是越来越卷，越来越焦虑。&lt;/p>
&lt;p>希望还是有的，不过是从长期来看。对于 2023，也许不能期待太多，只能把目光放长远，看看 3 年 5 年之后，活在当下但心在远方，坚持下去吧。&lt;/p></description></item><item><title>测试人员想涨薪？换份新工作吧</title><link>https://itest2025.pages.dev/want-to-increase-your-salary-as-a-tester-look-for-a-new-job_11/</link><pubDate>Fri, 08 Mar 2024 09:01:49 +0800</pubDate><guid>https://itest2025.pages.dev/want-to-increase-your-salary-as-a-tester-look-for-a-new-job_11/</guid><description>&lt;p>很少看到有讨论测试人员薪资的文章，当然了在一些论坛里这个话题似乎是主旋律，经久不衰。但写小作文去讨论这件事的测试人员还是不多的。今天看到一篇名为&lt;a href="https://dev-tester.com/want-to-increase-your-salary-as-a-tester-look-for-a-new-job" target="_blank" rel="noopener noreffer ">Want to Increase Your Salary as a Tester? Look for a New Job&lt;/a>的文章，说的很实在，国内国外都适用，翻译一下，供大家参考。&lt;/p>
&lt;p>我们大多数人都不介意获得加薪,尤其是考虑到通货膨胀如何影响世界大部分地区。一切似乎都在变得越来越贵,我们的购买力似乎每周都在萎缩。大多数公司每年都会进行渐进式的加薪,但这仍无法跟上现实生活中的开支。这些略微的加薪也难以反映出你全年的改进。&lt;/p>
&lt;p>你想要的加薪应当与你创造的价值和职业发展成正比。但是你该如何获得与你当前技能水平相称的加薪呢?你可以尝试改进现有技能以获得 QA 经理的晋升,或者从手工测试转向自动化测试。不幸的是,有些工作环境没有职位或能力来晋升任何人。有些公司根本不会从内部晋升任何人。无论你付出多少努力,这些决定都不在你的控制范围内。&lt;/p>
&lt;p>无论你是在公司工作了一两年的手工测试人员,还是拥有丰富经验的软件开发测试工程师,&lt;strong>获得加薪的最佳方式是找到一份新工作&lt;/strong>。&lt;/p>
&lt;h3 id="作为测试人员跳槽可以获得回报">作为测试人员跳槽可以获得回报&lt;/h3>
&lt;p>证据表明,与留在原职位获得的平均加薪相比,跳槽可以带来更高的工资增长。在一份报告中,工资和人力资源巨头 ADP 在 2021 年收集了 1800 万工人的数据,并指出换工作的人工资增长了 5.8%,而保留工作的工人只增长了 3.1%。在日本,人力资源公司 Recruit 今年也得到了同样的结论:换工作的人工资增加了 3%,而留在原地的人只增加了 1.2%。&lt;/p>
&lt;p>你或者你认识的人可能都有过有意跳槽获得大幅加薪的经历。我的个人经历也印证了这一点,包括我从一家公司跳槽到另一家公司时,工资增加了 50%(后来由于内部晋升又增加了 33%)。仅仅通过跳槽到一家新公司就将薪水翻倍的故事并不罕见。&lt;/p>
&lt;p>基本工资的大幅增加不是跳槽带来的唯一好处。你还应该考虑到与新角色相关的额外福利,比如灵活的工作时间、远程工作的能力和更多的假期时间。根据你所在的国家和组织,这些额外福利可以有很大差异。例如,硅谷的一家创业公司可能会在加薪的基础上提供股权。这许多因素不会直接影响你获得的金钱数额,但从长远来看可以极大地提高职业满意度。&lt;/p>
&lt;h3 id="跳槽时金钱并非唯一考量">跳槽时金钱并非唯一考量&lt;/h3>
&lt;p>当然,这不全是关于加薪。我们都需要金钱,但有时仅仅为了短期内的加薪而损害你的长期职业发展是不明智的。你需要看得更长远,确保你正在做出正确的决定来增加你未来多年的挣钱能力。对于职业生涯还在早期的测试人员来说,这一步尤其值得注意。&lt;/p>
&lt;p>密切关注你当前角色以及未来职位中的潜在停滞是很重要的。在我职业生涯早期,我离开了一份工作,却进入了另一份工作内容几乎相同的角色。说实话,这导致了停滞和懒惰。我最初跳槽时确实多赚了一点钱,但专业发展的缺乏可能使我失去的要比如果我选择能将我带出舒适区的公司所能获得的要多得多。&lt;/p>
&lt;p>当寻找新工作时,你需要探索的主要领域之一是拓展技能的能力。作为一名软件测试人员,你可能已经适应了当前角色中使用的某些工具和遵循的特定流程。找到可以让你接触新方法和新技术的不同职位将大大提高你在 QA 中的价值,并直接影响你未来测试职业生涯的挣钱能力。&lt;/p>
&lt;h3 id="在充满不确定性的时期找到测试工作">在充满不确定性的时期找到测试工作&lt;/h3>
&lt;p>如今,似乎大多数大型和小型科技公司每周都在裁员。仅 2022 年,像谷歌、Meta 和亚马逊这样的行业巨头就解雇了数万名员工。许多这些公司也暂停了招聘,因此我们现在有更少的选择来寻找新工作。由于市场上涌入大量优秀的被裁员工,任何可用职位的竞争可能也会变得更加激烈。&lt;/p>
&lt;p>仅仅发送一个充满时髦词汇的简历并期待接到电话已经不够了。你必须展示你的技能、经验和成为专业 QA 的渴望。在 LinkedIn 或 Ministry of Testing 论坛等地方扩大你的专业网络可以开启新的机会之门。写博客和上传视频是展示你知识并展现你对行业热情的绝佳方式。有许多不同的方式可以使你脱颖而出,许多有价值的公司看重这种主动性。&lt;/p>
&lt;p>尽管我们目前似乎处于低迷期,但情况并非大多数主要出版物所描述的那么令人绝望。如果你仔细寻找,你仍会发现许多受人尊敬和有趣的组织在寻找像你这样高技能测试人员加入他们的团队。当然,找到这些职位现在比以前更具挑战性,你可能会收到无数拒绝或“鬼影”。我知道这对心理是一个大负担。但这并非不可能克服。低迷期是暂时的,在跳槽之前,你可以开始为更光明的未来做准备。&lt;/p>
&lt;h3 id="总结">总结&lt;/h3>
&lt;p>在同一家公司工作几十年直到退休的日子早已一去不复返了。大多数现代公司都知道这一点,但暗自希望你尽管缺乏经济和非经济激励但还是继续留下来。作为一名软件测试人员,主导你的职业和财务未来取决于你自己。寻找一份新工作可能是实现这些目标的最佳策略。&lt;/p>
&lt;p>离开当前角色去别处找到一份新工作通常伴随着比等待可能永远不会到来的潜在加薪更高的加薪。你还可能获得更灵活的工作环境等额外福利,这可能极大地改善你的工作与生活平衡。除了金钱之外,新角色还带来了新职责,为你未来带来更大的发展机会。&lt;/p>
&lt;p>这篇文章并不是说每个读者都需要尽快找到一份新工作。你可能对当前的团队、薪水和职责感到满意。如果是这样,请珍惜你拥有的一切。但是许多人会从一个新环境中受益,无论是为了金钱还是职业发展。目前的求职市场有些紧张,但请开始把自己放在那里——你永远不知道能发现什么。走出舒适区是成为更好测试人员的绝佳方式。&lt;/p></description></item><item><title>Faceboook创建了一个可以自动修复bug的工具</title><link>https://itest2025.pages.dev/auto-fix-bug-tools-released-by-facebook_9/</link><pubDate>Fri, 08 Mar 2024 09:01:46 +0800</pubDate><guid>https://itest2025.pages.dev/auto-fix-bug-tools-released-by-facebook_9/</guid><description>&lt;p>最近 Facebook 的工程师们撰写了一份文档，解释了他们如何编写了一个可以自动修复 bug 的工具。在这篇论文中，他们介绍了 𝗦𝗔𝗣𝗙𝗜𝗫，这是一个自动检测和修复软件 bug 的工具。该工具对 Facebook App Family 中的六个重要安卓应用程序提供了修复建议，这些应用程序包括 Facebook、Messenger、Instagram、FBLite、Workplace 和 Workchat。&lt;/p>
&lt;h2 id="工作原理">工作原理&lt;/h2>
&lt;p>&lt;strong>步骤 1：检测崩溃 -&lt;/strong> 另一个名为 𝗦𝗮𝗽𝗶𝗲𝗻𝘇 的工具用于查找应用程序崩溃情况。当 Sapienz 识别出崩溃时，它会被记录到数据库中。&lt;/p>
&lt;p>&lt;strong>步骤 2：确定问题 -&lt;/strong> SAPFIX 可以准确定位导致问题的代码行。它首先检查崩溃是否可重现。如果不可重现，崩溃将被丢弃。它使用一种称为&amp;quot;基于频谱的错误定位&amp;quot;的技术来确定最可能导致崩溃的代码行。&lt;/p>
&lt;p>&lt;strong>步骤 3：提供修复建议 -&lt;/strong> 使用预定义的模板或代码变异，SAPFIX 提出了一个解决方案。在确定故障位置后，SAPFIX 尝试生成一个补丁。它采用了以下两种策略：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>基于模板的修复 -&lt;/strong> SAPFIX 使用预定义的模板为常见 bug 提供修复建议。这些模板是根据标准开发者实践设计的。&lt;/li>
&lt;li>&lt;strong>基于变异的修复 -&lt;/strong> 如果基于模板的方法失败，SAPFIX 将采用基于变异的系统。它会对故障位置应用一系列代码变异，生成潜在的修复方案。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>步骤 4：测试修复 -&lt;/strong> 提议的解决方案会经过测试以确保其有效性。它使用 𝗦𝗮𝗽𝗶𝗲𝗻𝘇 的测试用例来检查补丁的有效性。如果补丁通过所有测试，则被视为有效的修复方案。在补丁验证完成后，SAPFIX 使用 𝗜𝗻𝗳𝗲𝗿（一种静态分析工具）对提议的修复方案进行进一步分析。Infer 会检查补丁是否引入了任何新的潜在问题。&lt;/p>
&lt;p>&lt;strong>步骤 5：审查 -&lt;/strong> 开发人员拥有最终决定权，对修复方案进行审查和批准。&lt;/p>
&lt;p>论文地址是: &lt;a href="https://ieeexplore.ieee.org/document/8804442" target="_blank" rel="noopener noreffer ">https://ieeexplore.ieee.org/document/8804442&lt;/a>&lt;/p></description></item><item><title>大厂的测试经理们都在干什么呢</title><link>https://itest2025.pages.dev/what-do-test-leaders-do_8/</link><pubDate>Fri, 08 Mar 2024 09:01:45 +0800</pubDate><guid>https://itest2025.pages.dev/what-do-test-leaders-do_8/</guid><description>&lt;p>最近因为面试的关系跟一些大厂的测试经理有过一些交流，我们不妨看看大厂的测试经理们都在做什么吧。&lt;/p>
&lt;p>A 来自某一线互联网大厂，担任测试经理应该十多年了，有着丰富的质量管理经验。在聊的过程中我发现 A 近两年的工作重心都放在效能提升方面。因为机会难得，我就问了一些我比较关注的问题。&lt;/p>
&lt;p>问：如何提升测试开发比，比如从 1 比 3 提升到 1 比 4？&lt;/p>
&lt;p>答：我们之前的测试开发比其实也很高，不过现在已经降到 1 比 7 或者 1 比 8 了。这里面有一些事情是可以做的。首先质量管理方面产品的出厂质量不能降低，质量保障是一个端到端的事情，不能只靠测试同学去保障，研发人员也是质量控制中很重要的一环，所以研发人员在质量上游也要做好测试，这样后面的质量压力就会轻一点。另外我们还会定义标准的出厂测试以及 uat 测试的流程和指标，哪些东西要测以及怎么测，哪些指标要达到多少，什么环境进行什么样的测试，我们都会定义的很精细，这样才能花比较确定的力气去做一些相对确定的事情。另外需要尽可能的去自动化，或者开发相关的工具平台，给测试提效；最后还要管理好老板的预期，因为尽管有提升的空间，但整个过程还是需要花时间的。&lt;/p>
&lt;p>问：自动化测试的维护成本很高，比如系统的频繁改动会导致用例的更新速度跟不上，针对这一点有什么好的办法呢？&lt;/p>
&lt;p>答：接口测试没有界面相对来说维护成本还好，但是带 ui 的自动化测试维护成本确实很高，但一些项目又不能没有 ui 的自动化测试用例，对于这种情况，我们只能说用外包来尽量补，技术债总是有的，测试也有技术债，需要花成本去还。&lt;/p>
&lt;p>在跟 A 的聊天过程中，我发现大厂的质量管理现在做的越来越精细化和标准化，一些质量度量指标也更加立体和实际，聊完之后非常有收获。&lt;/p>
&lt;p>相对于 A 一直在一线大厂，B 的经历要丰富一些。两年多之前在某一线厂负责核心系统的整体测试，目前在二线大厂负责整体的测试工作，下属规模还是比较大的。&lt;/p>
&lt;p>问：可以简单了解一下在公司的主要工作职能吗？&lt;/p>
&lt;p>答：做的事情可以分为两大块。第一块是规范质量流程，定义质量度量的指标，比如怎么样才算是质量好，这里可以做的事情非常多，比如规范测试流程方面，我们定义了具体的质量保障流程以及流程中的交付物和产出物，质量的管理变得更加的高效和精益。第二块就是测试效率的提升和整体研发效能提升，里面也有很多细节。&lt;/p>
&lt;p>问：可以方便了解一下在度量质量方面，我们定义了哪些指标呢？&lt;/p>
&lt;p>答：主要的指标就是线上故障数。线上故障数来自几个方面。首先是用户的反馈。用户反馈分三个方面。第一个是吐槽；第二个是产品建议；第三个就是线上故障了。我们通过一些渠道收集这些用户反馈并进行记录；然后是发布时出现的故障，看回滚数就可以了；第三是我们一些钉钉群里的反馈，比如老板的反馈和内部反馈等；最后就是测试和开发在日常使用过程中发现的线上故障数和用户 app 端上报时发现的故障数。&lt;/p>
&lt;p>问：所以线上故障数就是线上 bug 对吧？&lt;/p>
&lt;p>答：不太一样，线上 bug 是汇总过的，比如一个线上 bug 可能会引发几百个故障，如果只记录 1 个线上问题的话，那么就没办法比较好的比较 bug 的严重程度的。所以记录故障，对故障的影响范围的评估就相对容易一些，就不会出现线上问题的影响范围变小的问题了，因此对线上的质量就有比较客观的评估了。&lt;/p>
&lt;p>问：那在效能提升方面，我们做了哪些实践呢？&lt;/p>
&lt;p>答：主要几个方面吧。首先定义了标准的开发过程，比如之前我们抽查过一些项目，从需求提出到上线可能要 30 多天(这里记得不是很清楚了)，后来我们通过定义好开发的标准过程，主要是消除状态扭转时的耗时，我们发现需求上线周期可能只要 7 天了。其实研发的效率并没有得到提升，开发一个需求还是需要 3-4 天，但是由于过程定义清楚了，大家的职责范围更明确了，就不能甩锅了，因此研发整体效率提升了不少；另一个实践就是专项测试。我记得当时我来的时候某个功能开发要 3 天，测试却要 21 天，这实在是吓到我了，后来我们进行了测试专项的优化，这个时间只需要 2 天了，而且发现的 bug 数还有了较大的提升，这是一个很典型的例子 。最后就是工具的研发，提升开发的 debug 效率以及测试的效率。&lt;/p></description></item><item><title>作为测试人员，我们该如何看待AI</title><link>https://itest2025.pages.dev/how-should-we-view-ai-as-testers_7/</link><pubDate>Fri, 08 Mar 2024 09:01:43 +0800</pubDate><guid>https://itest2025.pages.dev/how-should-we-view-ai-as-testers_7/</guid><description>&lt;p>前几天看到一篇文章讨论从测试人员的角度去理解 AI 的，稍微翻译了一下。原文地址https://stevethedoc.wordpress.com/2023/06/18/how-should-we-view-ai-as-testers&lt;/p>
&lt;p>上周三和周四，我有幸与我的两位同事 Sushmitha Sivan 和 Bhavana Akula 一起参加了伦敦的 AI 峰会。在那里，我们不仅听到了一些非常有趣的关于人工智能的演讲，还有机会为黑客马拉松的参与者们进行了大约一个小时的测试和质量工程的培训，教他们如何将这些知识应用到他们所面临的挑战中。&lt;/p>
&lt;p>人工智能对我来说还是比较新的（我猜很多人也是如此），所以我们都处于学习的不同阶段，但这个学习过程非常有趣，因为它的范围和对社会的影响都是令人着迷的。&lt;/p>
&lt;p>我不会告诉你如何测试人工智能，或者如何优化数据模型等等，这方面有其他地方可以去了解。这篇文章主要是关于我从参加的不同讲座中得到的一些更普遍的学习和想法，我脑海中浮现出了一些流行词和短语（以下是随机排列的）：&lt;/p>
&lt;ul>
&lt;li>道德&lt;/li>
&lt;li>法规&lt;/li>
&lt;li>抵抗&lt;/li>
&lt;li>未知的恐惧&lt;/li>
&lt;li>失业&lt;/li>
&lt;li>激发人类创造力&lt;/li>
&lt;li>减轻繁琐工作&lt;/li>
&lt;li>数据模型偏见&lt;/li>
&lt;li>训练数据模型&lt;/li>
&lt;li>负责任地使用人工智能&lt;/li>
&lt;li>深度伪造&lt;/li>
&lt;/ul>
&lt;p>这让我思考起作为测试专业人员，我们应该如何应对人工智能。我故意使用这个词汇，因为它涵盖了测试中的每个角色。&lt;/p>
&lt;p>我们大多数人在职业生涯中都测试过用户界面、后端数据库、API 等等，这些都可以生成特定的已知结果。我们有用户故事告诉我们预期的行为，以便我们可以相应地进行测试。那么对于这个新世界，我们该怎么看待呢？&lt;/p>
&lt;p>我认为人工智能的目的是增强我们人类的工具，帮助我们做出决策。计算机的思维速度比我们快得多，因此它们可以帮助我们摆脱一些乏味的工作，我们可以利用我们的大脑去做更有创造力的事情。&lt;/p>
&lt;p>当然，依赖人工智能可能会导致忽视固有的偏见，我们所做的决策可能基于有缺陷的数据。因此，我们需要谨慎对待我们对结果所放置的信任程度&lt;/p>
&lt;ul>
&lt;li>我们能相信 AI 工具使用的数据集吗？&lt;/li>
&lt;li>可能包含假数据吗？&lt;/li>
&lt;li>它是否包含偏见或者是偏差？&lt;/li>
&lt;li>我们是否相信输出涵盖了我们需要考虑的一切？&lt;/li>
&lt;li>结果是否受到了可能会影响的缺失参考文献的影响？&lt;/li>
&lt;/ul>
&lt;p>如果机器能够替我们进行批判性思考，并且我们毫无疑问地依赖它们，我们也可能失去自己的批判性思维能力。这种情况已经发生了，一个现实世界的例子是这样的——有多少 30 岁以下的人能够从书上读地图？如果他们的手机或汽车导航系统出故障了，有多少年轻一代能够应对并使用地图作为备用计划？他们在科技依赖下长大，而我们这些年纪稍大一点的人则能够两者兼顾。&lt;/p>
&lt;p>作为测试人员，我们很容易陷入打开像 ChatGPT 这样的东西，并要求它根据我们提供的信息帮助生成测试计划或测试用例的陷阱中，然后将其用作完美答案。我们必须谨慎行事。是的，我们可能会得到一些可以开始的东西，而不是一张空白纸，但如果一直这样做，我们就会失去从头开始自己启动这个过程的能力。有时候，将事物进行思维导图有助于我们自己建立联系-我们必须训练自己从 AI 给我们的任何想法中开始中途进行，这可能行得通也可能不行。我并不是说这一定是件坏事，但我们需要小心，不要失去自己思考的能力。&lt;/p>
&lt;p>有一些很棒的现实场景可以利用人工智能：&lt;/p>
&lt;ul>
&lt;li>寻找最佳的抵押贷款利率并将其整理在一张表格中&lt;/li>
&lt;li>为一个不熟悉的情况起草一封信件&lt;/li>
&lt;li>准备主持一场测验的研究工作&lt;/li>
&lt;li>准备一档广播节目（我可能会这样做）&lt;/li>
&lt;/ul>
&lt;p>随着我们在日常生活中开始使用人工智能，我们会越来越依赖它，它不会消失，而是需要受到监管（人类有一种令人羡慕的能力，可以将任何发明变成可以用于有害目的的东西！！），并且需要人们对其使用进行质疑。&lt;/p>
&lt;p>作为测试人员，我的建议是以适度的怀疑态度接受人工智能。质疑所得到的结果，并进行独立验证。你无法获得结果所基于的数据，因此要谨慎行事，做好测试人员最擅长的事情——深入探究、调查和提问。&lt;/p>
&lt;p>最后，保持你的批判性思维能力——在一个人们越来越依赖所听到的话作为真相的世界中，这一点将比以往任何时候都更加重要。那些能够退后一步，采取客观的方法的人将在未来脱颖而出。&lt;/p>
&lt;p>欢迎来到崭新的世界。&lt;/p>
&lt;h3 id="谨慎的在测试过程中使用人工智能">谨慎的在测试过程中使用人工智能&lt;/h3>
&lt;p>作者的观点我是大部分赞同的，最近一直在关注 AI 领域，生成式 AI 爆发性的增长以及快速的落地应用让很多人都印象深刻。我甚至听到过一种观点：凡是现在可以被外包的工作将来都可能被 ai 所取代。&lt;/p>
&lt;p>但事实果真如此吗？&lt;/p>
&lt;p>首先我必须说明，我是非常看好 AI 在测试领域的应用的，从三体里借用一个词语，那我可能是降临派，历史的车轮滚滚向前，螳臂当车可能是不太明智的。尽管我看好未来，不过从现在这个阶段来说，把 AI 应用到测试工作中我们还有很多问题需要解决。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>首先值得借鉴的应用场景目前并不多。这篇推文写作的时间是 2023 年的年中，从目前的情况看将 AI 应用到测试中的案例并不多见.有一个非常有启发的例子是用 AI 自动进行网页上操作，不过离真正的测试活动还是有些差距的，预期结果和断言的缺失让这个想法目前还只是属于自动化的范畴。&lt;/p></description></item><item><title>微软的QA变迁史</title><link>https://itest2025.pages.dev/how-microsoft-does-qa_6/</link><pubDate>Fri, 08 Mar 2024 09:01:42 +0800</pubDate><guid>https://itest2025.pages.dev/how-microsoft-does-qa_6/</guid><description>&lt;p>看到一篇梳理微软的 QA 变迁史的文章，之前只听说过微软曾经直接干掉了所以的 qa 角色，不过也只是道听途说而已，这篇文章见微知著的以内部 qa 的视角描述了整个演进的过程，并对最终的结果进行了描述，还是非常有参考价值的。原文在这里：https://blog.pragmaticengineer.com/how-microsoft-does-qa。 下面是中文翻译。&lt;/p>
&lt;h3 id="sdet-角色">SDET 角色&lt;/h3>
&lt;p>SDET（软件开发测试工程师）角色是微软在技术行业中首创的。他们是专注于编写自动化测试、构建和维护测试系统的软件工程师。SDET 与软件开发工程师（SDE）唯一的区别在于，SDET 通常不编写生产代码，而是编写测试代码，并与 SDE 在同一个团队中工作。&lt;/p>
&lt;p>我无法追溯该角色的确切引入时间，但很可能是在 1990 年代。例如，这是微软 Exchange 团队的一位成员在 2004 年发表的一篇帖子，解释了在他们组织中成为 SDET 的含义：&lt;/p>
&lt;blockquote>
&lt;p>“SDET 是一位在测试团队而非开发团队工作的开发人员。SDET 具备测试员的敏锐感，同时喜欢编写大量代码。&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>SDET 通过提供必要的工具和流程，使测试人员能够充分发挥他们的优势&amp;hellip;在产品上市之前，尽可能多地测试产品并发现尽可能多的错误。&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>SDET 具备分析产品功能和架构的能力，从而设计和实现有助于测试的工具。&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>SDET 喜欢短期项目生命周期，在一年内设计和实现许多工具和测试框架，使用最新技术，并有充分的创新空间。&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>尽管产品质量是首要关注的问题，但 SDET 在产品生命周期末期不会像开发人员那样感到压力。通俗地说&amp;hellip; SDET 很少会面临风险:)”&lt;/p>&lt;/blockquote>
&lt;p>微软为 SDET 角色设定了正式的职业发展路径。同一篇帖子中写道：&lt;/p>
&lt;blockquote>
&lt;p>“[SDET 职位]有很大的发展空间。如果你喜欢作为 SDET 所做的工作，你可以发展成为测试架构师。如果你想参与管理工作，那么你可以逐步晋升为 SDET 主管，然后成为测试经理。&lt;/p>&lt;/blockquote>
&lt;blockquote>
&lt;p>如果你只想编码而不参与测试工作，你可以选择成为开发人员的道路。许多人选择了这条道路。如果你意识到你的心属于测试，那么你可以成为一名测试员。”&lt;/p>&lt;/blockquote>
&lt;p>&lt;strong>直到 2014 年左右，SDE 和 SDET 之间的比例在微软内部普遍为 2:1&lt;/strong>。在 2012 年我所在的 Skype for Xbox One 团队也是如此。以下是我们团队的人员构成，根据员工人数计算：&lt;/p>
&lt;p>12 名 SDE（软件开发工程师）
6 名 SDET（软件开发测试工程师）
2 名 PM（产品经理）
1 名 EM（工程经理）
1 名 SDET 主管&lt;/p></description></item><item><title>更多的测试人员，更多的报酬</title><link>https://itest2025.pages.dev/ai-more-testers-more-money_5/</link><pubDate>Fri, 08 Mar 2024 09:01:40 +0800</pubDate><guid>https://itest2025.pages.dev/ai-more-testers-more-money_5/</guid><description>&lt;p>关于 ai 与测试的讨论现在逐渐流行起来，目前看来观点基本是积极和激进的，大多数人认为 ai 会重新塑造测试行业，不过基于 ai 的测试探索目前看来还是有限的，我稍微总结了一下，大概分为两类。&lt;/p>
&lt;ul>
&lt;li>利用大语言模型的推理能力进行驱动的自动化工具；这个之前的文章已经介绍过一些了，有兴趣的同学可以翻一下之前的存档；&lt;/li>
&lt;li>对融合了 ai 功能的软件产品进行测试。推荐大家研究一下这篇文章，里面的一些标准步骤还是让人备受启发的。https://blog.scottlogic.com/2023/11/14/testing-LLM-based-applications-strategy-and-challenges.html。这些测试大多是对prompt的安全，边界条件以及输出的稳定性进行测试。&lt;/li>
&lt;/ul>
&lt;p>就这个时间节点看来，ai 在测试领域上的应用其实不算太多。另外目前对于大模型本身的测试工作也基本是众测模式，模型提供商经过基本的 fine tuning 和测试之后发布模型或者提供 api 接口，通过使用者的反馈来持续的改进模型，基本上走的是强化学习的路子，对模型的测试不依赖于测试行为，而依赖于更多样性的标记数据，所以哪家大模型能跑出来其实就是看哪家的显卡多和数据多。&lt;/p>
&lt;p>最近看到一篇非常积极拥抱 ai 的测试文章，里面的观点基本没有数据佐证，先不去讨论正确与否，不过文章确实传递出了一种很强烈的乐观信号，因为这篇文章认为 ai 会带来更多的测试岗位和更高的报酬。下面是对这篇文章的翻译，https://jarbon.medium.com/ai-more-testers-more-money-045e1a34741d。先提前说明，对这种观点我持谨慎的乐观态度，尽管我一直认为从事ai其实跟我们当年做黑盒测试差不多，不过下文的描述在短期看来还是比较难达成的，未来是什么样确实比较难以预测，所以不如积极投身其中去创造未来吧。&lt;/p>
&lt;p>下面是原文的翻译：&lt;/p>
&lt;p>AI 裂变正在迅速到来。AI 将在软件测试领域创造新的有和无。将 AI 引入软件测试将改变测试人员、供应商和工具的工作方式。但是，这种变化不会如大多数人所担心或梦想的那样。声称 AI 很快就会简单地取代人工测试人员的工作是错误的。希望 AI 无法足够聪明地完成大部分测试工作也是错误的。将会有更多的测试工作，并且它们很快将得到更高的报酬。在这个过渡中，有些人会失去工作，但这取决于他们自己。只有思想开放、乐观的测试人员才能在这个过渡中生存下来，并且他们将得到丰厚的工作保障和报酬。&lt;/p>
&lt;h3 id="ai-对测试工作的影响">AI 对测试工作的影响&lt;/h3>
&lt;p>在短期内，AI 不会减少测试工作的数量 - 对测试人员的需求将会增加。许多测试人员担心 AI 会消灭他们的工作 - 这对大多数人来说是不真实的。应用 AI 于工作的“AI 辅助”测试人员将更加高效。那些能够迅速做到这一点的人将获得最多的工作保障、进步和回报。&lt;/p>
&lt;p>在 10 亿美元以上的测试领域中，最大的问题可能是如何衡量价值。工程团队知道他们需要测试，但很难量化，而事实是，大多数测试工作在今天没有 AI 的情况下几乎没有什么用处。当测试人员得到 AI 的辅助时，测试人员的速度、范围和价值将增长到明显的价值点。目前，大多数软件测试仅仅涵盖基本功能和一些边缘情况，成本高昂，并拖慢了工程团队的进展。&lt;/p>
&lt;p>然而，AI 辅助测试将赋予这些人类测试人员“超能力”，他们的贡献将是压倒性的明显。正如测试领域的一位朋友上周所描述的那样 - 不久之后，测试人员将穿戴 AI-“钢铁侠”装备。企业将希望增加更多的 AI 测试人员，因为他们终于看到了明显的积极回报。这些 AI 辅助测试人员将足够快，以便在当前的开发周期内发挥作用，给人足够的信心进行发布，发现对业务有影响的问题，并揭示仍待测试的内容。对于 AI 辅助测试人员的需求将增加，因为他们将为企业增加明显的价值 - 就像开发人员增加功能和修复错误一样。&lt;/p>
&lt;p>AI 还意味着开发人员正在创建更多的软件。AI 辅助工程师的加速是非常真实的，而且 AI 也意味着更多的人可以创建软件。所有这些新软件都需要由更多经过 AI 增强的测试人员进行测试。&lt;/p></description></item><item><title>在selenium中使用AjaxElementLocatorFactory来优化PO模式</title><link>https://itest2025.pages.dev/better-page-objects-strategy-using-ajaxelementlocatorfactory-with-selenium-and-java_4/</link><pubDate>Fri, 08 Mar 2024 09:01:39 +0800</pubDate><guid>https://itest2025.pages.dev/better-page-objects-strategy-using-ajaxelementlocatorfactory-with-selenium-and-java_4/</guid><description>&lt;p>之前看到的一篇对于 po 的改进的文章，非常有启发，简单翻译改写了一下，希望对大家有帮助。本文基于 java，至于其他语言是否有类似的实现，没有具体研究过。&lt;/p>
&lt;p>原文地址: &lt;a href="http://www.eliasnogueira.com/better-page-objects-strategy-using-ajaxelementlocatorfactory-with-selenium-and-java/?utm_campaign=Software%2BTesting%2BWeekly&amp;amp;utm_medium=email&amp;amp;utm_source=Software_Testing_Weekly_135" target="_blank" rel="noopener noreffer ">http://www.eliasnogueira.com/better-page-objects-strategy-using-ajaxelementlocatorfactory-with-selenium-and-java&lt;/a>&lt;/p>
&lt;p>PO 模式是 page object factory 设计模式的简称，主要是以页面为维度来聚合一些元素的定位，让代码有更好的维护性和重用性，具体细节可以看这里：&lt;a href="https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models" target="_blank" rel="noopener noreffer ">https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models&lt;/a>。这里是官方文档，非常值得精读。&lt;/p>
&lt;h3 id="page-factory">Page Factory&lt;/h3>
&lt;p>如果你已经对 po 很熟悉了，下面的内容可以放心跳过。&lt;/p>
&lt;p>下面是最基本的 Page Factory 套路，本质上是按页面去封装元素定位和操作。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">PageObjectExample&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">final&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">WebDriver&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">PageObjectExample&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">WebDriver&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">driver&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">login&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">findElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">By&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">id&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;email&amp;#34;&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="na">sendKeys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">findElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">By&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">id&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;password&amp;#34;&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="na">sendKeys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">findElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">By&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;next&amp;#34;&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="na">click&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>上面的代码只能说懂的都懂，不过这里有个问题，在 login 方法里，我们频繁使用&lt;code>driver.findElement&lt;/code>方法，这会显得有一些的啰嗦，下面是改进版本，优雅了很多。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">class&lt;/span> &lt;span class="nc">PageObjectExample&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@FindBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;email&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">WebElement&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@FindBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;password&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">WebElement&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nd">@FindBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;next&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">private&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">WebElement&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">next&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">PageObjectExample&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">WebDriver&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">PageFactory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">initElements&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">public&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nf">login&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">String&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">email&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">sendKeys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">password&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">sendKeys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">next&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">click&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里要注意的是在进行初始化的时候，需要调用&lt;code>PageFactory.initElements(driver, this);&lt;/code>&lt;/p></description></item><item><title>测试人员必会的docker秘籍</title><link>https://itest2025.pages.dev/docker-tips-for-testers-2023_3/</link><pubDate>Fri, 08 Mar 2024 09:01:37 +0800</pubDate><guid>https://itest2025.pages.dev/docker-tips-for-testers-2023_3/</guid><description>&lt;p>&lt;a href="https://www.docker.com/" target="_blank" rel="noopener noreffer ">Docker&lt;/a>现在是许多 QA 工程师的常用工具。它用于生产环境和测试环境或两者兼而有之。docker 的文档&lt;a href="https://docs.docker.com/get-started/" target="_blank" rel="noopener noreffer ">制作精良&lt;/a>，相对容易理解，但有时我们需要一些常用命令来解决问题。&lt;/p>
&lt;p>在这里我列举一下对于测试同学来说比较值得去弄明白的 docker 秘籍。&lt;/p>
&lt;h3 id="如何使用不同的参数运行已经启动的-docker-容器">*&lt;strong>*如何使用不同的参数运行已经启动的 docker 容器？**&lt;/strong>&lt;/h3>
&lt;p>可以用这个工具：&lt;a href="https://github.com/lavie/runlike/" target="_blank" rel="noopener noreffer ">https://github.com/lavie/runlike/&lt;/a>&lt;/p>
&lt;p>使用方法: &lt;code>runlike -p &amp;lt;container_name&amp;gt;&lt;/code> ，这样就可以拿到该容器第一次启动时候用的具体命令了。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">runlike -p testservice
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker run &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--name&lt;span class="o">=&lt;/span>testservice &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--user&lt;span class="o">=&lt;/span>&lt;span class="nb">test&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--env&lt;span class="o">=&lt;/span>&lt;span class="nv">KAFKA_HOST&lt;/span>&lt;span class="o">=&lt;/span>172.17.0.1:9092 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--env&lt;span class="o">=&lt;/span>&lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>/opt/java/openjdk/bin:/usr/local/sbin:/usr/local/bin &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--env&lt;span class="o">=&lt;/span>&lt;span class="nv">LANG&lt;/span>&lt;span class="o">=&lt;/span>en_US.UTF-8 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--workdir&lt;span class="o">=&lt;/span>/home/testapp &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>-p 8015:8080 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--restart&lt;span class="o">=&lt;/span>always &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--log-driver&lt;span class="o">=&lt;/span>journald &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--runtime&lt;span class="o">=&lt;/span>runc &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>--detach&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span>myrepo/testservice:master-1374
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这时候我们就可以修改一些参数，比如端口号信息，生活变得容易了一些。&lt;/p>
&lt;h3 id="如何在-docker-容器中运行本地-bash-脚本">*&lt;strong>*如何在 docker 容器中运行本地 bash 脚本？**&lt;/strong>&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cat local_script.sh &lt;span class="p">|&lt;/span> docker &lt;span class="nb">exec&lt;/span> &amp;lt;container_name&amp;gt; /bin/bash
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="如何重启或移除所有的容器">如何重启或移除所有的容器？&lt;/h3>
&lt;p>这个技巧非常管用，推荐牢记。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker stop &lt;span class="k">$(&lt;/span>docker ps -a -q&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker restart &lt;span class="k">$(&lt;/span>docker ps -a -q&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="如何清理旧的-docker-镜像容器和卷">*&lt;strong>*如何清理旧的 docker 镜像、容器和卷？**&lt;/strong>&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker system prune -a
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="如何过滤-docker-ps-命令以仅获取所需的信息例如容器名称状态和镜像">*&lt;strong>*如何过滤 docker ps 命令，以仅获取所需的信息，例如容器名称、状态和镜像？**&lt;/strong>&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker ps --format &lt;span class="s2">&amp;#34;table {{.Names}}\t{{.Status}}\t{{.Image}}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="如何保存和恢复-docker-容器">*&lt;strong>*如何保存和恢复 docker 容器？**&lt;/strong>&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker commit -p &amp;lt;CONTAINER_ID&amp;gt; &amp;lt;YOUR_BACKUP_NAME&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker save -o &amp;lt;CONTAINER_FILE&amp;gt;.tar &amp;lt;YOUR_BACKUP_NAME&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker load -i &amp;lt;CONTAINER_FILE&amp;gt;.tar
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="如何将常用的-docker-命令简化成别名">如何将常用的 docker 命令简化成别名？&lt;/h3>
&lt;p>如果你是 docker 的重度用户的话，这个能力非常实用。&lt;/p></description></item><item><title>leetcode最有名的简单算法题：two sum</title><link>https://itest2025.pages.dev/leetcode-two-sum_2/</link><pubDate>Fri, 08 Mar 2024 09:01:36 +0800</pubDate><guid>https://itest2025.pages.dev/leetcode-two-sum_2/</guid><description>&lt;p>应该还是有很多同学在刷题找工作吧，如果大家刷 leetcode 的话，推荐的做法是从简单到难，这样一来 two sum 是大家绕不过去的最著名的简单算法题了，废话不多说，先看题目的描述。&lt;/p>
&lt;p>给定一个整数数组  &lt;code>nums&lt;/code>  和一个整数目标值  &lt;code>target&lt;/code>，请你在该数组中找出  &lt;strong>和为目标值&lt;/strong> &lt;em>&lt;code>target&lt;/code>&lt;/em>  的那  &lt;strong>两个&lt;/strong>  整数，并返回它们的数组下标。&lt;/p>
&lt;p>你可以假设每种输入只会对应一个答案。但是，数组中同一个元素在答案里不能重复出现。&lt;/p>
&lt;p>你可以按任意顺序返回答案。&lt;/p>
&lt;p>&lt;strong>示例 1：&lt;/strong>&lt;/p>
&lt;pre tabindex="0">&lt;code>输入：nums = [2,7,11,15], target = 9
输出：[0,1]
解释：因为 nums[0] + nums[1] == 9 ，返回 [0, 1] 。
&lt;/code>&lt;/pre>&lt;p>&lt;strong>示例 2：&lt;/strong>&lt;/p>
&lt;pre tabindex="0">&lt;code>输入：nums = [3,2,4], target = 6
输出：[1,2]
&lt;/code>&lt;/pre>&lt;p>&lt;strong>示例 3：&lt;/strong>&lt;/p>
&lt;pre tabindex="0">&lt;code>输入：nums = [3,3], target = 6
输出：[0,1]
&lt;/code>&lt;/pre>&lt;p>&lt;strong>提示：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>2 &amp;lt;= nums.length &amp;lt;= 104&lt;/code>&lt;/li>
&lt;li>&lt;code>109 &amp;lt;= nums[i] &amp;lt;= 109&lt;/code>&lt;/li>
&lt;li>&lt;code>109 &amp;lt;= target &amp;lt;= 109&lt;/code>&lt;/li>
&lt;li>&lt;strong>只会存在一个有效答案&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>**进阶：**你可以想出一个时间复杂度小于  &lt;code>O(n2)&lt;/code>  的算法吗？&lt;/p></description></item><item><title>在2023使用playwright进行自动化测试</title><link>https://itest2025.pages.dev/how-to-use-playwright-2023_123/</link><pubDate>Fri, 01 Mar 2024 02:04:39 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-use-playwright-2023_123/</guid><description>&lt;p>playwright 一直是我最看好的新一代自动化测试框架，2022 年底 playwright 在 npm 上的下载量超过了 100 万，尽管不如 selenium 和 cypress，不过势头还是相当强劲的。最近正好发现一篇文章简单的介绍了使用 typescript，pageobject 和 fixture 配合 playwright 进行用例编写的文章，这里把里面的精华拿出来分享一下。&lt;/p>
&lt;h2 id="老生常谈playwright-的优势">老生常谈，playwright 的优势&lt;/h2>
&lt;ul>
&lt;li>有个好爹，微软出品，看好长期更新维护和迭代，但也可能突然被砍掉，毕竟大公司都在裁员&lt;/li>
&lt;li>运行速度快&lt;/li>
&lt;li>自动等待元素出现&lt;/li>
&lt;li>报告的呈现很多元化，可以设置重试机制，捕获执行日志，截屏录屏等&lt;/li>
&lt;li>支持多个浏览器并行执行&lt;/li>
&lt;li>提供自动生成代码能力以及 Inspector GUI&lt;/li>
&lt;li>一套代码，跨浏览器执行的能力&lt;/li>
&lt;/ul>
&lt;h2 id="目录结构">目录结构&lt;/h2>
&lt;p>框架整体的目录结构如下。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="p">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">├──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">│&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">├──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">global&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">setup&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">ts&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">│&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">└──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">playwright&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">ts&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">├──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">package&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">lock&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">json&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">├──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">package&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">json&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="err">└──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">src&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="err">├──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="err">│&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">└──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">json&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="err">├──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">fixtures&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="err">│&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">├──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">AxeFixture&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">ts&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="err">│&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">└──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">TodoFixture&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">ts&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="err">├──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">pages&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="err">│&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">└──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">TodoPage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">ts&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="err">└──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tests&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="err">├──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">a11y&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">spec&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">ts&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="err">└──&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">demo&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">pom&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">todo&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">spec&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">ts&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="config-目录">config 目录&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>&lt;em>playwright.config.ts&lt;/em>&lt;/strong> playwright 的配置文件&lt;/li>
&lt;li>&lt;strong>**&lt;strong>global-setup.ts&lt;/strong>**&lt;/strong> 在所有用例执行前运行一次，主要的目的是登录一次被测系统并保存浏览器的全局状态到 storageState.json 文件中。这样就不需要每个用例都去单独登录一次了。更多信息可以参考文档。&lt;a href="https://playwright.dev/docs/test-advanced#global-setup-and-teardown" target="_blank" rel="noopener noreffer ">https://playwright.dev/docs/test-advanced#global-setup-and-teardown&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="page-object">Page Object&lt;/h3>
&lt;p>po 基本上是自建框架的必选项了。具体的实现如下&lt;/p></description></item><item><title>强行为新项目写接口测试是一种什么样的体验</title><link>https://itest2025.pages.dev/api-test-for-brand-new-project_120/</link><pubDate>Wed, 01 Mar 2023 09:04:35 +0800</pubDate><guid>https://itest2025.pages.dev/api-test-for-brand-new-project_120/</guid><description>&lt;p>继续上次的话题，为新项目写 ui 自动化测试是一件非常有挑战的事情，写接口测试会不会容易一点呢？这次我就尝试了一下。&lt;/p>
&lt;p>现阶段我们的管理端接口其实不多，就 8 个左右，所以从工作量上评估其实还可以。&lt;/p>
&lt;h3 id="测试策略">测试策略&lt;/h3>
&lt;p>讲策略之前我们先看一下项目的简单业务属性。该项目的管理后台其实就是稍微复杂一点的增删改查。增加一条记录，编辑记录，各种组合条件查询记录，删除功能暂时没有，后面可能会跟进。&lt;/p>
&lt;p>我的测试策略也很简单，首先搭建 1 个单独的测试环境，防止跟其他测试形式冲突，然后把最大的精力放在数据的准备和清理上。&lt;/p>
&lt;ul>
&lt;li>准备数据：为了测试查询，比较好的方式是每次都先清空数据库，然后动态创建一些固定的数据，我指的固定是比如固定 100 条，每条的排序规则，各个字段都是确定的。&lt;/li>
&lt;li>数据清理：完成测试后清空数据库，比如搜索测试完成之后清空数据库去测试创建和编辑，这样创建的时候就没有存量数据造成的断言干扰，就可以实现每次创建之前数据都是 0 条，创建成功之后变成 1，这样断言就相对容易，而且用例能够以随机的顺序运行，减少了依赖；&lt;/li>
&lt;/ul>
&lt;h3 id="代码实现">代码实现&lt;/h3>
&lt;p>这里最有挑战的是数据准备和清理的代码实现，我需要准备下面的数据&lt;/p>
&lt;ul>
&lt;li>通过接口创建一些数据到 db，之所以用接口创建是因为一些联动的操作直接写 db 的话无法触发。我们的接口是 http 的，用 python+requests 就可以了，稍微麻烦的点是构造的数据需要通过接口的有效性校验，比如时间区间之类的，因为我们的服务会跑在多个不同的国家和地区，timezone 也是需要关注的&lt;/li>
&lt;li>把创建成功的数据 id 以及一些需要用到的字段存到 redis 里。我习惯于用 redis 的 set，因为天生去重，并且可以使用&lt;code>srandmember&lt;/code>来随机返回几条数据，在做查询校验的时候非常方便&lt;/li>
&lt;li>查询一些关联表的信息或者需要用到的数据，保存在 redis 里。这里我用的是 redis 的 string 类型，需要保存的信息直接序列化成 json 字符串，非常的方便；&lt;/li>
&lt;li>直接写 sql 做数据库的清理，直接调用 es 的 resetful api 做索引的清理。我们的查询使用的是 es，所以清理数据的时候除了用 sql 去 delete 之外，es 的 index 也是要清理的&lt;/li>
&lt;li>使用配置文件来进行多环境多地区的配置，比如 mysql 每个地区的配置都不一样，同样的地区不同的业务数据也在不同的库里，把配置弄起来还是很有必要的&lt;/li>
&lt;/ul>
&lt;p>最终实现的效果是运行&lt;code>TEST_ENV=test REGION=cn python data_builder.py&lt;/code>这条命令之后就可以在对应的环境创建初始化数据了。代码比较啰嗦我就不粘贴了。&lt;/p>
&lt;h3 id="用例编写">用例编写&lt;/h3>
&lt;p>用例编写无非是增删改查。&lt;/p></description></item><item><title>假如有一天我躺平了</title><link>https://itest2025.pages.dev/if-i-lost-my-job-one-day_118/</link><pubDate>Wed, 01 Mar 2023 09:04:32 +0800</pubDate><guid>https://itest2025.pages.dev/if-i-lost-my-job-one-day_118/</guid><description>&lt;p>最近躺平几乎成了内卷的反义词，我了解了一下躺平最初的由来，发现是源自百度贴吧，帖子的主人公过着消费极低而且自在悠闲的生活。没有钱了就去横店躺平扮演尸体，赚了一点点钱就不再工作，反正他的消费非常少，躺几天尸够他活个一阵子了。&lt;/p>
&lt;p>这种生活方式突然受到了追捧，当然，很多人只是说说而已，真让他们去躺平难度还是非常大的，毕竟上有老下有小，现实总是不能允许你做任性的事情。这可能表现了喝彩者对内卷化下意识的反对和厌恶，也算是反内卷方程式的一个潜在的解法。&lt;/p>
&lt;p>我不知道内卷是不是经济发展变缓这个特殊缓冲区的产物。因为之前经济高速增长，人们已经有了努力就能获得一切的惯性，当经济稍微停滞一些的时候，努力的惯性也许就成了内卷的核反应堆。等经济增速回归到一个较低水平的时候，也许各种花式躺平就成了主流，比如现在的台湾和日本，到时候可能一部分人卷上加卷，大部分人默默躺平。这也没什么不好，之前旅行的时候经过一些小城市，穿梭在人流中竟然感受到了许久不见烟火气，心里也曾经有过这样一个念头，假如有一天我躺平了，我会做些什么？&lt;/p>
&lt;h3 id="如何生存">如何生存&lt;/h3>
&lt;p>做为一个测试人，我总是信奉一条原则，在项目越早期想的越多越清楚，后面的坑可能就会相对少一些。所以在躺平之前，我应该会重点思考一个问题，那就是生存还是毁灭的终极问题。&lt;/p>
&lt;p>躺平了就意味着不能在职场打工干饭了，因为如果打工的话，你的身体告诉你要躺平，但你的老板却花式要求你要更加努力，修更多福报。&lt;/p>
&lt;p>不能打工如何活下去呢？记得《穷爸爸富爸爸》里就有类似的讨论，不打工可能凭借自己的资产活下去，比如投资的公司的收入，隐形资产比如知识产权的收入。不过这些离我比较遥远，我觉得比较有可能的收入来源是&lt;/p>
&lt;ul>
&lt;li>稳健理财的收入。长期定期投入的话，可能会有年化 5%左右的收入吧；&lt;/li>
&lt;li>激进型理财的收入。比如股票和以股票为主的基金收入；我有个朋友曾经跟我分享过一个例子，他有个同事很多年前买了几百万的大头股，现在不仅股票涨了，而且每年都有十几万的分红，过的相当滋润，当然了，这是极其典型的幸存者偏差，大家听完一笑而过就好。另外我也见过有一对台湾夫妻分享他们财务独立的案例，他们定投了十几年的基金，后来财务独立环游世界，这个例子我找到了好几个佐证和类似的例子，所以应该有一定的可行性。好的年份激进型理财的年化收益可能会有 10%左右；&lt;/li>
&lt;li>零散收入。这部分收入可遇不可求，比如帮别人开发一些自动化工具或者自动化脚本之类的，随缘就好；&lt;/li>
&lt;li>潜在收入。学一门手艺，比如木工电工之类的，接一些零活；&lt;/li>
&lt;/ul>
&lt;p>总的来说收入主要靠投资和理财，其他的只能随缘，不能糊口。&lt;/p>
&lt;p>下面来算支出。&lt;/p>
&lt;ul>
&lt;li>房租水电。在一线城市躺平成本比较高，而且周围的人在疯狂搞钱，受他们的影响容易焦虑，所以可以考虑搬去二线城市；一些优质教育资源集中的二线城市的头部学校可能比一线城市的普通学校综合实力更强，也能缓解一些鸡娃焦虑。那么二线城市的房租是多少呢？我随便搜了一下，好点的位置大概 5000 左右，如果稍微降低一下标准，算上水电等，一年 6 万应该是可以搞定的；&lt;/li>
&lt;li>吃饭。在深圳自己买菜的话，一天 100 块应该够 3 口之家吃的很好了；现在物价基本上大部分城市都差不多，所以在吃上面的支出一二线城市 100 块一天是差不多了，加上偶尔出去吃一顿，那么一年 4 万应该是可以搞定的；&lt;/li>
&lt;li>出行。去二线城市应该可以卖掉现在的车，因为一台车一年的成本差不多要 1 万块，然后换一个小电驴，周边的出行应该就没大问题了；出远门可以地铁或者打车，想开车自驾的话可以租车，租车很便宜，一天 100 多一点可以搞定。所以综合算来一年 1 万左右吧；&lt;/li>
&lt;li>穿衣。我自己的衣服很简单，随便找个店买点基本款就可以了，如果住在南方几件 t 恤就能过一年，所以一年穿衣千把块就可以了。算上全家的一年大概 1 万左右可以搞定；&lt;/li>
&lt;li>其他支出。比如换手机或者买点乱七八糟东西之类的，一年大概 2 万吧；&lt;/li>
&lt;/ul>
&lt;p>这样算下来，总支出应该是 6+4+1+1+2=14 万，再加上一些无法预计的支出，再放宽 2 万，一年的生活成本应该是 16 万。&lt;/p>
&lt;p>如果有 400 万左右的存款，放 100 万去做高风险投资，年化 10%，300 万做稳健理财，大概 5%，这样一年的收入应该能有 25 万，粗茶淡饭一年生活下来，还有 9 万的盈余可以投入到下一年的激进理财里，因此除了跑不赢通胀的焦虑之外，拿 400 现金应该就可以躺平了吧。记得前不久有则新闻，一对北京土著夫妻卖了北京的房产，一家三口着 1000 万去昆明买房定居，其实算下投资收益，他们哪怕找不到工作一家三口平平淡淡过一生也应该足够了吧。&lt;/p>
&lt;h3 id="可以做些什么">可以做些什么&lt;/h3>
&lt;p>不工作的话可以做的事情就很多了。&lt;/p>
&lt;ul>
&lt;li>旅行。可以自驾也可以公共交通，丰俭由人，预算够的话就稍微放飞一些，预算吃紧就经济一些，行万里路，给生活带来不同的节奏；&lt;/li>
&lt;li>读书。可以泡图书馆和书店，书非借不能读也；&lt;/li>
&lt;li>带娃。我前几天听说一个数据，中国家庭在义务制教育阶段对孩子的教育投资大概占家庭总收入的 12%左右，这还只是平均数，在一线鸡娃焦虑的城市这个数字只会更高，我估计可能有 20%以上，所以如果孩子不上各种补习班，自己带孩子出去旅行或者教点什么的话，每年节省的费用是可观的；不过自己教娃很难，我觉得这个比上班累；&lt;/li>
&lt;li>学一些实用的技能。比如木工电工自己理发之类的生存能力，做到万事不求人，这样长期看来也能节约不少支出；&lt;/li>
&lt;li>学一些不实用的技能。比如英语钢琴之类的，这样可以教娃，省培训班的钱；&lt;/li>
&lt;li>打游戏。我觉得我可以在家打一个月的足球经理或者三国志，足不出户；&lt;/li>
&lt;li>锻炼身体。这只是说说，哪怕有时间了坚持锻炼身体还是挺难的，不过好的身体可以省药钱，还是要锻炼的；&lt;/li>
&lt;/ul>
&lt;p>这样算下来，有 400 万存款就可以在一个二线城市非常平静的生活下去了，有这样经济能力的人不在少数，起码我身边就有不少。不过大家还是坚持在一线城市卷珠帘，低绮户，照无眠，我猜大概率还是有激情有理想，另外伴随着深深的通货膨胀以及教育焦虑吧。&lt;/p></description></item><item><title>做一个没有感情的用例生成机器</title><link>https://itest2025.pages.dev/why-you-should-write-test-case_115/</link><pubDate>Wed, 01 Mar 2023 09:04:27 +0800</pubDate><guid>https://itest2025.pages.dev/why-you-should-write-test-case_115/</guid><description>&lt;p>最近我们准备把一些服务从一个二次开发的 k8s 平台迁移到另一个二次开发的 k8s 平台去，这时候我们遇到了一个比较棘手的问题：因为迁移是有损的，也就是说迁移的过程中伴随着一定量的代码修改和容器编排方式的适配，那么我们如何保证服务迁移以后的功能是正常可用的呢？&lt;/p>
&lt;p>想了一些方法，这些办法其实比较常见：&lt;/p>
&lt;ul>
&lt;li>增加监控指标和告警指标，迁移后如果服务有问题那么这些指标会让我们尽可能快的发现问题；&lt;/li>
&lt;li>增加自动化的测试用例，通过用例尽可能的对现有服务功能进行覆盖；&lt;/li>
&lt;/ul>
&lt;p>那么问题就来了，如何用最低的成本去实现这些自动化用例呢？&lt;/p>
&lt;p>首先我们应该选择合适类型的自动化用例，目前我们可以实现的用例有&lt;/p>
&lt;ul>
&lt;li>单元测试用例，实现成本高，但是运行成本低，运行速度快，服务没有启动的情况下也能跑，如果有的选，那么单元测试用例是首选；&lt;/li>
&lt;li>接口级的测试用例，实现成本相对较低，运行成本高，需要服务跑起来才能进行测试，运行速度相对单元测试是慢一些的；&lt;/li>
&lt;li>ui 自动化测试用例，实现成本高，运行成本也高，运行速度也慢；&lt;/li>
&lt;/ul>
&lt;p>最终我们选择增加一些核心的单元测试用例和 ui 自动化用例，然后尽可能多写接口测试用例。&lt;/p>
&lt;p>我们的服务实现了一些微服务化，请求从最前面接入层进来之后会到 http 层，该层会调用更下层的 rpc 微服务层实现具体的业务逻辑，因此我们的接口就有两种，分别是&lt;/p>
&lt;ul>
&lt;li>http 接口，客户端直接调用&lt;/li>
&lt;li>rpc 接口，http 层以及微服务之间进行调用&lt;/li>
&lt;/ul>
&lt;p>因为业务逻辑往往需要多次的 rpc 调用才能实现，所以直接写 http 层的接口测试用例相对来说是一种比较经济的方式，因为一次的 http 接口调用会产生多次的 rpc 调用，从业务逻辑上和服务的触达性上来说都是令人满意的。&lt;/p>
&lt;p>那么怎么去用尽可能最低的成本去实现这些 http 接口的用例呢？我之前有过使用 postman 把自己变成一个没有感情的用例生成机器的经历，在这里可以给大家分享一下。&lt;/p>
&lt;p>举一个具体的例子，看这个接口。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-jsx" data-lang="jsx">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">POST&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="nx">api&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">users&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">请求&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;username&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;Jacob&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;email&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;jake@jake.jake&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;password&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;jakejake&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">响应&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;email&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;jake@jake.jake&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;token&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;jwt.token.here&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;username&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;jake&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;bio&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;I work at statefarm&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;image&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这是一个用户注册的 POST 接口，需要向后端传 json 类型的数据，我们可以看成是传递一个 user 对象，这个对象有 username, email 和 password 属性，用 json 字符串的形式表达出来而已。&lt;/p></description></item><item><title>用wiremock自建mock服务</title><link>https://itest2025.pages.dev/how-to-use-wiremock_94/</link><pubDate>Wed, 01 Mar 2023 09:03:55 +0800</pubDate><guid>https://itest2025.pages.dev/how-to-use-wiremock_94/</guid><description>&lt;p>我们很多时候都需要 mock 服务，比如&lt;/p>
&lt;ul>
&lt;li>在做性能测试的时候，我们希望调用第三方服务的接口可以被 mock 掉，这样就不会因为压测而对第三方依赖造成巨大的负载&lt;/li>
&lt;li>第三方服务归属于其他团队，不同环境之间的沟通和协调其实比较麻烦&lt;/li>
&lt;li>消除不同测试环境之间的差异&lt;/li>
&lt;li>控制请求的时延&lt;/li>
&lt;li>如果是使用云服务的话，使用 mock 接口还可以节约出口的带宽&lt;/li>
&lt;li>提升第三方服务的稳定性&lt;/li>
&lt;/ul>
&lt;p>所以 mock 的作用就是用最小的代价实现跟第三方接口几乎一样的假的接口，最好返回的内容是动态可配置的，这样我们在测试环境就可以直接去调用这个假接口，从而消除了对第三方的依赖。&lt;/p>
&lt;p>实现 mock 的方式很多，最简单的做法就是随机应变，自行实现，比如在 python 技术栈里，我们可以用 flask 去这样实现&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">flask&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Flask&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">jsonify&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Flask&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__name__&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/api/get-json&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">hello&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">jsonify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hello&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;world&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># Returns HTTP Response with {&amp;#34;hello&amp;#34;: &amp;#34;world&amp;#34;}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>用 go 实现起来也不难，下面的例子来自 go 的官方文档，使用了 gin 框架。&lt;a href="https://go.dev/doc/tutorial/web-service-gin" target="_blank" rel="noopener noreffer ">https://go.dev/doc/tutorial/web-service-gin&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/http&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/gin-gonic/gin&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// album represents data about a record album.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">album&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ID&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="s">`json:&amp;#34;id&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Title&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="s">`json:&amp;#34;title&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Artist&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="s">`json:&amp;#34;artist&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Price&lt;/span> &lt;span class="kt">float64&lt;/span> &lt;span class="s">`json:&amp;#34;price&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// albums slice to seed record album data.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">albums&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">album&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">ID&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Title&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Blue Train&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Artist&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;John Coltrane&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Price&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">56.99&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">ID&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Title&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Jeru&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Artist&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Gerry Mulligan&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Price&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">17.99&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">ID&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;3&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Title&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Sarah Vaughan and Clifford Brown&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Artist&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Sarah Vaughan&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Price&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">39.99&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">router&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">gin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Default&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">router&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">GET&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/albums&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">getAlbums&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">router&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">GET&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/albums/:id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">getAlbumByID&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">router&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">POST&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/albums&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">postAlbums&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">router&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;localhost:8080&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// getAlbums responds with the list of all albums as JSON.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">getAlbums&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">gin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">IndentedJSON&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusOK&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">albums&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// postAlbums adds an album from JSON received in the request body.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">postAlbums&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">gin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">newAlbum&lt;/span> &lt;span class="nx">album&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Call BindJSON to bind the received JSON to&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// newAlbum.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">BindJSON&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">newAlbum&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Add the new album to the slice.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">albums&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">albums&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">newAlbum&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">IndentedJSON&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusCreated&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">newAlbum&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// getAlbumByID locates the album whose ID value matches the id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// parameter sent by the client, then returns that album as a response.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">getAlbumByID&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">gin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">id&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Param&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Loop through the list of albums, looking for&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// an album whose ID value matches the parameter.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">albums&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ID&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">IndentedJSON&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusOK&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">a&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">IndentedJSON&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusNotFound&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">gin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">H&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;album not found&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>从上面的代码可以看出来，如果不需要实现创建接口的话，代码量还能少三分之一左右，总的来看其实难度也不算大。&lt;/p></description></item><item><title>可持续软件测试：为更绿色和更高效的未来制定 5 项策略</title><link>https://itest2025.pages.dev/sustainable-software-testing-5-strategies-greener-efficient-mair_85/</link><pubDate>Wed, 01 Mar 2023 09:03:42 +0800</pubDate><guid>https://itest2025.pages.dev/sustainable-software-testing-5-strategies-greener-efficient-mair_85/</guid><description>&lt;p>最近看到一篇文章是关于软件测试与节能减排的讨论的，可持续发展这个观点在西方国家目前经常被拿出来探讨，因为这与气候变化等的人类世问题关系非常密切。把这篇&lt;a href="https://www.linkedin.com/pulse/sustainable-software-testing-5-strategies-greener-efficient-mair" target="_blank" rel="noopener noreffer ">可持续软件测试：为更绿色和更高效的未来制定 5 项策略&lt;/a> 简单翻译了一下，里面还是有很多中肯的建议的，一些看法其实与效能提升关系密切，毕竟效率提高了，消耗和浪费可能会有所减少。&lt;/p>
&lt;h3 id="背景">背景&lt;/h3>
&lt;p>在技术进步与进步同义的时代，软件开发的生态足迹往往被忽视。&lt;/p>
&lt;ul>
&lt;li>ShiftProject 的一项研究估计，2018 年信息和通信技术(ICT)行业的碳足迹约占全球温室气体排放的 2-3%。&lt;/li>
&lt;li>彭博社预测，到 2030 年，IT 系统将占全球能源需求的惊人 20%。从这个角度来看，交通运输业目前占全球能源消耗的 25%。&lt;/li>
&lt;li>根据国际能源署(IEA)的数据，2018 年数据中心在全球消耗了约 200 太瓦时(TWh)的电力。&lt;/li>
&lt;/ul>
&lt;p>绿色软件开发的出现宛如灯塔，提供了一种转向生态友好实践的范式转变，与全球对可持续发展的迫切呼吁一致。这种方法不仅仅是一个流行语，它呼吁在软件生命周期中致力于资源效率、减少能源消耗和全面考虑环境。&lt;/p>
&lt;p>通过优化软件测试流程中的资源利用和能源需求，组织可以降低整体测试成本。对于大型测试项目来说，这可以节省大量的资金。此外，测试过程变得更加高效和流线化，从而实现更快的交付周期和更短的上市时间。&lt;/p>
&lt;p>目标很简单：优化测试策略和自动化实践，以节省资源，从而节省能源。&lt;/p>
&lt;p>以下是 5 项策略，可立即在您的组织中启动“绿色 QA”。&lt;/p>
&lt;h3 id="1-使所有测试执行上下文相关">1. 使所有测试执行“上下文相关”&lt;/h3>
&lt;ul>
&lt;li>仅执行与代码更改相关的测试。避免盲目执行；不要在每个环境上运行每个测试。避免大批量的测试执行。这将减少资源消耗并最大限度地减少能源使用。&lt;/li>
&lt;li>避免像晚上、一天多次等计划执行。这些执行会消耗太多资源，如果自上次执行以来部署的代码没有变化，则可能不会提供任何价值。&lt;/li>
&lt;li>将测试隔离，确保每个测试套件或测试用例在不影响其他测试套件或测试用例的情况下独立运行。这可以防止资源争用并提高并行执行的整体效率。减少测试间依赖，以便更容易地在不等待其他测试完成的情况下并发执行测试。&lt;/li>
&lt;li>利用云平台进行测试执行，因为它们可以根据需要动态分配资源。这可以为能源效率做出重大贡献。&lt;/li>
&lt;li>重新设计 CI 和 CD pipeline，以在各种“测试”阶段结合上述原则。&lt;/li>
&lt;/ul>
&lt;p>可以通过转向 Trunk-based 开发和使用注释在代码中标记测试并将其作为参数传递给 CI/CD pipeline 来实现。&lt;/p>
&lt;h3 id="2-实施节能的测试自动化-编码-实践">2. 实施节能的测试自动化 (编码) 实践&lt;/h3>
&lt;p>测试执行能耗 = 活动代码能耗 + 等待代码能耗 + 空闲代码能耗&lt;/p>
&lt;ul>
&lt;li>将测试自动化框架和脚本容器化。容器提供了一个轻量级且孤立的环境，使其易于水平扩展。它们还减少了每次代码下载和构建过程生成的网络流量。&lt;/li>
&lt;li>最小化文件 I/O 操作，尤其是在循环或频繁文件访问场景中。使用缓冲 I/O 操作来减少磁盘访问次数。例如：从文件中读取测试数据或从文件中读取配置或将结果推送到测试管理工具。&lt;/li>
&lt;li>学习编写优化的代码，包括死代码消除、循环优化、公共子表达式消除等。此外，分析不同算法的性能特征并为您的特定用例选择最合适的算法。&lt;/li>
&lt;li>保持测试工具和库是最新的，以受益于任何性能改进。&lt;/li>
&lt;li>在代码中及时释放资源，尤其是在资源受限的环境中，如移动设备或物联网设备测试。&lt;/li>
&lt;li>最小化后台进程、任务和线程，这些进程、任务和线程可能会提高 CPU 和内存利用率，从而增加功耗。&lt;/li>
&lt;li>调整日志记录和报告的详细程度，以避免不必要的资源消耗。&lt;/li>
&lt;/ul>
&lt;p>可以通过在自动化指南文档中记录所有上述内容、与质量工程师沟通并在代码审查过程中将它们作为检查领域来实现。&lt;/p></description></item><item><title>10分钟玩转playwright</title><link>https://itest2025.pages.dev/playwright-in-10-minutes_84/</link><pubDate>Wed, 01 Mar 2023 09:03:40 +0800</pubDate><guid>https://itest2025.pages.dev/playwright-in-10-minutes_84/</guid><description>&lt;p>playwright 是微软推出的一款自动化测试工具，据说开发小组的核心人员来自 google 的 puppeteer 团队，真是一出生就有了个好爹而且好爹的亲爹也是爹中的战斗机，这就让人非常期待了。&lt;/p>
&lt;h3 id="下载与安装">下载与安装&lt;/h3>
&lt;p>playwright 应该是使用 JavaScript 进行开发的，不过考虑到大部分的测试同学可能对 python 更为熟悉，这里的安装就以 python 版本为例。&lt;/p>
&lt;p>在命令行中输入&lt;code>pip install pytest-playwright&lt;/code>，等 playwright 安装成功之后再输入命令&lt;code>playwright install&lt;/code>，这条命令的作用是安装测试所需要的各种浏览器支持，相比较 selenium 需要先安装浏览器再安装相应版本的 driver，playwright 的初始化工作就显得轻松了很多。&lt;/p>
&lt;h3 id="第一个用例">第一个用例&lt;/h3>
&lt;p>接下来按照官方文档里的例子我们来跑第一个 playwright 用例。&lt;/p>
&lt;p>新建一个名为 test_my_app.py 的文件，然后把文档中的例子一字不差的进行拷贝粘贴。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">playwright.sync_api&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Page&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">expect&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_homepage_has_Playwright_in_title_and_get_started_link_linking_to_the_intro_page&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">page&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Page&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">page&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">goto&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://playwright.dev/&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Expect a title &amp;#34;to contain&amp;#34; a substring.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">page&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_have_title&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Playwright&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># create a locator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">get_started&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">page&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">locator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;text=Get Started&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Expect an attribute &amp;#34;to be strictly equal&amp;#34; to the value.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">get_started&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_have_attribute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;href&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;/docs/intro&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Click the get started link.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">get_started&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">click&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Expects the URL to contain intro.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">page&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_have_url&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;.*intro&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后在命令行里输入 pytest，视网速而定，等待个 10 秒钟左右，就可以看运行结果了。&lt;/p></description></item><item><title>从吴亦凡事件学习安全测试</title><link>https://itest2025.pages.dev/%E4%BB%8E%E5%90%B4%E4%BA%A6%E5%87%A1%E4%BA%8B%E4%BB%B6%E5%AD%A6%E4%B9%A0%E5%AE%89%E5%85%A8%E6%B5%8B%E8%AF%95_51/</link><pubDate>Sat, 26 Mar 2022 09:02:49 +0800</pubDate><guid>https://itest2025.pages.dev/%E4%BB%8E%E5%90%B4%E4%BA%A6%E5%87%A1%E4%BA%8B%E4%BB%B6%E5%AD%A6%E4%B9%A0%E5%AE%89%E5%85%A8%E6%B5%8B%E8%AF%95_51/</guid><description>&lt;p>一般情况下我是不喜欢追热点蹭热度的，不过这两天公安机关警情通报之后，吴亦凡事件发生了各种逆转，这起全员恶人事件非常凑巧的契合了安全测试中的一个概念：也就是中间人攻击。我们在吃瓜之余可以简单了解一下中间人攻击的一些原理，通过例子去学习，印象自然会深刻一些。&lt;/p>
&lt;h3 id="什么是中间人攻击">什么是中间人攻击？&lt;/h3>
&lt;p>下面的定义来自维基百科。&lt;/p>
&lt;p>&lt;strong>中间人攻击&lt;/strong>（英语：&lt;strong>M&lt;/strong>an-&lt;strong>i&lt;/strong>n-&lt;strong>t&lt;/strong>he-&lt;strong>m&lt;/strong>iddle attack，缩写：&lt;strong>MITM&lt;/strong>）在&lt;a href="https://zh.wikipedia.org/wiki/%E5%AF%86%E7%A0%81%E5%AD%A6" target="_blank" rel="noopener noreffer ">密码学&lt;/a>和&lt;a href="https://zh.wikipedia.org/wiki/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%AE%89%E5%85%A8" target="_blank" rel="noopener noreffer ">计算机安全&lt;/a>领域中是指攻击者与通讯的两端分别创建独立的联系，并交换其所收到的数据，使通讯的两端认为他们正在通过一个私密的连接与对方直接对话，但事实上整个会话都被攻击者完全控制。在中间人攻击中，攻击者可以拦截通讯双方的通话并插入新的内容。在许多情况下这是很简单的（例如，在一个未加密的&lt;a href="https://zh.wikipedia.org/wiki/Wi-Fi" target="_blank" rel="noopener noreffer ">Wi-Fi&lt;/a> &lt;a href="https://zh.wikipedia.org/wiki/%E6%97%A0%E7%BA%BF%E6%8E%A5%E5%85%A5%E7%82%B9" target="_blank" rel="noopener noreffer ">无线接入点&lt;/a>的接受范围内的中间人攻击者，可以将自己作为一个中间人插入这个网络）。&lt;/p>
&lt;p>一个中间人攻击能成功的前提条件是攻击者能将自己伪装成每一个参与会话的终端，并且不被其他终端识破。中间人攻击是一个（缺乏）相互&lt;a href="https://zh.wikipedia.org/wiki/%E8%AE%A4%E8%AF%81" target="_blank" rel="noopener noreffer ">认证&lt;/a>的攻击。大多数的加密协议都专门加入了一些特殊的认证方法以阻止中间人攻击。例如，&lt;a href="https://zh.wikipedia.org/wiki/SSL" target="_blank" rel="noopener noreffer ">SSL&lt;/a>协议可以验证参与通讯的一方或双方使用的证书是否是由权威的受信任的&lt;a href="https://zh.wikipedia.org/wiki/%E6%95%B0%E5%AD%97%E8%AF%81%E4%B9%A6%E8%AE%A4%E8%AF%81%E6%9C%BA%E6%9E%84" target="_blank" rel="noopener noreffer ">数字证书认证机构&lt;/a>颁发，并且能执行双向身份认证。&lt;/p>
&lt;h3 id="举个例子">举个例子&lt;/h3>
&lt;p>下面的内容依然来自维基百科。&lt;/p>
&lt;p>假设&lt;a href="https://zh.wikipedia.org/wiki/%E6%84%9B%E9%BA%97%E7%B5%B2%E8%88%87%E9%AE%91%E4%BC%AF" target="_blank" rel="noopener noreffer ">爱丽丝&lt;/a>（Alice）希望与&lt;a href="https://zh.wikipedia.org/wiki/%E6%84%9B%E9%BA%97%E7%B5%B2%E8%88%87%E9%AE%91%E4%BC%AF" target="_blank" rel="noopener noreffer ">鲍伯&lt;/a>（Bob）通信。同时，&lt;a href="https://zh.wikipedia.org/wiki/%E6%84%9B%E9%BA%97%E7%B5%B2%E8%88%87%E9%AE%91%E4%BC%AF" target="_blank" rel="noopener noreffer ">马洛里&lt;/a>（Mallory）希望拦截窃会话以进行窃听并可能在某些时候传送给鲍伯一个虚假的消息。&lt;/p>
&lt;p>首先，爱丽丝会向鲍伯索取他的&lt;a href="https://zh.wikipedia.org/wiki/%E5%85%AC%E9%92%A5" target="_blank" rel="noopener noreffer ">公钥&lt;/a>。如果 Bob 将他的&lt;a href="https://zh.wikipedia.org/wiki/%E5%85%AC%E9%92%A5" target="_blank" rel="noopener noreffer ">公钥&lt;/a>发送给 Alice，并且此时马洛里能够拦截到这个&lt;a href="https://zh.wikipedia.org/wiki/%E5%85%AC%E9%92%A5" target="_blank" rel="noopener noreffer ">公钥&lt;/a>，就可以实施中间人攻击。马洛里发送给爱丽丝一个伪造的消息，声称自己是鲍伯，并且附上了马洛里自己的&lt;a href="https://zh.wikipedia.org/wiki/%E5%85%AC%E9%92%A5" target="_blank" rel="noopener noreffer ">公钥&lt;/a>（而不是鲍伯的）。&lt;/p>
&lt;p>爱丽丝收到公钥后相信这个&lt;a href="https://zh.wikipedia.org/wiki/%E5%85%AC%E9%92%A5" target="_blank" rel="noopener noreffer ">公钥&lt;/a>是鲍伯的，于是爱丽丝将她的消息用马洛里的公钥（爱丽丝以为是鲍伯的）加密，并将加密后的消息回给鲍伯。马洛里再次截获爱丽丝回给鲍伯的消息，并使用马洛里自己的私钥对消息进行解密，如果马洛里愿意，她也可以对消息进行修改，然后马洛里使用鲍伯原先发给爱丽丝的公钥对消息再次加密。当鲍伯收到新加密后的消息时，他会相信这是从爱丽丝那里发来的消息。&lt;/p>
&lt;p>&lt;img
 class="lazyload"
 src="https://itest2025.pages.dev/svg/loading.min.svg"
 data-src="%E4%BB%8E%E5%90%B4%E4%BA%A6%E5%87%A1%E4%BA%8B%E4%BB%B6%E5%AD%A6%E4%B9%A0%E5%AE%89%E5%85%A8%E6%B5%8B%E8%AF%95%20f4fcfd1919004bae89ca78e6f0949ed6/Untitled.png"
 data-srcset="%E4%BB%8E%E5%90%B4%E4%BA%A6%E5%87%A1%E4%BA%8B%E4%BB%B6%E5%AD%A6%E4%B9%A0%E5%AE%89%E5%85%A8%E6%B5%8B%E8%AF%95%20f4fcfd1919004bae89ca78e6f0949ed6/Untitled.png, %E4%BB%8E%E5%90%B4%E4%BA%A6%E5%87%A1%E4%BA%8B%E4%BB%B6%E5%AD%A6%E4%B9%A0%E5%AE%89%E5%85%A8%E6%B5%8B%E8%AF%95%20f4fcfd1919004bae89ca78e6f0949ed6/Untitled.png 1.5x, %E4%BB%8E%E5%90%B4%E4%BA%A6%E5%87%A1%E4%BA%8B%E4%BB%B6%E5%AD%A6%E4%B9%A0%E5%AE%89%E5%85%A8%E6%B5%8B%E8%AF%95%20f4fcfd1919004bae89ca78e6f0949ed6/Untitled.png 2x"
 data-sizes="auto"
 alt="%E4%BB%8E%E5%90%B4%E4%BA%A6%E5%87%A1%E4%BA%8B%E4%BB%B6%E5%AD%A6%E4%B9%A0%E5%AE%89%E5%85%A8%E6%B5%8B%E8%AF%95%20f4fcfd1919004bae89ca78e6f0949ed6/Untitled.png"
 title="%E4%BB%8E%E5%90%B4%E4%BA%A6%E5%87%A1%E4%BA%8B%E4%BB%B6%E5%AD%A6%E4%B9%A0%E5%AE%89%E5%85%A8%E6%B5%8B%E8%AF%95%20f4fcfd1919004bae89ca78e6f0949ed6/Untitled.png" />&lt;/p>
&lt;p>具体展开如下：&lt;/p>
&lt;p>1.爱丽丝发送给鲍伯一条消息，却被马洛里截获：&lt;/p>
&lt;blockquote>
&lt;p>爱丽丝“嗨，鲍伯，我是爱丽丝。给我你的公钥” &amp;ndash;&amp;gt;  马洛里   鲍伯&lt;/p>&lt;/blockquote>
&lt;p>2.马洛里将这条截获的消息转送给鲍伯；此时鲍伯并无法分辨这条消息是否从真的爱丽丝那里发来的：&lt;/p>
&lt;blockquote>
&lt;p>爱丽丝   马洛里“嗨，鲍伯，我是爱丽丝。给我你的公钥” &amp;ndash;&amp;gt;  鲍伯&lt;/p>&lt;/blockquote>
&lt;p>3.鲍伯回应爱丽丝的消息，并附上了他的公钥：&lt;/p>
&lt;blockquote>
&lt;p>爱丽丝   马洛里&amp;lt;&amp;ndash; [鲍伯的公钥]&amp;ndash;  鲍伯&lt;/p></description></item><item><title>Selenium 4.11 正式发布，支持chrome for testing</title><link>https://itest2025.pages.dev/selenium-411-released_69/</link><pubDate>Sat, 19 Mar 2022 09:03:15 +0800</pubDate><guid>https://itest2025.pages.dev/selenium-411-released_69/</guid><description>&lt;p>Selenium 4.11.0 正式发布了，先来看一下主要特性。&lt;/p>
&lt;ul>
&lt;li>Chrome DevTools 支持的版本现在是：v113、v114 和 v115（Firefox 仍然对所有版本使用 v85）&lt;/li>
&lt;li>通过 Selenium Manager 支持 Chrome For Testing（CfT）&lt;/li>
&lt;li>Selenium Manager 现在可以在 PATH 或配置的路径上定位 driver 的二进制文件，检查潜在的不兼容性，并提供更好的警告和错误信息。&lt;/li>
&lt;li>每晚都会推送 Ruby 和 Java 的构建版本。对其他语言的支持即将推出。&lt;/li>
&lt;li>在查找窗口句柄时忽略进程 ID 匹配 - Edge 上的 IE 模式。&lt;/li>
&lt;/ul>
&lt;p>这里最重要的更新是支持了 Chrome For Testing.&lt;/p>
&lt;h3 id="chrome-for-testing">Chrome For Testing&lt;/h3>
&lt;p>这是 chrome 推出的专门针对测试场景使用的浏览器，为了解决下面一些痛点&lt;/p>
&lt;ul>
&lt;li>
&lt;p>chrome 的自动化更新。自动更新：对用户来说很方便，对开发者来说很痛苦，特别是测试同学，应为我们希望(a)在重复的测试运行中获得一致且可重复的结果，但如果浏览器可执行文件或二进制文件在两次运行之间决定自行更新，这会毁了一切。(b)我们想要固定一个特定的浏览器版本，并将该版本号添加到你的源代码仓库中，这样你就可以检出旧的提交和分支，并重新运行测试，以便使用那个时间点的浏览器二进制文件进行测试。基于上面两个原因，自动更新让人欲除之而后快。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>下载不到特定版本的 chrome 浏览器。除了自动更新之外，你可能也发现很难找到特定版本的 Chrome 二进制文件。谷歌故意不提供带有版本号的 Chrome 下载，因为用户不应该关心版本号，他们应该尽快更新到最新版本。这对用户来说很好，但对于需要在旧版本的 Chrome 中重现错误报告的开发人员来说很痛苦。这个问题的一个更具体的例子是当你想要使用 ChromeDriver 进行浏览器自动化时。你不仅需要以某种方式下载 Chrome 二进制文件，还需要一个相应版本的 ChromeDriver 二进制文件，以确保这两个二进制文件是兼容的。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>在这样的背景下，chrome for testing 应运而生。官方的说法是&lt;/p>
&lt;blockquote>
&lt;p>为了解决这些问题，Chrome for Testing 是 Chrome 的一个专用版本，针对测试用例进行了优化，不会自动更新，与 Chrome 发布流程集成，每个 Chrome 版本都可用。这个版本的二进制文件尽可能接近常规的 Chrome，同时不会对测试用例产生负面影响。&lt;/p></description></item><item><title>2022 年值得测试同学关注的技术趋势</title><link>https://itest2025.pages.dev/tech-for-testers-2022_89/</link><pubDate>Fri, 04 Mar 2022 09:03:48 +0800</pubDate><guid>https://itest2025.pages.dev/tech-for-testers-2022_89/</guid><description>&lt;p>国外有机构展望了 2022 年值得测试和开发同学关注的技术趋势，我在这里简单的给大家介绍一下，路漫漫其修远兮，大家一起求索吧。&lt;/p>
&lt;h3 id="混合组织成为常态">混合组织成为常态&lt;/h3>
&lt;p>2022 年很明显，有些人不会返回办公室，至少不会一直返回办公室。病毒大流行开始时最大的挑战之一就是远程工作的弱点和新机遇。
人们远程工作面临的最大困难是深陷工作无法自拔。另一方面，人们承认他们在在线交流和协作方面遇到的困难较少。全球 CIO 调查显示，截至 2021 年 3 月，31% 的受访者预计将继续永久远程工作。据另一项调查显示，73％的雇员要求更灵活的工作选择。&lt;/p>
&lt;h3 id="对测试人员意味着什么">对测试人员意味着什么&lt;/h3>
&lt;p>由于远程管理的困难降低使得原本对外包保持警惕的公司放弃了一些担忧。外包 QA 团队是内部团队的延伸。如果正确的设置流程，管理外包并不具有挑战性。更多的公司可能会开始使用外包的 QA 资源。&lt;/p>
&lt;h3 id="对通讯工具的需求不断增长">对通讯工具的需求不断增长&lt;/h3>
&lt;p>2020 年 3 月，在线会议软件的使用量猛增。Zoom 打破下载记录，股价上涨超过 100%，Google Hangouts 提供半年免费使用高级计划，Microsoft Teams 一个月赢得了 1200 万用户。这样的故事在 2021 年听起来并不令人震惊。我们已经加入了用户行列，成为统计数据的一部分。 组织仍在不断寻找可靠的通信和合作软件工具。远程办公不应限制工作活动。&lt;/p>
&lt;p>反过来，科技公司的目标是增强他们的通信工具，加入更多功能，以提供更大的灵活性。 例如，Slack 推出了 Huddles——一种轻量级的音频优先通信功能，允许用户在不安排面对面或视频会议的情况下接听电话。此外，现在用户可以发送音频和视频消息，这对许多人来说也是一个很大的好处。&lt;/p>
&lt;h3 id="对测试人员意味着什么-1">对测试人员意味着什么&lt;/h3>
&lt;p>各种规模和各行各业的公司都在使用通信工具，市场广阔，现有工具还有改进空间。随着新功能的添加，QA 部门将忙于进行功能、集成和回归测试。 喜欢使用定制解决方案的公司可能会决定升级其内部平台。例如，添加与 Zoom、Skype、Meet 或其他一些工具的集成将能够更快地访问视频通话功能。这些改进总是伴随着 API 和回归测试。&lt;/p>
&lt;p>企业可能对更复杂的产品感兴趣，例如具有多客户端功能的内网视频平台、媒体资产管理等。在这种情况下，提供商应该准备好提供具有高级定制功能的产品。从头开始创建这样的平台需要仔细的市场分析，然后进行广泛而细致的测试。&lt;/p>
&lt;h3 id="吸引眼球拉拢更多的用户">吸引眼球，拉拢更多的用户&lt;/h3>
&lt;p>人们越来越意识到社交网络和品牌可以如何操控他们的思想。此外，2021 年的数据表明，用户对新奇产品保持兴奋的程度是有限的，比如 clubhouse。新媒体专家用多篇文章总结了这些事件，一些人在新社交网络的早期就预测到了 clubhouse 这样的结果，以及总结了该产品的失败原因。&lt;/p>
&lt;ul>
&lt;li>显然，创作者没有看到长期的潜力，逐渐停止为平台增值。&lt;/li>
&lt;li>实时性的功能（例如只能进行直播而无法进行广播录制）营造了一种怀旧/复古的氛围，然而大房间的访问受限和人数限制导致了激怒和两极分化。&lt;/li>
&lt;li>算法和用户体验都远非完美。Clubhouse 围绕最初的炒作发展，而不是专注于平台改进 。&lt;/li>
&lt;/ul>
&lt;p>令人惊讶的是，TikTok 仍然是派对之王，逐渐吸引了更多不同的受众。当然，Instagram 也不容忽视，其通过推出 Reels 做出了回应。无论是良好的实现还是熟悉的平台，用户似乎都对它感到满意。&lt;/p>
&lt;h3 id="对测试人员意味着什么-2">对测试人员意味着什么&lt;/h3>
&lt;p>把用户牢牢的绑在平台上，对用户忠诚度甚至注意力的竞争变得更加激烈。 这意味着软件开发团队现在应该更加关注最终用户体验。当然，很大程度上决定开发新应用或功能的人并非技术专家。然而，软件开发人员是实现这些想法的人，而软件测试人员是接触结果的人。功能、UI 和兼容性测试将非常重要。 最后，在流程中涉及业务分析师的团队将更好地了解市场契合度、商业机会和规模。仔细的分析和精心编写的文档有助于避免逻辑空白和不适合市场/需求。&lt;/p></description></item><item><title>纠正两个常见的错误观念。端到端测试自动化是 简单而容易 还是 复杂而不可能</title><link>https://itest2025.pages.dev/correct-two-common-misconceptions-end-to-end-test-automation-is-simple-and-easy-or-complex_83/</link><pubDate>Thu, 03 Mar 2022 09:03:39 +0800</pubDate><guid>https://itest2025.pages.dev/correct-two-common-misconceptions-end-to-end-test-automation-is-simple-and-easy-or-complex_83/</guid><description>&lt;p>当前市场上各种端到端的测试工具层出不穷，工具市场很繁荣，但真正成功的项目实践却很少见。今天看到了一篇很有意思的文章，我愿称作者为&lt;strong>典型的 selenium 原教旨主义者&lt;/strong>， 他的一些观点尽管看上去非常的 old school，不过总的来说是很有道理的，这里简单的分享一下他的观点。&lt;/p>
&lt;p>他的这篇文章叫做 Correct two Common Misconceptions: End-to-End Test Automation is “Simple and Easy” or “Complex and Impossible”，翻译过来就是&lt;strong>纠正两个常见的错误观念。端到端测试自动化是 &amp;ldquo;简单而容易 &amp;ldquo;还是 &amp;ldquo;复杂而不可能&amp;rdquo;&lt;/strong>, 原文的地址是https://zhiminzhan.medium.com/correct-two-common-misconceptions-end-to-end-test-automation-is-simple-and-easy-or-complex-and-ad559ade982a。&lt;/p>
&lt;p>一切都开始于这样的一个观点:&lt;/p>
&lt;blockquote>
&lt;p>事实：大多数测试自动化的尝试都失败了&lt;/p>&lt;/blockquote>
&lt;p>&amp;ldquo;根据我在这个领域 17 年的经验，我同意图片中描述的主要概念，即大多数测试自动化的努力往往是失败的。&amp;rdquo; - 作者&lt;/p>
&lt;h3 id="端到端测试自动化是简单还是复杂">端到端测试自动化是简单还是复杂？&lt;/h3>
&lt;p>答案取决于你问谁。在过去的 30 年里，测试自动化供应商，如惠普，一直在推销 &amp;ldquo;记录/回放 &amp;ldquo;和 &amp;ldquo;对象识别工具 &amp;ldquo;的概念。尽管这些方法已经被证明是无效的，但一些供应商仍然坚持使用这些方法。&lt;/p>
&lt;p>在一个典型的软件团队中，众多的软件工程师和经理可能认为 &amp;ldquo;测试自动化 &amp;ldquo;是一个简单的任务，尽管没有人见过测试自动化的成功实施（也就是说，团队完全依靠手工测试）。作为证据，许多招聘广告要求申请人有 &amp;ldquo;创建测试自动化框架 &amp;ldquo;的经验，这似乎很荒谬。&lt;/p>
&lt;p>所以，端到端的测试自动化可能并不像许多人想象的那样简单和容易。&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;根据我的经验，优秀的开发人员不一定能成为优秀的测试人员，但优秀的测试人员（同时具有很强的设计能力）可以成为优秀的开发人员。这是一种心态和一种激情。&amp;hellip;他们是黄金&amp;rdquo;。&lt;/p>
&lt;ul>
&lt;li>谷歌副总裁帕特里克-科普兰，在一次采访中（2010 年）&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;blockquote>
&lt;p>&amp;ldquo;95%的时间，95%的测试工程师会写出糟糕的 GUI 自动化，只是因为它是一件非常难做的事情。&lt;/p>
&lt;ul>
&lt;li>这篇来自微软测试大师 Alan Page 的采访（2015 年）&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;blockquote>
&lt;p>&amp;ldquo;测试比开发更难。如果你想有好的测试，你需要把你最好的人放在测试中&amp;rdquo;。&lt;/p>
&lt;ul>
&lt;li>Gerald Weinberg，在一个播客中（2018 年）。&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;p>考虑到事实和上述知名专家的引言，似乎完成端到端的测试自动化是一个无法克服的挑战。&lt;/p>
&lt;p>我的答案是 &lt;strong>&amp;ldquo;它可以很简单，但往往是人为的错误使它变得不必要的困难。&amp;rdquo;&lt;/strong>&lt;/p>
&lt;h3 id="许多人做出了明显的错误的决定">许多人做出了明显的错误的决定&lt;/h3>
&lt;p>我见过错误的决定，往往不止一个，在失败的测试自动化尝试中。&lt;/p></description></item><item><title>两周内录完selenium教程是一种什么样的体验？</title><link>https://itest2025.pages.dev/selenium-tutorial-in-two-weeks_65/</link><pubDate>Thu, 03 Mar 2022 09:03:10 +0800</pubDate><guid>https://itest2025.pages.dev/selenium-tutorial-in-two-weeks_65/</guid><description>&lt;p>2021 年其实立过 flag，但最终事情太多，没有完成既定的目标。2022 年初的时候，想到很久都没有做过教程和视频了，于是兴冲冲的录了一些 selenium 的全套教程，目录如下：&lt;/p>
&lt;ul>
&lt;li>环境搭建&lt;/li>
&lt;li>跑个脚本先&lt;/li>
&lt;li>第 1 个自动化用例&lt;/li>
&lt;li>前进和后退&lt;/li>
&lt;li>搞定被测项目&lt;/li>
&lt;li>使用 id 定位&lt;/li>
&lt;li>使用 name 定位&lt;/li>
&lt;li>使用 xpath 定位：定位王者 xpath&lt;/li>
&lt;li>使用 tag name 定位&lt;/li>
&lt;li>使用 class name 定位&lt;/li>
&lt;li>使用 css 选择器定位&lt;/li>
&lt;li>往文本框里输入内容&lt;/li>
&lt;li>执行 js 脚本&lt;/li>
&lt;li>滑动到页面的最底部&lt;/li>
&lt;li>各种表单元素的操作&lt;/li>
&lt;li>显示等待&lt;/li>
&lt;li>page object&lt;/li>
&lt;li>常见问题答疑&lt;/li>
&lt;/ul>
&lt;h3 id="做的不足的地方">做的不足的地方&lt;/h3>
&lt;p>因为录制的时间比较短，所以影片的编辑和准备其实是不够充分的，教程应该能看，不过节奏感不好，毕竟年纪大了，有时候会显得啰嗦了一些。&lt;/p>
&lt;p>另外 selenium 的一些新特性其实并没有涉及，后面有时间可以重点补充一节。&lt;/p>
&lt;h3 id="亮点">亮点&lt;/h3>
&lt;p>当然，教程也是有一些亮点的。&lt;/p>
&lt;ul>
&lt;li>脉络基于官方文档，整体的思路还是清楚的；&lt;/li>
&lt;li>修正了一些官方文档的不足；&lt;/li>
&lt;li>使用本地文件进行演示，不依赖网络，脚本的稳定性还是比较高的&lt;/li>
&lt;/ul>
&lt;h3 id="发布节奏">发布节奏&lt;/h3>
&lt;p>目前 b 站和微信公众号都在同步更新，每周更新 4-5 集，目前录了 18 集，应该 1 个月就能更新完吧。&lt;/p>
&lt;h3 id="后续计划">后续计划&lt;/h3>
&lt;p>准备专门弄一个软件测试视频教程分享站点，代码还没写，不过大致的思路有了，啥时候不忙了就可以开工了。&lt;/p></description></item><item><title>selenium 4 0新特性及新旧api对比</title><link>https://itest2025.pages.dev/selenium-4-released_61/</link><pubDate>Thu, 03 Mar 2022 09:03:04 +0800</pubDate><guid>https://itest2025.pages.dev/selenium-4-released_61/</guid><description>&lt;p>众所周知，java 语言版本的 selenium 一般被认为是最正宗的 selenium 版本，今天我们以 java 语言为例，来看看 selenium 4.0 的各种新特性以及新旧 api 的对比。&lt;/p>
&lt;h3 id="capabilities">&lt;strong>Capabilities&lt;/strong>&lt;/h3>
&lt;p>如果你需要对浏览器进行一些全局设置，那么使用 Capabilities 是唯一的选择。说实话，旧的 Capabilities 有点不太符合直觉，具体用法如下。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">DesiredCapabilities&lt;/span> &lt;span class="n">capabilities&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">DesiredCapabilities&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">chrome&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">capabilities&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setCapability&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;platform&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Mac OS X&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">capabilities&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setCapability&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;version&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;94&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">RemoteWebDriver&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">capabilities&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在新版本中，我们直接设置 options 就可以了，语义上显得更为自然。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ChromeOptions&lt;/span> &lt;span class="n">options&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ChromeOptions&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">options&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setBrowserVersion&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;94&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">options&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setPlatformName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Mac OS X&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ChromeDriver&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">options&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="waits">Waits&lt;/h3>
&lt;p>在之前的版本里，我们实例化各种 wait 对象时候需要传入 2 个参数：time 以及 type of time，在新版本里我们只需要使用 Duration 类就可以了。&lt;/p>
&lt;p>这是之前的做法&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">manage&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">timeouts&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">implicitlyWait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TimeUnit&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SECONDS&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">manage&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">timeouts&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">pageLoadTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TimeUnit&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SECONDS&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">manage&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">timeouts&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">setScriptTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TimeUnit&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SECONDS&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>新的方式&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">manage&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">timeouts&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">implicitlyWait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">10&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">manage&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">timeouts&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">pageLoadTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofMinutes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">manage&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">timeouts&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">setScriptTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofHours&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>当然，现在支持各式各样的 Duration 了，需要注意的是这里接受的是 long 型的参数。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofNanos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="n">nanos&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofMillis&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="n">millis&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="n">seconds&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofMinutes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="n">minutes&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofHours&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="n">hours&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofDays&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="n">days&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>当然，我们还可以直接设置浏览器的各种全局等待时间，代码上看观感好了不少。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ChromeOptions&lt;/span> &lt;span class="n">options&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ChromeOptions&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">options&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setImplicitWaitTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">10&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">options&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setScriptTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">10&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">options&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setPageLoadTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ofSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">10&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="相对定位器">相对定位器&lt;/h3>
&lt;p>一些哲学流派告诉我们，世界是变化的，相对的，没有绝对的静，也没有绝对的动，物体总是相对着其他物体进行着运动。&lt;/p></description></item><item><title>惨遭打脸：字节某部门竟然有这么多测试</title><link>https://itest2025.pages.dev/%E6%83%A8%E9%81%AD%E6%89%93%E8%84%B8%E5%AD%97%E8%8A%82%E6%9F%90%E9%83%A8%E9%97%A8%E7%AB%9F%E7%84%B6%E6%9C%89%E8%BF%99%E4%B9%88%E5%A4%9A%E6%B5%8B%E8%AF%95_58/</link><pubDate>Thu, 03 Mar 2022 09:02:59 +0800</pubDate><guid>https://itest2025.pages.dev/%E6%83%A8%E9%81%AD%E6%89%93%E8%84%B8%E5%AD%97%E8%8A%82%E6%9F%90%E9%83%A8%E9%97%A8%E7%AB%9F%E7%84%B6%E6%9C%89%E8%BF%99%E4%B9%88%E5%A4%9A%E6%B5%8B%E8%AF%95_58/</guid><description>&lt;p>之前发过一篇公众号文章，大意是字节某些部门在推进去测试化，当时就有同学留言表示，在字节的某些部门测试同学其实很多，果不其然，昨天跟一位字节的候选人聊天，候选人就表示在他所在的部门，测试同学确实不少。&lt;/p>
&lt;p>他表示他们部门的测试主要有两种编制的同学构成，分别是正式和外包，正式员工跟外包的比例是 1：2，整个部门有几百人的测试规模。&lt;/p>
&lt;p>由于时间关系，我也没仔细了解这几百人全部是正式员工还是外包员工，也没弄清楚他们部门一共有多少人，不过从一个单体产品的角度上看，这几百人的规模还是比较大的。&lt;/p>
&lt;p>这些信息拼凑起来，我们可以知道大公司的去测试化进度并不是整个公司统一的，可能有些部门比较激进，有些部门就保守一些，测试同学的数量很多，但可能是外包为主。比如很多年前手 Q 团队的测试人数特别多，但是正式员工其实数量有限，大部分的功能测试都是外包同学，华为也类似。&lt;/p>
&lt;p>那么字节这个部门的测试同学在做些什么呢？&lt;/p>
&lt;p>候选人表示在 4 个方面进行探索。&lt;/p>
&lt;ul>
&lt;li>质量维度。负责产品的质量保障工作，这点大家其实很熟悉了；&lt;/li>
&lt;li>安全维度。负责产品的安全测试和安全方面的各种建设，其实候选人也表示这里的安全可能不够准确，其实应该是各种专项维度，比如性能稳定性之类的，不会只局限于安全；&lt;/li>
&lt;li>体验维度。这里是最有意思的，我们可以想一想，影响用户体验的是哪些因素？是不是应用的打开速度，给你推送内容的精准度之类？这里的体验就是量化可以影响用户体验的指标，然后持续监控指标，保障指标的值持续达标；比如持续监控 app crash 率，一旦大于某个阈值就告警，然后紧急进行定位和修复；&lt;/li>
&lt;li>效率维度。通过工具或其他活动提升测试效率。这里可以理解为偏测试开发的部分，比如开发测试工具提高自动化回归的效率等；&lt;/li>
&lt;/ul>
&lt;p>所以总结起来，测试同学在字节该部门的关键字其实是：质量+专项+体验+效率，质量这边其实有大量的活动是外包同学可以去承担的，其他的部分专业性比较强，自行做团队建设可能会更好一点。&lt;/p>
&lt;p>总之在同一个公司的不同部门里，对于测试的态度可能是不尽相同的，之前讨论的腾讯去测试化也是腾讯的某一个 bg，可能不一定是整个公司的行为。&lt;/p>
&lt;p>另外去测试化不是说完全不要测试，而是之前的一些必要的测试工作用一些成本更低的方式去做，比如外包。另外提高测试效率从另一方面说也就是降低测试的成本，是不是感觉有点卷起来了？想反内卷？字节的测试同学其实给我们提供了一个很好的思路，那就是换赛道。目前字节的测试有了 4 个赛道，而且除了质量赛道之外其他的赛道其实专业性还是很强的，所以也许在未来测试这边会分的越来越细，专业化越来越强，赛道越来越多。在某个赛道做专才或者在大部分赛道做通才都是不错的反内卷的方式吧。&lt;/p></description></item></channel></rss>