paint-brush
Четыре всадника нестабильных тестовк@truuts
1,367 чтения
1,367 чтения

Четыре всадника нестабильных тестов

к Eugene Truuts6m2023/10/24
Read on Terminal Reader

Слишком долго; Читать

Ненадежные автоматизированные тесты могут стать проклятием для инженеров по обеспечению качества, внося неопределенность и подрывая надежность наборов тестов. Основываясь на моем опыте наставника SDET и QA Automation, эта статья предлагает практические советы по преодолению нестабильности. Хотя я приведу примеры JavaScript и Playwright, эти универсальные советы применимы к любому языку и любой платформе и помогут вам писать надежные и надежные тесты автоматизации. Давайте углубимся и убедимся, что ваши тесты верны.
featured image - Четыре всадника нестабильных тестов
Eugene Truuts HackerNoon profile picture
0-item
1-item


Ненадежные автоматизированные тесты могут стать проклятием для инженеров по обеспечению качества, внося неопределенность и подрывая надежность наборов тестов. Основываясь на моем опыте наставника SDET и QA Automation, эта статья предлагает практические советы по преодолению нестабильности. Хотя я приведу примеры 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. Например, вы можете настроить автоматические повторы для каждого неудачного теста, указав максимальное количество повторов — три раза. Любой тест, который изначально не пройден, будет повторяться до трех раз, пока цикл выполнения теста не завершится. Преимущество состоит в том, что если ваш тест лишь слегка нестабильный, он может пройти после пары повторных попыток.


Однако недостатком является то, что он может выйти из строя, что приведет к дополнительному потреблению времени выполнения. Более того, такая практика может непреднамеренно привести к накоплению нестабильных тестов, так как может показаться, что многие тесты «пройдены» со второй или третьей попытки, и вы можете ошибочно пометить их как стабильные. Поэтому я не рекомендую устанавливать автоматические повторные попытки глобально для всего вашего тестового проекта. Вместо этого используйте повторные попытки выборочно в тех случаях, когда вы не можете быстро устранить основные проблемы в тесте.


Например, представьте, что у вас есть тестовый пример, в котором вы должны загрузить несколько файлов с помощью события filechooser. Вы получаете сообщение об ошибке <Timeout превышено при ожидании события «filechooser»>, которое указывает на то, что тест «Драматург» ожидает возникновения события «filechooser», но занимает более длительный тайм-аут.


 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++; } } }


Такой подход сэкономит дополнительное время и сделает ваш тест менее нестабильным.


В заключение хочу сказать, что путь к надежным автоматизированным тестам прост. Вы можете свести к минимуму или активно решать общие проблемы и реализовывать разумные стратегии. Помните, что этот путь продолжается, и с настойчивостью ваши тесты станут более надежными. Попрощайтесь с нестабильностью и поприветствуйте стабильность. Ваша приверженность качеству обеспечивает успех проекта. Приятного тестирования!


Также опубликовано здесь .