paint-brush
Flaky 测试的四骑士by@truuts
1,333
1,333

Flaky 测试的四骑士

Eugene Truuts6m2023/10/24
Read on Terminal Reader

不稳定的自动化测试对于 QA 工程师来说可能是一个祸根,它会引入不确定性并破坏测试套件的可靠性。本文根据我作为 SDET 和 QA 自动化导师的经验,提供了克服不稳定的实用建议。虽然我将提供 JavaScript 和 Playwright 示例,但这些通用技巧适用于任何语言和框架,帮助您编写健壮且可靠的自动化测试。让我们深入研究并确保您的测试站稳脚跟。
featured image - Flaky 测试的四骑士
Eugene Truuts HackerNoon profile picture
0-item
1-item


不稳定的自动化测试对于 QA 工程师来说可能是一个祸根,它会引入不确定性并破坏测试套件的可靠性。本文根据我作为 SDET 和 QA 自动化导师的经验,提供了克服不稳定的实用建议。虽然我将提供 JavaScript 和 Playwright 示例,但这些通用技巧适用于任何语言和框架,帮助您编写健壮且可靠的自动化测试。让我们深入研究并确保您的测试站稳脚跟。

1.避免隐式等待方法

有时,您会遇到必须等待元素出现在 DOM 中或等待应用程序达到特定状态才能继续测试场景的情况。即使使用像 Playwright 的自动等待功能这样的现代智能自动化框架,在某些情况下您仍然需要实现自定义等待方法。例如,考虑一个带有文本字段和确认按钮的场景。由于特定的服务器端行为,确认按钮仅在完成表单大约五秒后才可见。在这种情况下,抵制在两个测试步骤之间插入五秒隐式等待的诱惑至关重要。


 textField.fill('Some text') waitForTime(5000) confirmationButton.click()


请改用聪明的方法并等待确切的状态。它可以是在某些加载后出现的文本元素,也可以是在当前步骤成功完成后消失的加载器旋转元素。您可以检查是否已准备好进行下一步测试。


 button.click() waitFor(textField.isVisible()) textField.fill()


当然,在某些情况下,您需要等待一些无法巧妙检查的内容。我建议在这些地方添加注释,甚至添加原因解释作为等待方法中的参数或类似的内容:


 waitForTime(5000, {reason: "For unstable server processed something..."}


使用它的另一个好处是测试报告,您可以在其中存储此类解释,这样其他人甚至将来的您都会很清楚什么以及为什么在这里等待五秒钟。


 waitForTime(5000, {reason: "For unstable server processed something..."} button.click() waitFor(textField.isVisible()) textField.fill()

2. 使用稳健可靠的定位器来选择元素

定位器是自动化测试的重要组成部分,这是每个人都知道的。然而,尽管有这个简单的事实,许多自动化工程师还是担心稳定性并在测试中使用类似的东西。


 //*[@id="editor_7"]/section/div[2]/div/h3[2]


这是一个愚蠢的方法,因为 DOM 结构不是那么静态;一些不同的团队有时可以更改它,并在测试失败后进行更改。因此,请使用强大的定位器。顺便说一句,欢迎您阅读我的 XPath 相关故事。



3. 使测试彼此独立

当在单个线程中运行包含多个测试的测试套件时,确保每个测试都是独立的至关重要。这意味着第一个测试应该将系统返回到其原始状态,以便下一个测试不会由于意外情况而失败。例如,如果您的一个测试修改了 LocalStorage 中存储的用户设置,则在第一次测试运行后清除“用户设置”的 LocalStorage 条目是一个好方法。这可确保第二次测试将使用默认用户设置执行。您还可以考虑将页面重置为默认条目页面以及清除 cookie 和数据库条目等操作,以便每个新测试都以相同的初始条件开始。


例如,在Playwright中,您可以使用beforeAll()、beforeEach()、afterAll()和afterEach()注释来实现。


通过几个测试检查下一个测试套件。第一个是更改用户的外观设置,第二个是检查默认外观。


 test.describe('Test suite', () => { test('TC101 - update appearance settings to dark', async ({page}) => { await user.goTo(views.settings); await user.setAppearance(scheme.dark); }); test('TC102 - check if default appearance is light', async ({page}) => { await user.goTo(views.settings); await user.checkAppearance(scheme.light); }); });


如果这样的测试套件并行运行,一切都会顺利进行。但是,如果这些测试逐一运行,您可能会面临失败的测试,因为其中一个测试会干扰另一个测试。第一个测试将外观从浅色变为深色。第二个测试检查当前外观是否较亮。但它会失败,因为第一个测试已经更改了默认外观。

为了解决这个问题,我添加了每次测试后执行的 afterEach() 钩子。在这种情况下,它将导航到默认视图并通过从本地存储中删除用户的外观来清除用户的外观。


 test.afterEach(async ({ page }) => { await user.localStorage(appearanceSettings).clear() await user.goTo(views.home) }); test.describe('Test suite', () => { test('TC101 - update appearance settings to dark', async ({page}) => { await user.goto(views.settings); await user.setAppearance(scheme.dark); }); test('TC102 - check if default appearance is light', async ({page}) => { await user.goTo(views.settings); await user.checkAppearance(scheme.light); }); });


使用这种方法,该套件中的每个测试都将是独立的。


4. 明智地使用自动重试

没有人能够避免遇到不稳定的测试,解决这个问题的一种流行的补救措施是使用重试。您可以将重试配置为自动发生,即使在 CI/CD 配置级别也是如此。例如,您可以为每个失败的测试设置自动重试,指定最大重试次数为 3 次。任何最初失败的测试都将重试最多 3 次,直到测试执行周期完成。优点是,如果您的测试只是稍微不稳定,重试几次后可能会通过。


然而,缺点是它可能会失败,从而导致额外的运行时消耗。此外,这种做法可能会无意中导致不稳定测试的积累,因为许多测试可能在第二次或第三次尝试时似乎“通过”,并且您可能会错误地将它们标记为稳定。因此,我不建议为整个测试项目全局设置自动重试。相反,在无法立即解决测试中的根本问题的情况下,有选择地使用重试。


例如,假设您有一个测试用例,您必须使用“filechooser”事件上传一些文件。您会收到<等待事件“文件选择器”时超出超时>错误,这表明 Playwright 测试正在等待“文件选择器”事件发生,但需要更长的 ied 超时时间。


 async function uploadFile(page: Page, file) { const fileChooserPromise = page.waitForEvent('filechooser'); await clickOnElement(page, uploadButton); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(file); }


这可能是由于多种原因造成的,例如应用程序中的意外行为或缓慢的环境。因为它是随机行为,所以您首先想到的是使用自动重试来进行此测试。但是,如果您重试整个测试,则可能会浪费额外的时间并获得与 uploadFile() 方法本身中第一个错误相同的行为,因此如果发生错误,您无需重试整个测试。


为此,您可以使用标准的 try...catch 语句。


 async function uploadFile(page: Page, file) { const maxRetries = 3; let retryCount = 0; while (retryCount < maxRetries) { try { const fileChooserPromise = page.waitForEvent('filechooser'); await clickOnElement(page, uploadButton); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(file); break; // Success, exit the loop } catch (error) { console.error(`Attempt ${retryCount + 1} failed: ${error}`); retryCount++; } } }


这种方法将节省额外的时间,并使您的测试不那么不稳定。


最后,可靠的自动化测试之路非常简单。您可以最大限度地减少或积极应对常见挑战并实施智能策略。请记住,这个旅程正在进行中,只要坚持不懈,您的测试就会变得更加可靠。告别不稳定,迎接稳定。您对质量的承诺可确保项目成功。测试愉快!


也发布在这里