В чем состоит автоматизация тестирования

Содержание:

Используя различные подходы и примеры, пользуясь различными методами, приходим к такой последовательности действий: SpecFlow (опционально): DSL, затем NUnit: тестовый фреймворк, PageObject + PageElements: UI-абстракция и в заключении Selenium.WebDriverС целью начать тестирование вовремя используется TFS 2012 и TeamCity. В дальнейшем вы увидите, как к этому можно прийти, обнаружим типичные ошибки, которые встречаются на пути, и как определить способы, чтобы их решить.

В чем состоит автоматизация тестированияВ чем состоит автоматизация тестирования
Получи грант, покрывающий 50% стоимости обучения
И обучайся новой профессии онлайн из любой точки мира

Почему сложен этот путь

Все программисты осознают, что автоматизация тестирования состоит из многих положительных моментов: от экономии времени до исключения человеческого фактора, исключает монотонное регрессионное тестирование. Однако в этом опросе существуют скрытые подводные камни.В тестировании встречаются негативные моменты — хрупкость и ломкость в связи с изменением UI; неправильное кодирование и внешнее влияние и многое другое.Возьмем код, связанный с YouTube-каналом популярного музыканта из Южной Кореи PSY.

[Test]
public void Google_SearchGangnamStyle_PsyYouTubeChanelIsOnTop() {
  var wd = new OpenQA.Selenium.Firefox.FirefoxDriver {
    Url = "http://google.com"
  };
  try {
    wd.Navigate();
    wd.FindElement(By.Id("gbqfq")).SendKeys("gangnam style");
    wd.FindElement(By.Id("gbqfb")).Click();
    var firstResult = new WebDriverWait(wd, TimeSpan.FromSeconds(10)).Until(w => w.FindElement(By.CssSelector("h3.r>a")));
    Assert.AreEqual("PSY - YouTube", firstResult.Text);
    Assert.AreEqual("http://www.youtube.com/user/officialpsy", firstResult.GetAttribute("href"));
  } finally {
    wd.Quit();
  }
}

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

Автоматизированное тестирование: слои приложения

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

В чем состоит автоматизация тестирования
87% наших выпускников уже работают в IT
Оставь заявку, и мы поможем с выбором новой профессии

Техдрайвер

У нас техдрайвер называется Selenium.WebDriver, это средство управления браузером. Безусловно, если выполнить некоторые простые манипуляции в рамках процесса тестирования, то можно сэкономить большое количество времени, причем вполне достаточно Proxy. Однако применение web-driver будет хорошей идеей по ряду причин:

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

В первую очередь, нужно вынести настройки в конфиг. Получится следующая картина:

<driverConfiguration targetDriver="Firefox" width="1366" height="768" isRemote="false" screenshotDir="C:Screenshots" takeScreenshots="true" remoteUrl="…" />

Далее создается отдельный класс, он нужен для логики чтения конфига

[Test] public void WebDriverContextGoogle_SearchGangnamStyle_PsyYouTubeChanelIsOnTop() {
  var wdc = WebDriverContext.GetInstance();
  try {
    var wd = wdc.WebDriver;
    wd.Url = "http://google.com";
    wd.Navigate();
    wd.FindElement(By.Id("gbqfq")).SendKeys("gangnam style");
    wd.FindElement(By.Id("gbqfb")).Click();
    var firstResult = new WebDriverWait(wd, TimeSpan.FromSeconds(10)).Until(w => w.FindElement(By.CssSelector("h3.r>a")));
    var expected = new KeyValuePair < string, string > ("PSY - YouTube", "http://www.youtube.com/user/officialpsy");
    var actual = new KeyValuePair < string, string > (firstResult.Text, firstResult.GetAttribute("href"));
    Assert.AreEqual(expected, actual);
  } finally {
    wdc.Dispose();
  }
}

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

Контекст тестирования

Для black-box тестирования необходимы различные данные, которые выносятся в конфигурационную секцию. Далее видим такую картину.

<environmentsConfiguration targetEnvironment="Google">
   <environments>
      <environment name="Google" app="GoogleWebSite">
         <apps>
            <app name="GoogleWebSite" url="http://google.com/" />
         </apps>
         <users>
            <user name="Default" login="user" password="user" />
         </users>
      </environment>
   </environments>
</environmentsConfiguration>

Теперь не нужно дублировать URL во всех тестах, достаточно собрать проект с другой конфигурацией.

<environmentsConfiguration targetEnvironment="Google-Test" xdt:Transform="SetAttributes">

Паттерн Page Objects

Паттерн хорошо показал себя в автоматизации. Он поможет тестам работать кодом техдрайвера с высокоуровневой абстракцией.У Page Objects очень много преимуществ — разделение полномочий, размещение локаторов в одном месте, локаторы пишутся в декларативном стиле и многое другое.

Чего недостает в Page Objects

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

[FindsBy(How = How.Id, Using = "gbqfq")] 
public IWebElement SearchTextBox { get; set; } 

[FindsBy(How = How.Id, Using = "gbqfb")] 
public IWebElement SubmitButton  {get; set; }
public GoogleSearchResults ResultsBlock { get; set; }
public void EnterSearchQuery(string query) {
  SearchTextBox.SendKeys(query);
}
public void Search() {
  SubmitButton.Click();
}

Еще одну

public class GoogleSearchResults: PageElement {
  [FindsBy(How = How.CssSelector, Using = "h3.r>a")] 
  public IWebElement FirstLink { get; set; }
  public KeyValuePair < string, string > FirstResult {
    get {
      var firstLink = PageHelper.WaitFor < GoogleSearchResults > (w => w.FirstLink);
      return new KeyValuePair < string, string > (firstLink.Text, firstLink.GetAttribute("href"));
    }
  }
}

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

///<summary>/// Get Page element instance by type///</summary>/// 
<typeparam name="T">Page element type</typeparam>/// 
<param name="waitUntilLoaded">Wait for element to be loaded or not. Default value is true</param>/// 
<param name="timeout">Timeout in seconds. Default value=PageHelper.Timeout</param>/// 
<returns>Page element instance</returns>
public T GetElement<T>(bool waitUntilLoaded = true, int timeout = PageHelper.Timeout)	
where T : PageElement
///<summary>/// Wait for all IWebElement properies of page instance to be loaded.///</summary>/// 
<param name="withElements">Wait all page elements to be loaded or just load page IWebElement properties</param>/// 
<returns>this</returns>
public Page WaitUntilLoaded(bool withElements = true)

Показ технической реализации займет много места и времени, главное, в итоге мы приходим к простому и изящному коду:

var positionsWidget = Page.GetElement<GoogleSearchResults>();

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

public static IWebElement WaitFor( Expression<Func<TPage, IWebElement>> expression, int timeout = Timeout)
var firstLink = PageHelper.WaitFor(w => w.FirstLink);

Объяснение технической реализации займет много времени, поэтому смотрим на код в нынешнем состоянии.

[Test] public void Google_SearchGangnamStyle_PsyYouTubeChanelIsOnTop() {
  try {
    var page = WebDriverContext.CreatePage < GooglePage > (EnvironmentsConfiguration.CurrentEnvironmentBaseUrl);
    page.EnterSearchQuery("gangnam style");
    page.Search();
    var expected = new KeyValuePair < string, string > ("PSY - YouTube", "http://www.youtube.com/user/officialpsy");
    var actual = page.GetElement < GoogleSearchResults > ().FirstResult;
    Assert.AreEqual(expected, actual);
  } finally {
    WebDriverContext.GetInstance().Dispose();
  }
} 

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

Тест

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

public class WebDriverTestsBase<T> : TestsBasewhere T:Page, new(){        
/// <summary>/// Page object instance/// </summary>        
protected T Page { get; set; }
///<summary/// Relative Url to target Page Object///</summary>        
protected abstract string Url { get; }        
[SetUp]        
public virtual void SetUp(){              
WebDriverContext = WebDriverContext.GetInstance();              
Page = Framework.Page.Create<T>(WebDriverContext.WebDriver,EnvironmentsConfiguration.CurrentEnvironmentBaseUrl,Url,PageElements);}         
[TearDown]         
public virtual void TearDown(){              
if (WebDriverContext.HasInstance){                  
var instance = WebDriverContext.GetInstance();                  
instance.Dispose();             
}         
 } 
  }

Снова займемся написанием теста

public class GoogleExampleTest: WebDriverTestsBase < GooglePage > {
  [Test] public void Google_SearchGangnamStyle_PsyYouTubeChanelIsOnTop() {
    Page.EnterSearchQuery("gangnam style");
    Page.Search();
    var expected = new KeyValuePair < string, string > ("PSY - YouTube", "http://www.youtube.com/user/officialpsy");
    var actual = Page.GetElement < GoogleSearchResults > ().FirstResult;
    Assert.AreEqual(expected, actual);
  }
}

Он уже выглядит практически идеально. Достаточно вынести некоторые добавления

[TestCase("gangnam style", "PSY - YouTube", "http://www.youtube.com/user/officialpsy")] public void Google_SearchGoogle_FirstResult(string query, string firstTitle, string firstLink) {
  Page.EnterSearchQuery(query);
  Page.Search();
  var expected = new KeyValuePair < string, string > (firstTitle, firstLink);
  var actual = Page.ResultsBlock.FirstResult;
  Assert.AreEqual(expected, actual, string.Format("{1} ({2}) is not top result for query " {
      0
    }
    "", firstTitle, firstLink, query));
}

В итоге все стало более понятно, но нужно добиться идеала.

DSL

В коде все-таки остались проблемы, которые можно решить с использованием плагина SpecFlow.

Feature: Google Search As a user I want to search in google So that I can find relevent information
Scenario Outline: Search Given I have opened Google main page
   And I have entered <searchQuery>
   When I press search button
   Then the result is <title>, <url>
Examples:
   |searchQuery | title | url |
   |gangnam style |PSY — YouTube |http://www.youtube.com/user/officialpsy|
[Binding]
public class GoogleSearchSteps: WebDriverTestsBase<GooglePage>{[Given(«I have opened Google main page»)]
public void OpenGooglePage() {// Page is already created on SetUp, so that’s ok} [Given(@«I have entered (.*)»)]
public void EnterQuery(string searchQuery) {Page.EnterSearchQuery(searchQuery);} [When(«I press search button»)]
public void PressSearchButton() {Page.Search();} [Then(«the result is (.), (.)»)]
public void CheckResults(string title, string href) {var expected = new KeyValuePair<string, string>(title, href);
var actual = Page.GetElement<GoogleSearchResults>().FirstResult; Assert.AreEqual(expected, actual);}}

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

Инструкция автоматизатора

1. Нужно избегать проблемных локаторовНеверно

[FindsBy(How = How.XPath, Using = "((//div[@class='dragContainer']/div[@class='dragHeader']" +       
"/div[@class='dragContainerTitle'])[text()="Account Info"])" +       
"/../div[@class='dragContainerSettings']")]public IWebElement SettingsButton { get; set; }

Верно

[FindsBy(How = How.Id, Using = "gbqfb")]        
public IWebElement SubmitButton { get; set; }

2. Инкапсулируйте логику приложения в классах различных страниц

3. Необходимо выделять виджеты.

4. Следует группировать элементы в виджеты.

5. Нужно избегать магических строк в коде теста.

6. Следует выносить дублирующиеся операции в базовые классы тестов.

7. Требуется вынесение Page Objects в отдельную сборку.

8. Необходимо использовать Assert исключительно в коде тестов.

9. Нужно использовать Assert’ы для улучшения читаемости тестаНе верно

var actual = Page.Text == “Success”Assert.IsTrue(actual);

Верно

Assert.AreEqual(MessageHelper.Success, Page.Text)

10. Обращайте внимание на сообщения об ошибках в Assert’ах

Assert.AreEqual(MessageHelper.Success, Page.Text, “Registration process is not successfull”);

11. Применяйте высокоуровневые абстракции, к примеру:

Page.GetElement<GoogleSearchResults>();var firstLink = PageHelper.WaitFor<GoogleSearchResults>(w => w.FirstLink);

12. Группировать шаги в DSL нужно семантическиНеверно

I have logged as a user with empty cart

Верно

I have logged inAnd my cart is empty

13. В DSL сравнивать кортежи нужно одним шагомНеверно

When I open Profile pageI can see first name is “Patrick”And I can see last name is “Jane”And I can see phone is “+123 45-67-89

Верно

When I open Profile pageI can see profile info: Patrick Jane +123 45-67-89

14. Применяйте black-box тестирование.

15. Добейтесь того, чтобы результаты тестов были в общем доступе и их понимали все.

Оцените статью
(1 оценок) 5 / 5
Читать следующую статью
Заполни форму и получи грант на обучение