В своей практике много раз сталкивался с крупными и древними проектами, которые поддерживаются и в полном объеме эксплуатируются лет по 10, а то и более. А бывает, что и довольно новый проект был сделан на основе того, старого и древнего. Команды разработчиков менялись с годами, а проект все разрастался и разрастался. У каждого разработчика свой стиль, видение, идеи. И вот такой проект все рос и рос. Появлялась новая функциональность, методы. А старые обрастали условиями, комментариями. Да и заказчики все время подгоняли по срокам. Все же сталкивались с ситуацией, когда заказчик говорит что-то наподобие: «Ну, я задание еще не сформулировал, но оно должно было быть выполнено еще вчера»? Сроки горят, а, соответственно, приходится чем-то жертвовать. И это что-то — модульные тесты. Ведь на тестирование может уходить до 60% от общего времени разработки. А дальше на написание тестов опять времени нет. Ну, оно же и так работает, заказчик не жалуется, а значит, все хорошо. И в результате получаем ситуацию, когда уже непонятно, что и как друг с другом связано, что лишнее, а что критичное. Модульными тестами уже давно никто в этом проекте не пользуется, а те, что есть, падают с ошибками, ибо их тоже никто не обновлял. А сами методы порой занимают строк по 800 и более, включающих в себя десятки условий. Любая попытка взяться за исправление такой ситуации навевает чувство тоски и отчаяния.
Действительно, пытаться исправить такую ситуацию очень сложно, это займет огромные временные ресурсы, которых никогда нет. Конечно, можно все свалить на плечи тестировщиков. Да, хороший тестировщик знает логику своего проекта и может проверить на первый взгляд не связанную с конкретной задачей функциональность. Но порой и у хорошего тестировщика куча своих задач или конкретно с вашей задачей он мало знаком. И в ситуациях, описанных выше, крайне необходимо иметь хоть какое-то автоматизированное тестирование.
В конечном итоге, получаем ситуацию, когда unit-тестов нет, а их написание займет неоправданно большие временные ресурсы, т.к. методы не адаптированы под них. Они имеют большой размер с огромным количеством вложенных условий. Рефакторинг невозможен из-за запутанной логики и отсутствия описания проекта. Да, времени на поддержание документации порой тоже нет.
Интеграционные тесты также невозможны из-за запутанной логики и отсутствия описания.
Думаете, таких рабочих проектов нет? Поверьте, встречаются, по крайней мере, мне на них везло.
В конечном счете у нас остаются только сквозные тесты.
Заметьте, я ни в коем случае не пропагандирую идею замены всех тестов на сквозные. Я хочу лишь описать возможность минимизации негативных последствий от отсутствия тестирования как такового.
Сквозные тесты (e2e) — это автоматизированные имплементации, имитирующие взаимодействие конечного пользователя с системой. Обычно они представляют собой определенный поток (flow, или user story), например, «пользователь может ответить на комментарий», в который включены все шаги, выполняемые пользователем, такие как: нажатие кнопок, ввод текста, прокрутка и т.п.. Таким образом, тестированием охватывается все приложение от начала до конца.
Сквозные тесты обычно считаются наиболее ценными и информативными, поскольку они имитируют взаимодействие с пользователем и гарантируют, что вся система функционирует правильно. Недостатком e2e-тестов является медленное выполнение работыв по сравнению с другими автотестами, к тому же, они подвержены нестабильности, из-за большого количества зависимостей, что делает их сложными в обслуживании.
Само автоматизированное тестирование можно разделить на две части: тестирование с кодом и codeless-тестирование.
Тестирование с кодом
Когда проводите тестирование с кодом, вы используете фреймворки, которые могут автоматизировать браузеры. С помощью них вы моделируете веб-браузеры и даете указания для выполнения различных действий в вашем целевом приложении. После чего вы проверяете реакцию приложения на соответствующие действия. Один из самых популярных фреймворков — Selenium.
Codeless-тестирование
В ситуации с codeless-тестированием вы используете фреймворки на базе искусственного интеллекта, которые запоминают действия. Опираясь на некоторую дополнительную информацию, они проверяют ответ целевого приложения на действия должным образом.
Эти инструменты часто выглядят как малокодовые платформы для разработки, где вы перемещаете различные панели. Один из таких инструментов – TestCraft. Это codeless-решение, разработанное на платформе Selenium.
Как правило, эти инструменты стоят дороже из-за того, что такие функции, как создание, сопровождение и запуск тестов выполняются с помощью простого перемещения панелей, а также из-за того, что для проведения тестов не нужно уметь писать программный код. Но я упомянул здесь про TestCraft, потому что у него есть бесплатная версия, которая включает в себя практически все.
Конечно, если речь идет о скорости и деньгах, то codeless-решение может оказаться вам больше по душе, правда все эти инструменты еще достаточно новые. Поэтому они пока не могут иметь ту сложность наборов тестов, которой можно достичь, написав код самостоятельно.
В нашем случае codeless-решение мы применить не сможем из-за сложности и запутанности проекта. Поэтому остановимся на первом варианте.
Но и при тестировании с кодом стоит быть осторожным и учесть некоторые сложности:
Сквозные тесты не обязаны «тестировать как можно больше за один раз». Часто тестировщики пишут огромные сквозные тесты, верифицирующие буквально каждый аспект пользовательского пути, вдаваясь в ненужную детализацию там, где это не так уж обязательно. Например, для проверки пути логина в большинстве случаев будет достаточно верифицировать перенаправление пользователя на главную страницу после успешного ввода.
При падении сквозного теста бывает довольно сложно определить «место падения». Но это можно компенсировать грамотным логированием и созданием скриншотов.
На сквозные тесты требуется большое время в CI-пайплайне, что может тормозить или даже блокировать разработку.
Сквозные тесты имеют «непрерывную» природу: каждая новая функциональность или изменение существующей приводят к необходимости обновления сквозных тестов. Этот фактор можно смягчить созданием реюзабельных компонентов, абстрагирующих детали имплементации.
Однако упростить себе жизнь тоже можно, следуя советам:
Реюзабельные тесты стоит делать сравнительно небольшими, по возможности независимыми, или создавать реюзабельными хотя бы их компоненты. Это позволит «нанизывать цепочку» тестовых компонентов на полный end-to-end пользовательский путь. Вместо написания множества изолированных, нигде больше не задействованных тестовых компонентов. Это позволит снизить время на поиск проблемных мест в автотестах и быстрее обновлять тест-сьют.
Покрывать e2e лишь критическую функциональность и не тратить время на отработку сообщений об ошибках во второстепенных пользовательских путях — они как правило достаточно просты и не требуют внимания на таком высоком уровне как e2e;
Сквозные тесты не должны слишком зависеть от деталей имплементации. Не обязательно обновлять e2e-тесты при каждом изменении деталей имплементации в каждом конкретном пользовательском пути. Как говорилось выше, желательно «абстрагировать» отдельные компоненты. Тогда тесты будет проще писать, а далее обслуживать.
«Как тестировать» разобрали, теперь осталось выяснить чем тестировать.
Одним из способов создания автоматизированных тестов — это использование инструмента Selenium. В статье все примеры будут приведены на C#.
Спросим у Яндекс Нейро, что это такое и с чем его едят.
Selenium — инструменты для автоматизации тестирования веб‑приложений. Вместе они позволяют создавать тесты, которые проверяют веб‑сайт на ошибки.
Selenium предоставляет инструменты для поиска и взаимодействия с веб‑элементами на странице (кнопки, текстовые поля, выпадающие списки и другое). C# даёт возможность эффективно управлять этими элементами.
Для использования Selenium в C# применяется интерфейс IWebDriver. Он создаёт соединение между Selenium WebDriver и веб-браузером и позволяет взаимодействовать с веб-элементами, переходить между страницами и выполнять действия на веб-странице.
Selenium предоставляет большой набор возможностей для имитации действий пользователя в различных браузерах.
Собственно, а почему именно Selenium? Наверняка же есть же аналоги. Конечно, один из них — Cypress.
Cypress.io представляет собой многофункциональную платформу для автоматического тестирования веб-интерфейсов, предоставляющую эффективные инструменты для написания тестов любой сложности: от небольших модульных (unit) до развернутых сквозных (e2e). Любые компоненты и процесс их взаимодействия можно подробно отследить, предварительно описав сценарий.
В принципе Cypress неплох, но набор браузеров меньше и поддерживает только TypeScript/JavaScript. Тогда как Selenium дружит с Java, Python, Scala, Ruby, C#, Perl, Groovy, PHP.
Другим конкурентом является Playwright.
Playwright — это библиотека автоматизации с открытым исходным кодом для тестирования браузеров и веб-скрапинга, разработанная корпорацией Microsoft
Этот уже лучше. Неплохой набор браузеров Chromium (включая Chrome, Edge, Opera), Firefox, Safari. Но до Selenium не дотягивает. Например, Interner Explorer не поддерживает. Да, соглашусь, браузер старый и непопулярный. Все уже давно переходят на Chromium, но некоторые специализированные программы могут использовать древний IE. Например, почти любое Win32-приложение, умеющее отображать веб-страницы и при этом в распакованном виде занимающее меньше 60 мегабайт, использует внутри Internet Explorer. Кстати, это касается не только маленьких по размеру приложений, например, Visual Studio использует Internet Explorer для отображения dВеб-страниц, когда это требуется в работе IDE. Также Internet Explorer и Edge, в отличие от других браузеров уже предустановлены в систему, а их движки хранятся в папке System32. Это, вкупе с API для разработки приложений, означает, что все приложения в системе, использующие данные движки будут загружать их в память только однажды. И этот принцип распространяется на все приложения.
А вот с приложениями (в том числе и браузерами) на основе Chromium это так не работает. Каждое приложение комплектует с собой собственную сборку библиотеки CEF, что, кроме раздувания размера приложения, не позволяет операционной системе иметь только одну копию dll в ОЗУ. Это сильно замедляет производительность при использовании множества подобных приложений. Помимо того, сам размер CEF довольно удручающий.
Так что упускать Internet Explorer не нужно, он еще может быть полезен.
Пожалуй, единственным недостатком Seleniuma по сравнению с другими системами, заключается в медленной работе и возрасте.
Одним из моих давнишних проектов было веб-приложение для внутренней работы колл-центра, которое предоставляло необходимую информацию операторам, регистрировало обращение клиентов и т.д. Для работы с ip-телефонией существуют разные наборы программ. В моем случае был внутренний самописный браузер на базе Internet Explorer с функциями программного телефона, где и отображался мой проект.
И я много раз видел, когда сайт прекрасно работает в Google Chrome, но падает в IE, например, из-за потерянного символа «;». Да, Google Chrome умеет исправлять некоторые ошибки верстки.
Таким образом, мой выбор пал именно на Selenium. Давайте пробежимся, как с ним работать.
В первую очередь, необходимо подключить драйвер браузера. Без него никак. Каждый браузер подключается отдельно.
var chromeDriver = new ChromeDriver();
var firefoxDriver = new FirefoxDriver();
В принципе, это все, далее уже сама логика тест-кейсов.
Загружаем страницу:
.Navigate().GoToUrl(”
https://example.test.ru”
);
И на уже загруженной странице определяем элемент, с которым будем выполнять различные действия. Поиск может производиться по разным параметрам элемента, таких как:
Id Name
ClassName
CssSelector
LinkText
TagName
IWebElement element = driver.FindElement(
By.Id
("username"));
Ввод данных в текстовое поле:
element.SendKeys("myUsername");
Нажатие на кнопку:
Либо нажатие на кнопку можно выполнить через action:
var actions = new Actions(driver);
actions.Click
(element).Perform();
Кстати, click может быть и двойным:
actions.DoubleClick(element) ).Perform();
И click правой кнопкой мыши:
actions.ContextClick(element).Perform();
Выбор записи из выпадающего списка
Здесь также можно выполнять выбор разными способами
SelectByIndex
SelectByValue
SelectByText
var elementSelect = new SelectElement(elementSelect);
elementSelect.SelectByText("Какой-то текст");
Ожидание загрузки элемента с использованием явного ожидания
Ожидания позволяют дождаться определенное состояние элемента перед взаимодействием с ним.
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until(ExpectedConditions.ElementToBeClickable(sendButton));
wait.Until(ExpectedConditions.ElementToBeClickable(sendButton));
Ожидание наличия элемента с использованием неявного ожидания
Неявные ожидания автоматически применяются ко всем действиям WebDriver, ожидающим элемент на странице в течение определенного времени.
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
IWebElement resultElement = driver.FindElement(
By.Id
("result"));
string resultText = resultElement.Text;
Ожидание исчезновения элемента после удаления
Иногда требуется ожидать, когда элемент исчезнет после какого-либо действия.
var loadingSpinner = driver.FindElement(By.ClassName(("loading-spinner"));
// 10 – время ожидания в секундах
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until(ExpectedConditions.InvisibilityOfElementLocated(By.ClassName("loading-spinner")));
Ожидание изменения значения элемента после действия
var element = driver.FindElement(
By.Id
("action-button"));
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until(ExpectedConditions.TextToBePresentInElement(element, "Новое значение"));
Ожидание наличия элемента во фрейме
Если элемент находится внутри фрейма, его необходимо сначала переключить и затем применить ожидание.
driver.SwitchTo().Frame("frame-name");
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
var elementInFrame = wait.Until(ExpectedConditions.ElementIsVisible(
By.Id
("element-id")));
// Вернуться на основную страницу после завершения действий во фрейме
driver.SwitchTo().DefaultContent();
Ожидание асинхронной загрузки элемента
Сайты с асинхронной загрузкой могут требовать специального ожидания для корректной проверки наличия элементов.
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until(driver => asyncLoadedElement.Displayed && asyncLoadedElement.Enabled);
Это лишь небольшая часть красоты Selenium. Я лишь приоткрыл дверь в огромный мир возможностей этой библиотеки. А теперь давайте попробуем применить это на практике. Так как вариантов много, все зависит специфики проекта и требований к тестированию.
Например, Windows Form приложение может стать удобным помощником тестировщику, с выбором блоков функциональности программы, назначенных на тестирование. Например, авторизация, где десяток кейс-тестов объединены в одну команду автотеста. При этом другие тесты, , в которых нет необходимости при проверке конкретной задачи, выполняться не будут. Такой проект полностью удовлетворит требования интеграционного тестирования.
Для наглядности я сделал простенький пример такого сервиса. Да, можно добавить больше красоты с помощью специальных библиотек или написать на Xamarin, дополнить ко всему статистику, но... это уже вопросы, несвязанные с этой статьей.
Ситуацию со старым проектом, у которого все тесты устарели, я привел лишь для наглядности.
Однако никто вам не мешает использовать Selenium и в Unit-тестах.
Давайте попробуем написать простой пример по следующему сценарию:
Перейдем на веб‑страницу — https://excample.test.ru/.
Увеличим окно до максимума.
Нажмем на меню «Тестирование».
Закроем браузер.
using NUnit.Framework;
using OpenQA.Selenium;
using
OpenQA.Selenium.Chrome
;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestProject
{
class TestClass
{
IWebDriver m_driver;
[Test]
public void cssDemo()
{
m_driver = new ChromeDriver();
m_driver.Url = "
https://demo.guru99.com/test/guru99home/
";
m_driver.Manage().Window.Maximize();
IWebElement link = m_driver.FindElement(By.XPath(".//*[@id='rt-header']//div[2]/div/ul/li[2]/a"));
link.Click
();
m_driver.Close();
}
}
}
Конечно, можно запустить проект автотеста в консольном приложении и всегда выполнять все тесты. Советую запускать такой автотест в Jenkins для полной проверки перед публикацией в прод. Я не буду расписывать последовательность действий, но приложу ссылку на статью, где уже все есть: https://victorz.ru/20181009775.
В заключение хочу сказать, что в этой статье мы компенсировали отсутствие unit-тестов набором автотестов. Действительно, такой подход не идеален и имеет свои недостатки, но при правильном планировании способен уберечь проект от неочевидных ошибок и сэкономить QA-отделу кучу времени и нервов.