За пределами PageObject, Дмитрий Жарий
Есть ли необходимость в использовании объектно-ориентированного подхода в автоматизации тестирования? Нужно ли нам экономить исходный код, или лучше скопировать, вставить и подправить? Что лучше, “сложное” ООП или простые “дубовые” тесты. Ответ зависит от Вашего проекта автоматизации и от дальнейших планов его развития. Если Вы планируете остановится на десятке тестов, то в таком случае в “более сложных практиках” необходимости нет. Если же Вы планируете долгосрочное развитие фреймворка автоматизации, то задуматься о правильной архитекторе и будущем росте необходимо уже прямо сейчас.
В своем докладе Дмитрий расскажет, как очень несложные практики ООП помогут сделать код еще проще и понятней. И поверьте, в хорошем коде может разобраться любой человек, а горы “вечно падающего копи-паста” будут просто ненавидеть. Также Дмитрий расскажет о том, как Автоматизация может тестировать сама себя при помощи легких UI тестов перед тем, как запустить более тяжелые функциональные тесты и сценарии. Он покажет, как написав код тестового набора всего лишь один раз, применить его для каждой страницы отдельно. Примеры кода к докладу будут на C#/.NET. Но, любую такую практику можно реализовать на любом общеизвестном и популярном языке программирования.
8. Как улучшить?
Вынести часто используемый функционал
в общедоступные методы
Отформатировать код
Добавить комментарии
driver.FindElement(By.XPath("//a[text()='Log On']"))
.Click();
// Click on the Log On link.
var logOnLink = driver.FindElement(
By.XPath("//a[text()='Log On']"));
logOnLink.Click();
atdays.com #atdays 8
9. И, если у вас все под
контролем…
atdays.com #atdays 9
11. var ddlMonthSelect =
new
SelectElement(driver.Fin
Прости, ня!
dElementByName(@"EXPIRYD
ATE_MM"));
var
ddlYearSelect = new
SelectElement(driver.Fin
dElementByName(@"EXPIRYD
ATE_YY"));
var
txtSecurityCode =
driver.FindElementByName
(@"CVV");
var
btnContinue =
driver.FindElementById("
btnSubmit");
ddlMonthSelect.SelectByT
ext("05");
ddlYearSelect.SelectByTe
xt("15");
atdays.com #atdays 11
12. Когда приходит Хаос…
"Тест-простыней" становится слишком
много
В коде не разобраться без пол-литру
На поддержку уходит уж слишком много
времени
Легче всё переписать заново
Начинаете ненавидеть разработчиков,
которые вынесли div из span
atdays.com #atdays 12
13. PageObject – он как книжная
полка
Pages
LoginPage
→ Login(name, passwd)
MainPage
→ LogOut()
→ GotoProjects()
→ GotoUserProfile()
→ Search(text)
atdays.com #atdays 13
15. PageObject – это:
Концентрация
на языке задачи,
а не на языке решения
atdays.com #atdays 15
16. PageObject – главная цель
Обеспечить хранение локаторов в отдельном
классе
Обеспечить повторное использование локаторов
и/или действий над страницей без дублирования
кода
Обеспечить слой абстракции от «драйвера» так,
чтобы в тестах не использовались физические
элементы идентификации
элементов управления приложением
atdays.com #atdays 16
17. А выглядит всё вот так
MainPage
Т
Е
С
Create Т
Account
Page
atdays.com #atdays 17
22. Позитив 3: Driver стал доступен
отовсюду!
public static class WebBrowser
{
public static RemoteWebDriver _driver = null;
public static RemoteWebDriver Driver
{
get
{
_driver = _driver ?? new InternetExplorerDriver();
return _driver;
}
}
}
public static RemoteWebDriver Driver {
get { return WebBrowser.Driver; }
}
atdays.com #atdays 22
23. И еще раз о позитиве
Теперь жизнь браузера (драйвера)
контролирует класс WebBrowser.
Текущий драйвер доступен из любого
участка кода
Любая страница доступна из MyPages.*
Наш тест теперь помещается на один
экран монитора
atdays.com #atdays 23
24. Ограничения статического
класса в C#
class static class
Наследоваться можно нельзя
Породить много разных можно нельзя
Инициализировать параметрами
можно нельзя
через конструктор
полностью нельзя. Очень
Завалить можно живучие
Присвоить переменной можно нельзя
Передать как параметр другому методу можно нельзя
Реализовать интерфейс можно нельзя
Использовать статические методы можно можно
Использовать статические поля можно можно
Инициализировать при помощи Webdriver нельзя "из коробки",
можно
PageFactory нужно написать свою фабрику
atdays.com #atdays 24
25. ГИБКОСТЬ – ЭТО ВАЖНО.
PAGE OBJECT КАК OBJECT
atdays.com #atdays 25
27. Изменения в MyPages
public static class MyPages
{
public static MainPage MainPage
{
get { return new MainPage();}
}
public static DonatePage DonatePage
{
get { return new DonatePage(); }
}
}
atdays.com #atdays 27
30. PaymentResultPage
public class PaymentResultPage : AbstractPageBase
{
public void WaitUntilExists()
{
WebDriverWait wait = new WebDriverWait(Driver,
TimeSpan.FromSeconds(30));
wait.Until(ExpectedConditions.TitleContains(
@"Donate-error - Payments"));
}
public string GetResultHeaderText()
{
var lblFirstHeader = Driver.
FindElementById(@"firstHeading");
return lblFirstHeader.Text;
}
}
atdays.com #atdays 30
31. Changelog
Появился базовый класс
AbstractPageBase
Из MyPages.* можно по-прежнему
получить любую страницу
Декларация страниц была вынесена из
MyPages в отдельные файлы
Мы лишились ограничений статических
классов
ТЕПЕРЬ НАС НЕ ОСТАНОВИТЬ!
atdays.com #atdays 31
34. http://bash.im/quote/420885
xxx: тема письма в рабочей почте
" RE: FW: FW: RE: RE: FW: RE: СРОЧНО!!!!!!"
yyy: Вот это проблема структурных
организаций
ЦЕПОЧКА ОТВЕТСТВЕННОСТИ
/CHAIN OF RESPONSIBILITY/
atdays.com #atdays 34
36. Iinvokable
public interface IInvokable
{
void Invoke();
bool Exists();
}
Invoke – вызвать
Invokable – то, что можно вызвать
I invokable – Я вызываемый(-оя, -ое)!
atdays.com #atdays 36
37. НужнаяPage.Invoke():
НужнаяPage.Invoke():
Если я уже Exists() – то вот она я!
Если я не Exists(), то я сделаю:
ПредыдущаяPage.Invoke()
Потом что-то нажму – вот она я!
Donation
Donate
Payments
Page
Form
atdays.com #atdays 37
38. Что дает нам Iinvokable?
Любую страницу можно вызвать с
параметрами по умолчанию
var page = MyPages.
PaymentResultErrorInvalidCreditCard;
page.Invoke();
atdays.com #atdays 38
39. Что дает нам Iinvokable?
Передать страницу как параметр метода
public void TestThatPageExists(IInvokable page)
{
page.Invoke();
Assert.IsTrue(page.Exists());
}
[TestMethod]
public void Test_PaymentResultErrorInvalidCreditCard()
{
TestThatPageExists(
MyPages.PaymentResultErrnvalidCreditCard
);
}
atdays.com #atdays 39
40. Что дает нам Iinvokable?
Если нужная страница уже открыта (после
предыдущего теста ) – она будет использована
повторно
public void Invoke()
{
if (Exists() == false)
{
var mainPage = MyPages.MainPage;
mainPage.Invoke();
mainPage.GoToDonatePage();
}
}
atdays.com #atdays 40
42. Сышишь, а есть какиета
кантролы?
public interface IHaveExpectedControls : IInvokable
{
List<IWebElement> GetExpectedControls();
}
.Invoke()
.Exists()
.GetExpectedControls()
atdays.com #atdays 42
43. Get Expected Controls
(PageObject)
public class DonatePage : AbstractPageBase, IHaveExpectedControls
{
[FindsBy(Using=@"input[name='amount'][value='50']",
How = How.CssSelector)]
public IWebElement rbtnDonate50;
[FindsBy(Using=@"input[value='Donate by credit/debit card']",
How = How.CssSelector)]
public IWebElement btnMakeDonation;
public List<IWebElement> GetExpectedControls()
{
return new List<IWebElement>()
{
rbtnDonate50,
btnMakeDonation
};
}
atdays.com #atdays 43
44. Позволяет писать
универсальные тесты
public virtual IHaveExpectedControls CurrentPage
{
get { return null; }
}
[TestMethod]
public void TestExpectedControls()
{
CurrentPage.Invoke();
var expectedControls = CurrentPage.GetExpectedControls();
foreach (var expectedControl in expectedControls)
{
Assert.IsTrue(expectedControl.Displayed);
}
}
atdays.com #atdays 44
45. Авто-тесты для авто-тестов?
Легкие тесты, которые:
1. Открывают каждую страницу
2. Проверяет каждый важный
элемент страницы
Основной тест-набор
atdays.com #atdays 45
47. Спасибы:
За то, что доклад состоялся:
Спасибо Вам!
За Invoke()
Виктору Линчевскому
Леониду Артемьеву
Помощь в подготовке доклада:
Михаил Поляруш
Андрей Ребров
atdays.com #atdays 47
48. Я не прощаюсь, я говорю: до
свидания!
Дима Жарий
atdays.com #atdays 48