...

понедельник, 13 января 2014 г.

SWD.Starter: Быстрый старт автоматизации тестирования UI на C# + Selenium WebDriver + PageObjects

WTF Logo

Статья расскажет о том, как настроить фреймворк автоматизированного тестирования пользовательского интерфейса на языке C#, вместе с Selenium WebDriver и паттерном PageObjects.

Стартовый набор с открытым исходным кодом – SWD.Starter – поможет написать и запустить ваш первый тест в течении 10 минут. Кроме этого, предлагая архитектуру фреймворка, основанную на хороших практиках автоматизации тестирования.

Весь код SWD.Starter может быть полностью настроен под ваши задачи.


Что такое SWD.Starter?




SWD.Starter – это стартовый набор для вашего фреймворка автоматизации тестирования. Весь исходный код доступен на GitHub: dzhariy/SWD.Starter, а лицензия проекта (unlicense), позволяет вам использовать исходный код как угодно, хоть продавать.

SWD.Starter – это уже настроенный проект, содержащий весь необходимый инфраструктурный код для начала создания и запуска тестов пользовательского интерфейса через Selenium WebDriver.


SWD.Starter настойчиво рекомендует использование паттерна PageObjects. И в случае использования этого паттерна, вы сможете писать новый код авто-тестов действительно быстро, при этом, сохраняя красивую архитектуру и читабельность кода.


Что необходимо для начала




Для запуска проекта, вам будет необходимо следующее программное обеспечение:

  1. Visual Studio Express 2013 Desktop Edition (также, теоретически, поддерживаются VS2010 и VS2012)

  2. Git для выкачки проекта из Github

  3. Дополнительные драйвера браузеров Selenium WebDriver, которые можно скачать с официальной страницы проекта




Для быстрой и удобной установки ПО, я рекомендую использовать пакетный менеджер для Windows – Chocolatey.

Согласно инструкциям на главной странице, откройте cmd.exe, и в консольном окне, просто выполните следующий код:

@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('http://ift.tt/10ZoiHu'))" && SET PATH=%PATH%;%systemdrive%\chocolatey\bin



А далее, в том же консольном окне, выполните следующие команды:


  • cinst VisualStudioExpress2013WindowsDesktop

  • cinst git




Теперь, в консольном окне (например в cmd.exe или Far Manager), выберете папку, куда вы хотите клонировать SWD.Starter – и запустите команду:

git clone http://ift.tt/1doWTus

Это обязательный шаг, иначе проект не скомпилируется:

Скопируйте chromedriver.exe и IEDriverServer.exe в папку SWD.Starter\webdrivers


А вот видео полной установки на чистую виртуалку (которую я скачал с modern.ie )


Следуй за розовым покемоном, Нео!



На всякий случай замечу, на видео видно, что Windows на виртуальной машине требует активацию.

Cогласно лицензионным условиям modern.ie я имею право использовать такие образы легально в тестовых целях. В пользовательском соглашении сказано, что я не должен активировать Windows в этом случае.



Что такое PageObjects и почему это настолько важно?




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

Теперь ваши тесты не работают со страницей напрямую, вызывая низкоуровневые методы WebDriver, а используют более высокоуровневые операции, специфичные для каждой страницы.


Это сокращает количество строк кода в тестах, тем самым делая код более читабельным, понятным и надёжным.

Подход PageObjects – это альтернатива бот-стилю – вызову методов WebDriver из тестов напрямую.

В самом начале, бот-стиль кажется проще и понятнее чем использование PageObjects. Но, это огромное заблуждение, которое может привести ваш проект автоматизации к краху.


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


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

Но, что вас ждёт, когда количество книг возрастёт до 100? Поверьте, я вам не завидую. Просто потому, что сам через это уже прошёл.

Heap of books


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

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


Book shelf


Тесты в бот-стиле




Основное преимущество тестов в бот-стиле то, что вы можете их «записать даже не зная языка программирования», при помощи таких инструментов, как Selenium IDE и Selenium Builder.

В результате, может получится нечто такое:


Очень длинная простыня кода типа: driver.FindElement(By.Id(ConfirmPassword)).SendKeys(pass);


class BrittleTest
{
[Test]
public void Can_buy_an_Album_when_registered()
{
var driver = Host.Instance.Application.Browser;
driver.Navigate().GoToUrl(driver.Url);
driver.FindElement(By.LinkText("Admin")).Click();
driver.FindElement(By.LinkText("Register")).Click();
driver.FindElement(By.Id("UserName")).Clear();
driver.FindElement(By.Id("UserName")).SendKeys("HJSimpson");
driver.FindElement(By.Id("Password")).Clear();
driver.FindElement(By.Id("Password")).SendKeys("!2345Qwert");
driver.FindElement(By.Id("ConfirmPassword")).Clear();
driver.FindElement(By.Id("ConfirmPassword")).SendKeys("!2345Qwert");
driver.FindElement(By.CssSelector("input[type=\"submit\"]")).Click();
driver.FindElement(By.LinkText("Disco")).Click();
driver.FindElement(By.CssSelector("img[alt=\"Le Freak\"]")).Click();
driver.FindElement(By.LinkText("Add to cart")).Click();
driver.FindElement(By.LinkText("Checkout >>")).Click();
driver.FindElement(By.Id("FirstName")).Clear();
driver.FindElement(By.Id("FirstName")).SendKeys("Homer");
driver.FindElement(By.Id("LastName")).Clear();
driver.FindElement(By.Id("LastName")).SendKeys("Simpson");
driver.FindElement(By.Id("Address")).Clear();
driver.FindElement(By.Id("Address")).SendKeys("742 Evergreen Terrace");
driver.FindElement(By.Id("City")).Clear();
driver.FindElement(By.Id("City")).SendKeys("Springfield");
driver.FindElement(By.Id("State")).Clear();
driver.FindElement(By.Id("State")).SendKeys("Kentucky");
driver.FindElement(By.Id("PostalCode")).Clear();
driver.FindElement(By.Id("PostalCode")).SendKeys("123456");
driver.FindElement(By.Id("Country")).Clear();
driver.FindElement(By.Id("Country")).SendKeys("United States");
driver.FindElement(By.Id("Phone")).Clear();
driver.FindElement(By.Id("Phone")).SendKeys("2341231241");
driver.FindElement(By.Id("Email")).Clear();
driver.FindElement(By.Id("Email")).SendKeys("chunkylover53@aol.com<script type="text/javascript">
/* <![CDATA[ */
(function(){try{var s,a,i,j,r,c,l,b=document.getElementsByTagName("script");l=b[b.length-1].previousSibling;a=l.getAttribute('data-cfemail');if(a){s='';r=parseInt(a.substr(0,2),16);for(j=2;a.length-j;j+=2){c=parseInt(a.substr(j,2),16)^r;s+=String.fromCharCode(c);}s=document.createTextNode(s);l.parentNode.replaceChild(s,l);}}catch(e){}})();
/* ]]> */
</script>");
driver.FindElement(By.Id("PromoCode")).Clear();
driver.FindElement(By.Id("PromoCode")).SendKeys("FREE");
driver.FindElement(By.CssSelector("input[type=\"submit\"]")).Click();

Assert.IsTrue(driver.PageSource.Contains("Checkout Complete"));
}
}





Такой подход может быть оправдан при выполнении одноразовых задач. Например, если вам необходимо создать 1000 пользователей через интерфейс приложения – достаточно записать создание одного, и с минимальными изменениями поместить код в цикл.

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

Вот один небольшой пример:


Предположим, доступ к одной странице продукта осуществляется из 30-ти тестов. В один прекрасный день, программисты принимают решение поменять вёрстку страницы:

Теперь и элементы называются по другому, и «логика кликов» меняется.

В этом случае, вам будет необходимо внести изменение в 30 тестов, вместо того, чтобы сделать это в одном классе.

Как вы думаете, сколько времени эта интереснейшая работа займёт?


Bot style


Тесты с использованием PageObject-классов




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

Обратите внимание, что в коде теста стало больше строк… Но, это ведь только за счёт комментариев и пояснений, которые важны для демонстрации в этой статье, но необязательны в вашем реальном коде.

Уберите все комментарии и пустые строки – и код все равно останется читабельным и сократится по количеству строк.

class PageObjectTest
{
[Test]
public void Can_buy_an_Album_when_registered()
{
// Обычно, конструкторы PageObject объектов не выполняют действий на странице.
// Они необходимы лишь для получения ссылки на объект.
var registerUserPage = new RegisterUserPage();

// Просто открывает страницу регистрации, при этом,
// кликая на все нужные ссылки по пути
registerUserPage.Invoke();

// Этот класс используется для передачи данных.
// Некоторые данные могут быть заполнены «по умолчанию», но об этом – позже
var newUserFromData = new UserFromDataData()
{
UserName = "HJSimpson",
Password = "!2345Qwert",
};

// Момент заполнения и отправки формы
registerUserPage.FillForm(newUserFromData);
registerUserPage.Submit();

// А следующий код выбирает товар из витрины, добавляет его в корзину
// и переходит на страницу оформления заказа.
var showCasePage = new ShowCasePage();
showCasePage.Goto("Disco");
showCasePage.SelectProduct("showCasePage");
showCasePage.AddToCard();
showCasePage.Checkout();

var checkOutForm = new CheckOutForm();

// .DefaultValues возвращает класс с уже заполненными данными по умолчанию.
// Если нас что-то не устраивает – всегда можно заменить.
var checkoutFromData = UserCheckoutFromData.DefaultValues;


// Вот как раз это и не устраивает! А давайте JavaScript инъекцию добавим!
checkoutFromData.Email = @"chunkylover53@aol.com<script type=""text/javascript"">
/* <![CDATA[ */
(function(){try{var s,a,i,j,r,c,l,b=document.getElementsByTagName(""script"");l=b[b.length-1].previousSibling;a=l.getAttribute('data-cfemail');if(a){s='';r=parseInt(a.substr(0,2),16);for(j=2;a.length-j;j+=2){c=parseInt(a.substr(j,2),16)^r;s+=String.fromCharCode(c);}s=document.createTextNode(s);l.parentNode.replaceChild(s,l);}}catch(e){}})();
/* ]]> */
</script>";

CheckoutCompletePage checkoutCompletePage = checkOutForm.Submit();

Assert.IsTrue(checkoutCompletePage.GetPageTitle().Contains("Checkout Complete"));
}
}


Page Object

Ну что? Хотите создавать тесты, используя PageObject?


Первый Smoke-тест в SWD.Starter




Если вы задаётесь вопросом: с чего начать автоматизацию тестирования? То, у меня для вас есть очень простой ответ, который подойдёт в 99% случаев.

Начните со смоук тестов для каждой страницы приложения.

Рецепт:



  1. Взять страницу любого уровня вложенности

  2. Открыть страницу

  3. Проверить, что все важные элементы – присутствуют на странице.




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

Что все важные элементы отдельной страницы до сих пор не изменились

То, что наши PageObject классы по прежнему соответствуют актуальной странице

То, что путь из точки А. (главная страница) в точку Б. (любая другая страница) – возможен для конечно пользователя приложения.

И все это работает в разных браузерах.

А теперь, давайте напишем первый тест для страницы регистрации нового пользователя Хабрахабр:


Покрыв все страницы приложения такими тестами – вы будете приятно удивлённы: метрики покрытия покажут покрытие больше 50%. Конечно же, мы понимаем, что метрика покрытия кода – не самая основная, но согласитесь, это – хороший результат.


Кроме того, в SwdBrowser.cs есть метод HandleJavaScriptErrors(). В данной реализации, его нужно просто почаще вызывать, например, в каждом .Invoke(). И тогда, этот метод сможет отловить возможные неожиданные ошибки JavaScript.


Я надеюсь, что в ходе просмотра видео, вы заметили несколько интересных вещей?

Например, что в проекте уже готова инфраструктура для смоук-тестов PageObject-классов?..

И чтобы добавить тест – необходимо просто его записать, сгенерировать код… и следовать инструкциям в сгенерированном коде.

А в самом начале, мы видим строку кода:



[TestMethod]
public void S01_First_Step_Run_WebDriver_with_Firefox()
{
SwdBrowser.Driver.Url = "http://swd-tools.com";
}


которая: открывает браузер, переходит по нужному URL… и закрывает браузер.

Не много ли это для одной строки?

И почему открылся именно FireFox, а что если я хочу Internet Explorer?

Об этом и многом другом – ниже.


Хорошие практики в автоматизации тестирования




Вы знаете, опасно называть практики «лучшими», и поэтому, оставим просто «хорошими».

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

Для того, чтобы показать, как хорошие практики работают вместе, я и начал работу над SWD.Starter.

Вот, например, по статье Автоматическое создание Браузера и инициализация PageObject как раз и был реализован SwdBrowser. А PageObject классы, унаследованные от CorePage – умеют самостоятельно инициализировать веб-элементы.

А в заметке WebDriverWait и PageObject, я рассказываю, как добавить «умные» методы ожидания элементов для PageObject, по типу WebDriverWait для обычных элементов.


Все это уже вошло в SWD.Starter. И если вас интересует решение конкретной проблемы – просто посмотрите код, а я, со временем, сделаю так, чтобы в нем можно было легко разобраться. Уже сейчас, некоторые классы в достаточной мере документированы, например – Swd.Core.Configuration.Config Class. А комментарии для некоторых классов уже есть в коде, но пока ещё не мигрировали в Doxygen.


Структура проекта SWD.Starter




Ядро SWD.Starter – это Swd.Core. В нем содержатся такие интересные штуки как: Solution


  • Класс Swd.Core.Configuration → Config, который читает настройки фреймворка из внешнего файла Config.config. Именно в этом файле можно выбрать запускаемый браузер, а также добавить свои настройки.

  • Класс Swd.Core.WebDriver → SwdBrowser – уже упоминался. Он управляет жизнью браузера. А рядом, в том же пространстве имен, находятся полезные методы и классы, упрощающие работу с браузером.

  • В пространстве имен Swd.Core.Pages, живут базовые классы для PageObject'ов.


В Swd.Core расположен только общий код, который, в дальнейшем, можно расширить в дочерних проектах по тестированию.


Пример такого тестового проекта – DemoProject.

Тестовый проект состоит из двух основных подпроектов:



  • Demo.TestModel – содержит декларации PageObject-классов, кастомизированные базовые классы, необходимые данные, кусочки логики работы приложения, и другие библиотечные функции, специфичные для конкретного тестируемого приложения.

    Обычно, для отдельного тестируемого приложения должна быть лишь одна библиотека Модели.

  • Demo.TestProject – проект, содержащий наборы тестов. Таких тестовых проектов может быть несколько. Вот, например, Demo.Tutorial – это тоже проект с тестами, и он также как Demo.TestProject использует библиотеку Модели (Demo.TestModel ).

  • Demo.Tutorial – попытка создания руководства по работе с Swd.Starter. Пока ещё не совсем законченный, но уже сейчас, можно читать файл «Ch00Introduction.cs» и пробовать запускать тесты.


Обратная связь, лицензия и сотрудничество




Лицензия проекта позволяет вам производить любые действия с кодом проекта, которые может ограничить лишь ваша фантазия. (http://unlicense.org/)

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

Но, мне бы было очень полезно получить от вас обратную связь. Оставить комментарии можно как тут, так и на странице проекта на Github.

А лучше всего, если вы отправите реальный чёткий пацанячий pull-request в репозиторий на github.

Но, если это будет огромное изменение с перелапачиванием половины кода, то, неплохо было бы вначале его обсудить.


Над чем можно работать? – Там поле почти не паханное:



  • Документация

  • Туториал

  • Новый полезный код, решающий реальные проблемы

  • Новые демонстрационные проекты

  • Инструкции


И ещё. 28-го февраля 2014 в Киеве, я планирую выступать с докладом на конференции Selenium Camp 2014. Доклад будет посвящён проекту SWD Page Recorder, но и проекту SWD.Starter будет посвящено не мало времени. А после, запись доклада появится в разделе архива материалов, через 3-4 месяца после конференции.

Я буду доступен все два дня, и буду готов пообщаться «в живую» как после моего доклада, так и в течении всего времени конференции.


Полезные материалы




Успешной вам автоматизации.




P.S.: SWD расшифровывается как Selenium WebDriver

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий