片状测试

有什么比“红色测试”更令人不快的? 测试是绿色还是红色,目前尚不清楚原因。 在2017年Heisenbug莫斯科会议上, 安德烈·索尔采夫( Andrei Solntsev) (Codeborne)谈到了它们为什么会出现以及如何减少其数量。 他的报告中的例子是,当您与它们发生碰撞时,您会直接在皮肤上感到疼痛。 这些技巧非常有用-值得认识测试人员和开发人员。 出乎意料的事情是:您可以发现有时候脱离屏幕与女儿一起玩魔方时,有时会发现一个问题。

结果,听众对报告表示赞赏,我们不仅决定发布视频,而且还为Habr制作报告的文本版本。



我认为,易碎测试是自动化领域中最相关的主题。 因为问题“世界正在做什么,您如何使用自动化?” 所有人都回答:“没有稳定性! 我们的测试会定期下降。”

您在您的位置进行了测试,它是绿色的,另外两天是绿色的,然后一次又一次突然落在詹金斯身上。 您尝试重复一次,将其启动,然后再次变为绿色。 最后,您永远不知道:这是一个错误还是只是葡聚糖测试? 并且每次您需要了解。

通常,在每晚对詹金斯(Jenkins)进行测试之后,测试人员会首先看到“ 30项测试下降了,您需要学习”,但是每个人都知道接下来会发生什么...



您当然猜到了什么不雅词伪装成:“我会重新开始。” 就像,“今天没有不愿意了解...”,这通常是这样发生的,这是一场真正的灾难。

没有确切的统计信息,但我经常从不同的人那里听说,他们有大约30%的测试-片状。 粗略地说,他们发射了1000颗,其中300颗周期性地呈红色,然后用手检查它们是否真的掉落了。

谷歌发表几年文章 :它说他们有1.5%的片状测试,并说明了他们如何减少数量。 我可以吹牛一点,说我在Codeborne的项目现在是0.1%。 但实际上,所有这些都是不好的,甚至是0.1%。 怎么了

占1.5%,这个数字似乎很小,但是实际上意味着什么呢? 假设一个项目中有上千个测试。 这可能意味着一个构建中进行了15个测试,然后进行了12个测试,然后进行了18个测试。这非常糟糕,因为在这种情况下,几乎所有构建都是红色的,因此您需要不断地动手检查其是否正确。

甚至我们的1 ppm(0.1%)仍然很差。 假设我们有1000个测试,那么0.1%表示通常有十个瀑布中的一个会受到1-2个红色测试的影响。 这是我们詹金斯一家的真实情况,结果是:一次奔跑,一次片状测试跌落,另一次奔跑。



事实证明,我们没有一天没有红色建筑。 由于有很多绿色,所以一切似乎都很好,但是客户有权问我们:“伙计们,我们付钱给您,您总是向我们提供红色!” 你在做什么?”

我会在客户的位置感到不满意,并解释说:“通常,这在行业中是正常的,一切对每个人都是红色的”,对吗? 因此,我认为这是一个非常紧急的问题,让我们一起了解如何处理它。

计划是这样的:

  1. 我收集的不稳定测试(根据我的实践,绝对真实的案例,复杂而有趣的侦探故事)
  2. 不稳定的原因(有些甚至需要花费多年的时间进行研究)
  3. 如何处理? (希望这将是最有用的部分)

因此,让我们从我的收藏开始,我非常看重它:这花费了我很多小时的时间进行调试。 让我们从一个简单的例子开始。

示例1:经典


对于种子-经典的Selenium脚本:

driver.navigate().to("https://www.google.com/"); driver.findElement(By.name("q")).sendKeys("selenide"); driver.findElement(By.name("btnK")).click(); assertEquals(9, driver.findElements(By.cssSelector("#ires .g")).size()); 

  1. 我们打开WebDriver;
  2. 找到元素q,在单词中驱动搜索。
  3. 找到“按钮”元素,然后单击;
  4. 检查答案是否为九个结果。

问题:哪条线可以在这里中断?

是的,我们都知道! 任何行都可以中断,原因完全不同:

第一行是互联网速度慢,服务崩溃,管理员未配置某些内容。

第二行-如果元素是动态绘制的,则还没有时间渲染。
第三行会打破什么? 这对我来说是出乎意料的:我为会议编写了此测试,在本地运行了该测试,由于该错误,它落在了第三行:



这表示此时元素不可单击。 这似乎是一个简单的基本Google表单。 秘密在于,在第二行中,我们按下了单词,而当我们输入单词时,Google已经找到了第一个结果,并在这样的弹出窗口中显示了前几个结果,然后他们关闭了下一个按钮。 这并非在所有浏览器中都发生,而且并非总是如此。 发生这种情况的原因是,此脚本大约每5个就有一次。

例如,第四行可能会落下,因为此元素是动态绘制的,还没有时间绘制。

在此示例中,我想说,根据我的经验,90%的片状测试基于相同的原因:

  • Ajax请求速度:有时它们运行较慢,有时更快;
  • Ajax请求的顺序;
  • 速度js。

幸运的是,由于这些原因,可以治愈! 硒化物解决了这些问题。 它如何决定? 我们在Selenide上重写了Google测试-几乎所有内容都看起来像它,只使用了$符号:

 @Test public void userCanLogin() { open(“http://localhost:8080/login”); $(By.name(“username”).setValue(“john”); $(“#submit”).click(); $(“.menu”).shouldHave(text(“Hello, John!”)); } 

该测试始终通过。 由于setValue(),click()和shouldHave()方法很聪明:如果没有时间绘画,它们会稍等一下再试一次(这被称为“智能期望”)。

如果您详细一点,那么所有这些*方法应该很聪明:



他们可以根据需要等待。 默认情况下,它们等待的时间最多为4秒,并且该超时当然是可配置的,您可以指定其他任何超时。 例如,如下所示:mvn -Dselenide.timeout = 8000。

示例2:nbob


因此,使用硒化硒可以解决90%的薄片测试问题。 但是,在复杂得多的案件中,有10%仍然有复杂而令人困惑的原因。 我今天想谈的正是他们,因为这是一个“灰色地带”。 让我举一个例子:一个不稳定的测试,我立即在一个新项目中遇到了。 乍一看,这根本不可能发生,但这很有趣。

我们测试了键盘应用程序的登录亭。 该测试希望以用户“ bob”的身份登录,即在“登录”字段中输入三个字母:bob。 为此,使用了屏幕上的按钮。 通常,这是可行的,但是有时测试会崩溃,并且值“ nbob”保留在“登录”字段中:



自然地,您正在努力地寻找我们本可以编写“ nbob”的代码-但是在整个项目中,这根本不是(数据库,代码,甚至Excel文件中都没有)。 这怎么可能?

我们将更详细地看一下代码-看起来一切都很简单,没有任何困惑:

 @Test public void loginKiosk() { open(“http://localhost:9000/kiosk”); $(“body”).click(); $(By.name(“username”)).sendKeys(“bob”); $(“#login”).click(); } 

我们开始进一步辩论,逐步进行,使用这种方法,我们设法理解:该错误有时出现在$(“ body”)行之后,单击()。 也就是说,在此步骤中,“ n”出现在“登录”字段中,然后在后续步骤中添加“ bob”。 谁已经猜出“ n”的来源?

碰巧字母N位于屏幕中间,并且至少在Chrome中,click()函数的工作方式如下:它计算元素的中心坐标并单击它。 由于身体是一个很大的元素,因此她单击了整个屏幕的中央。



而且这并不总是会失败。 谁知道为什么? 其实我自己并不完全知道。 可能是由于浏览器窗口始终以不同的大小打开的事实,并且并不总是以字母N开头。

您可能有一个问题:为什么有人赚了$(“ body”),然后点击()? 我也不知道到底如何,但我想将重点从田野上移开。 Selenium中有一个问题,单击()是,但不单击()不是。 如果该字段中有焦点,则无法从该焦点中删除它,您只能单击任何其他元素。 而且由于没有其他合理的元素,他们点击了身体,并获得了这种效果。

因此,道德道理:请勿将任何插入<body>的内容插入。 换句话说,您无需在紧急情况下进行任何额外的移动。 实际上,这种情况经常发生:自从我与Selenide打交道以来,我经常会收到“有些事情不起作用”的投诉,然后事实证明,在设置方法的某个地方,有15条多余的行没有任何用处并干扰。 无需大惊小怪,无论如何都要插入测试,例如“突然之间将更加可靠”。

因此,我们扩展了测试不稳定的原因:

  • Ajax请求速度;
  • Ajax请求的顺序;
  • 速度js;
  • 浏览器窗口大小;
  • 虚荣!

同时,我的建议是:不要以最大化的速度运行测试(即,不要在全窗口中打开浏览器)。 通常,每个人都这样做,在Selenide中,默认情况下(或仍然是)。 相反,我建议您始终以严格定义的屏幕分辨率启动浏览器,因为这样会排除此随机因素。 并且我建议您根据规范设置应用程序支持的最小大小。

示例3:幻影帐户


一个有趣的例子是,所有只能重合的事物都会立即重合。

进行了一项测试,检查此屏幕上应该有5个帐户。



通常,它是绿色的,但有时不清楚它在什么条件下跌落,并说屏幕上没有5个,而是6个。

我开始研究额外账单的来源。 绝对难以理解。 问题出现了:也许我们还有另一个测试,在测试期间会创建一个新帐户? 事实证明,是的,存在这样的LoansTest。 在它与下降的AccountsTest(预计有五个帐户)之间,可能会有一百万个其他测试。

我们正在尝试了解它是怎么回事:创建帐户的LoansTest不应在最后删除它吗? 我们看一下它的代码-是的,最后应该有一个After函数。 然后,从理论上讲,一切都应该没问题,这是什么问题?

也许测试将其删除,但仍保留在某个地方? 我们看一下加载帐户的生产代码-它确实具有@CacheFor批注,它将帐户缓存了五分钟。

问题出现了:但是测试是否应该清除此缓存? 这是合乎逻辑的,难道不会有这样的门槛吗? 我们看一下它的代码-是的,它确实在每次测试前都会清除缓存。 怎么了 在这里,您已经迷失了方向,因为假设已经结束:对象被删除,缓存被清除,树形棍子,还有什么问题呢? 然后他开始只是爬升代码,花了一些时间,甚至几天。 直到我最终查看了此类和超类,然后在那里发现了一个可疑的东西:



已经有人注意到了吧? 没错:在子类和父类中,有一个同名的方法,它不会调用super。

在Java中,这非常容易做到:您可以在IntelliJ IDEA或Eclipse中按Alt + Enter或Ctrl + Insert,默认情况下,它会为您创建setUp()方法,您不会注意到它会覆盖超类中的方法。 也就是说,仍然没有调用缓存。 当我看到这个时,我非常生气。 现在对我来说是快乐的。

因此,道德:

  1. 在测试中,监视干净的代码非常重要。 如果在生产代码中每个人都注意这一点,他们将进行代码审查,然后在测试中-并非总是如此。
  2. 如果生产代码已通过测试验证,那么谁来测试测试? 因此,在IDE中使用检查尤为重要。

发生此事件之后,我在IDEA中发现这样的检查,默认情况下处于关闭状态,该检查检查:如果该方法在某处被重写,但是没有@ Overrid注释,则将其标记为错误。 现在,我总是歇斯底里地选中此框。

让我们再次总结一下:这是怎么发生的,为什么测试不总是失败? 首先,它取决于这两个测试的顺序;它们始终以随机顺序运行。 另一个测试取决于它们之间经过了多少时间。 帐户被缓存五分钟,如果更多通过,则测试为绿色,如果更少,则失败,这很少发生。

我们扩展了为什么测试可能不稳定的列表:

  • Ajax请求速度;
  • Ajax请求的顺序;
  • 速度js;
  • 浏览器窗口大小;
  • 应用程序缓存;
  • 先前测试的数据;
  • 时间

示例4:Java时间


有一项测试可以在我们所有的计算机和Jenkins上运行,但有时会在Jenkins客户上崩溃。 我们看一下测试,了解原因。 事实证明它正在下降,因为当选中“付款日期应该是现在或过去”时,结果证明是“将来”。

 assert payment.time <= new Date(); 

我们看一下代码,在某些情况下突然可以确定将来的日期吗? 我们不能:在唯一初始化付款时间的地方,使用新的Date(),并且始终是当前时间(在极端情况下,如果测试非常缓慢,则可能是过去的时间)。 这怎么可能? 他们打了很长时间,无法理解。

并且一旦他们查看了应用程序日志。 因此,第一个道德准则–在检查测试以查看应用程序本身的日志时非常有用。 举起你的手,是谁做的。 总的来说,不是大多数。 并且有一些有用的信息:例如,请求日志,某某时间执行某某URL,给出某某某答案。



请注意,这里有可疑的东西吗? 我们看一下时间:这个请求已处理了三秒钟。 怎么会这样 他们战斗了很长时间,无法理解。 最后,当我们超出理论范围时,我们做出了一个愚蠢的决定:詹金斯编写了一个简单的脚本,该脚本每秒记录一次当前时间。 启动了它。 第二天,当这个片状测试在晚上一次跌落时,他们开始观看该文件跌落时的摘录:



所以:34秒,35、36、37、35、39 ...我们发现它很酷,但是怎么可能呢? 理论又结束了,又过了两天。 Matrix在和您开玩笑时,确实是这样,对吗?

直到最后一个主意击中我……事实证明是这样。 Linux具有在中央服务器上运行的时间同步服务,并询问“现在有几毫秒?” 事实证明,在这个特定的詹金斯上推出了两种不同的服务。 在此服务器上更新Ubuntu时,测试开始崩溃。

在那里,先前已配置了ntp服务,该服务访问特殊的银行服务器并从那里花费时间。 在新版本的Ubuntu中,默认情况下包括新的轻量级服务,例如systemd-timesyncd。 两者都起作用。 没人注意到这一点。 由于某种原因,中央银行服务器和一些中央Ubuntu服务器发出了3秒的差异响应。 自然,这两种服务相互干扰。 在Ubuntu文档的某个深处,它表示当然不允许这种情况...好,谢谢您提供的信息:)

顺便说一下,与此同时,我学到了Java的一个有趣的细微差别,在此之前,尽管我有多年的经验,但还不知道。 Java中最基本的方法之一称为System.currentTimeMillis(),通常借助它来定时调用某些东西,许多人编写了这样的代码:

 long start = System.currentTimeMillis(); // ... long end = System.currentTimeMillis(); log.info("Loaded in {} ms", end-start); 

这样的代码在Apache Commons的Guava库中。 也就是说,如果您需要检测调用某个东西花费了多少毫秒,那么他们通常会这样做。 许多人可能听说过不应该这样做。 我也听到了,但不知道为什么,也懒得理解。 我以为问题是因为System.nanoTime()出现在Java的某些版本中-更准确,它产生的纳秒精度是百万倍。 而且,由于我的通话通常持续一秒钟或半秒钟,因此该准确性对我而言并不重要,因此我继续使用System.currentTimeMillis(),我们在日志中看到的时间为-3秒。 因此,事实上,正确的方法是这样,现在我发现了原因:

 long start = System.nanoTime(); // ... long end = System.nanoTime(); log.info("Loaded in {} ms", (end-start)/1000000); 

实际上,这是在方法文档中编写的,但是我从未读过。 我一生都以为System.currentTimeMillis()和System.nanoTime()是同一件事,只是相差一百万倍。 但事实证明,这些根本不同。

System.currentTimeMillis()返回实际的当前日期-自1970年1月1日以来现在的毫秒数。 System.nanoTime()是一种不受实时限制的抽象计数器:是的,可以保证每单位纳秒级的增长,但是它与当前时间无关,甚至可以是负数。 在JVM开始时,以某种方式随机选择了一个时间点,并且该时间点开始增长。 这让我感到惊讶。 对你也一样 好吧,他来并没有白费。

示例5:绿色按钮的诅咒


在这里,我们的测试填写了一定的表格,单击绿色的“确认”按钮,有时它不会继续进行。 为什么它不走是不可理解的。



我们以四个零开始行驶并挂起,请勿转到下一页。 单击发生而没有错误。 我查看了所有内容:Ajax请求,等待,超时,应用程序日志,缓存-我什么都没找到。 Sergey Pirogov编写的Video Recorder库尚未出现。 它允许在代码中添加一个注释来录制视频。 然后,我可以拍摄该测试的视频 ,并以慢动作观看它,这最终澄清了在视频播放前几个月我都无法解决的情况。



进度条将按钮阻塞了片刻,而单击恰好在此时起作用,然后单击了此进度条。 也就是说,进度条单击并消失了! 而且它在任何屏幕截图,任何日志中都不可见,您将永远不知道发生了什么。

原则上,从某种意义上讲,这是一个应用程序错误:出现进度条是因为该应用程序确实爬出了屏幕的边缘,如果滚动,它会显示很多有用的数据。 但是用户并没有抱怨,因为所有内容都可以在大屏幕上显示,而不仅仅是在小屏幕上可以显示。

示例6:为什么Chrome冻结?


两年侦探调查是绝对真实的案例。 情况是这样的:我们的测试经常会掉落并掉落,并且在堆栈痕迹中很明显Chrome冻结了:不是我们的测试,即Chrome。 在日志中可以看到“构建正在运行36个小时...”,他们开始删除线程转储和堆栈跟踪-它们显示测试中一切正常,对Chromedriver的调用被挂起,并且通常在关闭时(我们称为close方法,并且此方法无效,将挂起36个小时)。 如果有趣,则堆栈跟踪如下所示:



我们试图做所有想到的事情:

  • 配置打开/关闭浏览器的超时时间(如果您无法在15秒内打开/关闭浏览器,请在15秒后重试,最多尝试3次)。 在单独的线程中打开和关闭浏览器。 结果:所有三个尝试都以相同的方式挂起。
  • 终止旧的Chrome进程。 他们在Jenkins的“ kill-chrome”中创建了一个单独的作业,例如,您可以“杀死”一个小时以上的所有进程:

    killall-早于1h chromedriver
    killall-比1h铬版早

    这至少释放了内存,但是没有回答“正在发生什么?”这个问题。 实际上,这件事只会延迟我们做出决定的时间。
  • 启用调试应用程序日志。
  • 启用WebDriver调试日志。
  • 每20个测试后重新打开浏览器。 看起来似乎很荒谬,但当时的想法是:“如果Chrome因为累而冻结了怎么办?” 好吧,内存泄漏或其他问题。

上一次尝试的结果是完全出乎意料的:问题开始更加频繁地重复! 而且我们希望这将有助于稳定Chrome,使其运行更好。 这通常是大脑的外卖。 但是实际上,当问题开始再次出现时,应该不会感到难过,而应该会感到高兴! 这样可以更好地研究它。 如果她开始重复很多次,应该坚持下去:“是的,是的,现在我要添加其他内容,日志,断点...”

我们正在尝试重复这个问题:我们写了一个从1到1000的循环,在这个循环中,我们只是打开浏览器,然后关闭应用程序中的第一页。 写了这样的循环,宾果游戏! 结果:问题开始稳定地重复(尽管大约每80次迭代)! 好酷! 没错,这个成就很长一段时间都没有给任何东西。 您启动了它,等待第80次迭代,Chrome崩溃了……然后该怎么办? 您查看堆栈跟踪,转储,日志-那里没有任何有用的信息。 Chrome中的开发者工具可能会有所帮助,但直到2017年9月,这些工具才不能与Selenium一起使用(端口发生冲突:您从Selenium启动Chrome,DevTools无法打开)。 很长一段时间我都没想到该怎么办。

这个故事从这里开始了一个神话般的时刻。 一次,经过无数次尝试,我再次运行了这些测试,它再次像第56次那样再次挂起,我认为“让我们挖掘其他东西”(尽管我不知道在哪里放置断点或什么东西添加日志)。 此刻,我的女儿愿意玩立方体,但我的测验只是挂在这里。 我说:“等等,”她告诉我:“什么,你不明白,我在这里有b和a !”

该怎么办,可悲的是离开电脑,去玩魔方……突然间,大约20分钟后,我不小心瞥了一眼屏幕,看到了一张完全出乎意料的画面:



发生了什么:倒计时,会话终止多少分钟后,我建立了一个立方体塔,有两个,一个...会话终止,测试继续进行,运行到最后并落下(不再有任何元素,会话已终止)。

发生的情况:Chrome并没有真正冻结,正如我们一直以来所认为的那样,Chrome一直在等待着某些事情。 会话到期后,请等待。 Chrome到底在期待什么-理解这一点是完全不可理解的,我不得不使用二进制搜索方法来清理所有代码:扔掉一半的JavaScript和HTML,尝试再次重复80次迭代-它没有挂起,哦,那意味着在某处……一般来说,我们通过实验来理解问题出在这里:

 var timeout = setTimeout(sessionWatcher); 

JavaScript — , , . , JavaScript- , : , <script> . , , , , . JavaScript — jQuery, $, function , :

 var timeout; $(function() { timeout = setTimeout(...); }); 

-, , , . , . 1000 , .

, : , , , . , Chrome, . , .

, flaky- , , , . , — , , ( ). , . , : ?

Chrome flaky-: -, , , .

UI- : , . click(), , . , , : click() , . - , ? :)

. , , , . , , , , Docker.

, - , . :

  • Ajax-;
  • Ajax-;
  • JS;
  • ;
  • ;
  • ;
  • ;
  • ;
  • UI-;
  • ( ).

flaky- , . , -, : , .

. «» . , flaky- , ID, flaky- . .

, : , , .

, flaky- usability, -, flaky- . .

, , … , , , flaky- security-, . , !

, , flaky-:

  • ;
  • Selenide;
  • ;
  • .

— . flaky- , unit- , UI-? , ( ), , flaky.

Selenide .

. (, / ). , « ?». , , . , .

, . , , , , . : « », , (10 , 20 , — , — ). , flaky - .

, flaky- :

  1. ;
  2. ;
  3. 录影带

« » , , , : - . «», «» , : flaky-, , flaky. , , . . , Jenkins pipeline, Jenkins :

 finally { stage("Reports") { junit 'build/test-results/**/*.xml' artifacts = 'build./reports/**/*,build/test-results/**/*,logs/**/*' archiveArtifacts artifacts: artifacts } } 

finally , . : - - . Jenkins , . , Jenkins , . , .

(Selenide , ). flaky-. , Video Recorder , : video — , !

, Docker: TestContainers ( Heisenbug ). Rule , — Docker , , . .

 @Rule public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer() .withRecordingMode(RECORD_ALL, new File("build")) .withDesiredCapabilities(chrome()); 

.

. , , , , , . flaky- .

, ! , :) . , « , , », , .

— , , , , - . — , … ! :) flaky- — : !

, : 6-7 Heisenbug . , , . (, , ) .

Source: https://habr.com/ru/post/zh-CN416757/


All Articles