Я хотела бы поделиться с вами несколькими хорошими практиками, которым я научилась в процессе работы – но как бы мне хотелось их знать, когда я только начала свое путешествие в мир автоматизированного тестирования. Хотя то, что мы называем «лучшей практикой», не обязательно подойдет для конкретного проекта или специалиста. Читайте статью, и вы найдете советы о том, как можно улучшить свой фреймворк автоматизации на Selenium C#.

Соглашения о написании кода

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

Соглашения о наименовании

  • Используйте PascalCase для имен методов и классов.

// Good
public class HomePage
// Bad
public class homepage
public class homePage
  • Используйте camelCase для локальных переменных и параметров (первое слово начинается со строчной буквы, следующие слова - с верхнего регистра).

  • Не используйте аббревиатуры — для имени класса, метода или переменной нет ограничений по количеству символов, поэтому избегайте создания путаницы или несоответствий с помощью аббревиатур.

Избегайте использования символа подчеркивания (единственным исключением являются внутренние переменные).
// Good
private string _password = "12345";
HomePage homePage = new HomePage(driver);
// Bad
HomePage home_Page = new HomePage(driver);
  • Используйте слово «Test» в конце тестовых классов (например,  LoginTest, ShoppingCartTest и т. д.).

  • Используйте существительные для имен классов, и глаголы -- для методов.

Соглашения о разметке

  • Не используйте более одного оператора или объявления для каждой строки.

  • Комментарии должны быть добавлены в строку, предшествующую коду, а не после кода. Кроме того, постарайтесь сделать код как можно более понятным, чтобы его не нужно было комментировать.

    Пример хорошего форматирования комментариев:

// This methods sends the credentials.
public void Login(string name, string password)
{
      Name.SendKeys(name);
      Password.SendKeys(password);
      LoginButton.Click();
}

И пример того, как делать не стоит:

public void Login(string name, string password)
{
      Name.SendKeys(name); //sends the name
      Password.SendKeys(password); //sends the password
      LoginButton.Click();
}

Принцип DRY

DRY (Don't Repeat Yourself) является принципом, направленным на уменьшение дублирования кода. Он определяется как «Каждая часть информации должна иметь единое, однозначное, верное представление в системе».

Чтобы достичь принципа DRY, убедитесь, что вы избегаете дублирования кода. Например, если все тесты требуют входа пользователя в систему, создайте начальный метод, который выполняет вход и запускается перед каждым тестом. Или объявите свои веб-элементы как переменные, чтобы вы не искали их на странице каждый раз, когда вам нужно взаимодействовать с ними.

// Good:
private IWebElement name => driver.FindElement(By.CssSelector("#name"));
name.Click();
name.SendKeys("User Name");
// Bad:
driver.FindElement(By.CssSelector("#name")).Click();
driver.FindElement(By.CssSelector("#name")).SendKeys("User Name");

Независимые тесты

Это очень важный аспект хорошего и стабильного фреймворка для автоматизации. Если вы хотите, чтобы ваши тесты были надежными, не делайте один тест зависимым от другого, т. е. не создавайте тестовые данные для TestCase2 в TestCase1. Это может привести к неожиданным падениям тестов, и ценное время, потраченное на отладку и исправление, не будет фактически находить проблемы. Кроме того, если вы хотите начать параллелизацию тестов (над чем вам определенно стоит задуматься), вы не сможете контролировать порядок выполнения тестов, поэтому зависимый тест может выполниться до теста, от которого он зависит.

Принцип единой ответственности

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

Рассмотрите возможность использования методов предусловия и постусловия, чтобы отделить предварительные шаги (например, открытие браузера, вход в систему) от тестов, а также очистить после теста (закрыть браузер, удалить все данные, относящиеся к тесту). 

[SetUp]
public void Login()
{         
        runner.Run(new LoginTest());
}
[Test]
public void AddItemsToCart()
{         
        runner.Run(new AddItemsToCartTest());
}
[OneTimeTearDown]
public void TearDown()
{
        runner.Dispose();            
}

В приведенном выше примере используется NUnit с TestProject C# SDK. [SetUp] — это метод для входа пользователя в систему, а затем сам тест проверяет только то, что товары добавлены в корзину. Если с логином что-то не так, упадет только метод с предусловием, а тест не сможет продолжиться. Таким образом, мы будем знать, что проблема заключается в функциональности входа в систему, а не в добавлении товаров в корзину.

Ограничение количества проверок

Эта практика усиливает принцип единой ответственности. Имейте в виду, что тесты должны иметь одну, максимум две проверки. Если вы чувствуете необходимость добавления большего количества - подумайте о разделении вашего большого теста на более мелкие, более детальные, которые сосредоточены на меньшем количестве проверок.

Стратегия локаторов

Важной частью автоматизации пользовательского интерфейса является использование правильных локаторов. Стратегии локаторов, доступные в Selenium C#:

  • Id

  • Name

  • LinkText

  • PartialLinkText

  • TagName

  • ClassName

  • CssSelector

  • XPath

Наиболее рекомендуемой стратегией локатора является идентификатор (Id), если он доступен и уникален. Это самый быстрый локатор, а также он прост в использовании. Имя класса (ClassName) также является хорошей стратегией локатора, но оно также должно быть уникальным, что, как вы, вероятно, заметили, не всегда так.

Если ID и ClassName недоступны, следующим предпочтительным локатором является CssSelector, который представляет собой шаблон, использующий атрибуты и их значения. Ниже описано, как использовать CssSelector в C# для идентификации элемента div, следующего за элементом с ID=password:

driver.FindElement(By.CssSelector("#password ~div"));

После CssSelector следует XPath, который находит элементы в структуре DOM. LinkText и PartialLinkText доступны только в том случае, если элемент, который необходимо найти, является ссылкой, в противном случае они не могут быть использованы. Name и TagName следует использовать только в том случае, если они уникальны или когда необходимо найти несколько элементов с одинаковым именем или именем тега с помощью метода FindElements().

Чтобы определить лучший локатор для элемента, можно использовать средства разработчика в браузере.

Использование ожиданий

Избегайте использования Thread.Sleep()

Вы, наверное, слышали это раньше, но я скажу это еще раз. Thread.Sleep() заставит тест ждать количество времени, указанное в качестве параметра. Например, если вы установили задержку на 5 секунд, а затем попытались найти элемент, вам придется подождать целых 5 секунд, прежде чем тест продолжится, даже если загрузка элемента занимает всего 3 секунды. Это добавляет ненужные задержки в ваш код. Две секунды могут показаться не такими уж и долгими, но, если вы используете это в сотнях тестов, они в сумме составляют несколько минут.

Вместо этого рекомендуется использовать методы ожидания Selenium WebDriver.

Неявные ожидания

Неявное ожидание задерживает выполнение теста на указанное количество времени, пока веб-элемент не будет найден. Однако, в отличие от Sleep, если элемент найден до истечения времени, тест будет продолжаться. Это также относится ко всем веб-элементам в тестовом скрипте.

Вот как выглядит неявное ожидание в C#: 

driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);

Недостатком неявного ожидания является то, что оно применяется только к методу FindElement().

Явные ожидания

Явные ожидания применяются только к определенному элементу. Мы используем их, чтобы подождать, пока не будет выполнено определенное условие. 

Чтобы использовать явные ожидания в C#, необходим пакет DotNetSeleniumExtras NuGet. Ниже приведен пример кода явного ожидания, которое ожидает, пока элемент станет кликабельным.

WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
IWebElement element = wait.Until(SeleniumExtras.WaitHelpers.ExpectedConditions.
         ElementToBeClickable(By.Id("element-id")));

Будьте внимательны при смешивании явных и неявных ожиданий

Это может привести к неопределенному поведению, непредвиденному времени ожидания и общему снижению надежности тестов.

Объектная модель страницы

Уже хорошо известной практикой в автоматизации тестирования пользовательского интерфейса является использование шаблонов проектирования. Наиболее популярным шаблоном проектирования в автоматизации пользовательского интерфейса является объектная модель страницы (также известная как POM – Page Object Model).  При работе с POM мы отделяем методы взаимодействия c пользовательским интерфейсом от фактических тестов. Каждая веб-страница или веб-компонент представлены отдельным классом, где мы храним все методы, связанные с этой конкретной страницей или компонентом, которые мы затем вызываем из методов теста. Это отличный способ для избегания дублирования кода и упрощения поддержки тестового фреймворка.

// The method inside the page object class returns true if the name is displayed, and false if it not:
public bool IsUserLoggedIn(string name)
{
      return DisplayName.Text.Equals(name);
}
// The assertions inside the test method:
Assert.IsTrue(IsUserLoggedIn("My Username");

Отсутствие проверок в объектах страницы

Не допускайте проверок в классах объектов страницы. Если необходимо включить проверку в тестовый класс, сделайте так, чтобы она возвращала логическое значение. Таким образом, вы можете использовать его для проверки как позитивных, так и негативных сценариев.

// The method inside the page object class returns true if the name is displayed, and false if it not:
public bool IsUserLoggedIn(string name)
{
      return DisplayName.Text.Equals(name);
}
// The assertions inside the test method:
Assert.IsTrue(IsUserLoggedIn("My Username");

Отсутствие Selenium методов в тестах

Включите Selenium методы в классы объектов страницы, а не в тестовые методы. Каждый веб-элемент может быть объявлен как внутренняя переменная внутри своего класса, и методы должны быть открытыми, чтобы к ним можно было получить доступ из тестовых классов.

Использование оберток

Или, что еще лучше, оберните методы Selenium в свои собственные методы. Это уменьшает зависимость от любых возможных изменений внутри библиотек Selenium. Это также может быть полезно, если вы часто используете определенные методы вместе.

Например, чтобы написать что-либо внутри текстового поля, можно щелкнуть на текстовое поле, очистить существующий текст, а затем ввести необходимый текст. Можно создать метод, который принимает элемент в качестве параметра и выполняет все следующие действия:

public void WriteText(IWebElement element, string text)
{
    element.Click();
    element.Clear();
    element.SendKeys(text);
}

Делайте скриншоты

Еще одна хорошая практика — делать скриншоты упавших шагов. Это поможет лучше понять, где и почему произошло падение шага, а также упростит отладку. В Selenium доступен метод GetScreenshot(), который делает снимки экрана всей веб-страницы или определенного элемента.

Отчётность

И последнее, но не менее важное: используйте хорошее средство создания отчетов для демонстрации результатов тестового запуска. Это не только поможет вашей работе стать более явной, но и вы сможете поделиться результатами с руководством, клиентами или другой не технической аудиторией, которые могут быть заинтересованы в различных метриках тестирования.

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

Выводы

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

Комментарии (3)


  1. RussianTM
    28.11.2023 10:18

    11 хорошая практика написания тестов на Selenium - переходите на Playwright


  1. Bagir123
    28.11.2023 10:18

    Моё мнение что при использовании селекторов они должны быть сделаны в едином стиле.

    Либо во всех местах id и name, либо css, либо везде xpath.

    А если смешивать, то тесты становятся плохо читаемы. Касательно быстродействия, то при кликинге различие между типами селекторов не влияет ни на скорость ни на ресурсоемкость, потому что сам клик и отслеживание его реализации на порядок перекрывает поиск селектора.

    А если парсить, то selenium используется в связке с angelsharp или agilitypack, дерево dom анализируется один раз для страницы, что ускоряет парсинг на порядок и опять же скорость и ресурсоемкость упирается в количество окон и отслеживание последнего нужного тега на странице, а не в поиск селектора.


  1. niksr
    28.11.2023 10:18

    На английском оригинальные тексты выглядят нормально, но эти переводы в лоб читать невозможно