Сообщений 2    Оценка 35 [+1/-0]         Оценить  
Система Orphus

Автоматизированное тестирование веб-сайтов на основе поведения пользователей

Практическое применение SpecFlow и WatiN

Автор: Смирнов Олег Сергеевич
Источник: RSDN Magazine #2-2010
Опубликовано: 26.02.2011
Исправлено: 10.12.2016
Версия текста: 1.1
Введение
Gherkin
SpecFlow
WatiN
Практика
Заключение
Список использованных источников

Введение

Сегодня уже мало кого удивишь гибкими подходами к разработке программного обеспечения (ПО). Всё больше компаний работают с применением методологии Scrum и создают свои решения на основе парадигмы, называемой "разработкой, управляемая тестированием (Test Driven Development, TDD)". Это позволяет разработчикам не только хорошо проектировать архитектуру программного продукта, но и тщательно тестировать написанный код, уменьшая тем самым количество потенциальных ошибок. Однако, несмотря на эти достоинства, существует один существенный недостаток – TDD большей частью ориентируется на технический аспект в разработке ПО, в то время как бизнесу нужно “копирование” бизнес-процессов в том виде, в котором они существуют. Программисты, к сожалению, не могут гарантировать точность такого “копирования”, т.к. они не являются экспертами в той предметной области, решение для которой они создают. Несколько смягчает ситуацию тот факт, что в Scrum существует так называемый механизм пользовательских историй (user stories). Они позволяют экспертам предметной области формировать требования к ПО на естественном языке, а программистам лучше понимать бизнес-процессы данной предметной области. Для уменьшения разрыва между представлением проблемы и её программным решением, а также для смещения акцента с технической реализации на практическое использование системы пользователем, парадигма TDD была дополнена и получила новое название, а именно – разработка, управляемая поведением (Behaviour Driven Development, BDD). Повышение уровня абстракции позволяет экспертам разрабатывать спецификации, которые могут использоваться в качестве приёмочных тестов и помочь программистам в понимании процессов, происходящих в предметной области. В данной статье мы попробуем применить BDD на практике для автоматизированного тестирования Web-приложения.

Gherkin

Естественный язык тяжело интерпретировать, используя программные алгоритмы. Поэтому мы его искусственно ограничим в рамках написания пользовательских историй. Для этого воспользуемся шаблоном “Допустим – Если – То” (Given - When – Then). Шаблон описывает начальные условия, действия пользователя и конечный результат. Пример:

Допустим, что сотрудник Иванов зарабатывает 400р. в час.

Если Иванов работает 40 часов в неделю,

То Иванов будет получать 64000р. в месяц.

Этот простой сценарий есть не что иное, как отрывок из спецификации на языке Gherkin (спецификации можно писать на большинстве языков мира, включая русский язык). Данный язык является бизнес-понимаемым предметно-ориентированным языком.

ПРИМЕЧАНИЕ

Предметно-ориентированный язык программирования (англ. domain-specific programming language, domain-specific language, DSL) — язык программирования, специально разработанный для решения определённого круга задач, в отличие от языков программирования общего назначения, например C, или языков моделирования общего назначения наподобие UML.

С помощью бизнес-понимаемого DSL можно описывать проблему не беспокоясь о деталях реализации этого языка, в то время как с бизнес-реализуемым DSL необходимо чётко понимать механизм работы языка для его эффективного использования. Приведём полный текст спецификации на языке Gherkin:

      Функционал: Расчёт заработной платы сотрудника
    Для того чтобы рассчитать заработную плату сотрудника
    Как бухгалтер
    Я должен знать заработную плату за час
    И количество отработанных часов
Сценарий: Расчёт заработной платы
    Допустим, что сотрудник Иванов зарабатывает 400р. в час.
    Если Иванов работает 40 часов в неделю,
    То Иванов будет получать 64000р. в месяц.

Слова, отмеченные курсивом, являются ключевыми в языке Gherkin. Данный язык широко используется на разных платформах, но наибольшую популярность он приобрел благодаря платформе Ruby и приложению Cucumber.

SpecFlow

После того, как мы описали сценарии поведения, нам необходим инструмент для генерации тестов на их основе. В этой статье мы рассмотрим один из таких инструментов – SpecFlow. Данный продукт позволяет генерировать модульные тесты (unit tests), используя такие фреймворки, как MSTest, NUnit, XUnit и другие.

ПРЕДУПРЕЖДЕНИЕ

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

Пример спецификации:

Feature: Viewing post pages
    In order to read news
    I should be able to visit pages and posts
  
Scenario: viewing a post when logged in
    Given I am logged in
    When I view the post page
    Then I see the page

Scenario: viewing a post when not logged in
    Given I am not logged in
    When I view the post page
    Then I see the page

Более подробно мы рассмотрим его в практической части статьи.

WatiN

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

[Test]
publicvoid SearchForWatiNOnRSDN()
{
  using (var browser = new IE("http://img.meta.ua/rsdnsearch/"))
  {
    browser.TextField(Find.ByClass("inp")).TypeText("WatiN");
    browser.Button(Find.ByClass("fb")).Click();

    Assert.IsTrue(browser.ContainsText("WatiN"));
  }
}

При запуске данного теста вы увидите, как открывшийся браузер IE перейдёт на страницу поиска, на которой будут заданы параметры и запущен поиск. После это, с помощью фреймворка для написания тестов, будет выполнена проверка того, что поиск завершён.

ПРИМЕЧАНИЕ

Несмотря на явную нацеленность на тестирование Web-приложений, WatiN часто используется для получения информации со сторонних сайтов. Такая техника носит название web scraping.

Практика

Попробуем собрать всё вместе и написать приёмочные тесты для простейшего сайта на ASP.NET MVC 2. Хотя, как вы увидите позже, тесты никак не зависят от языка, на котором написано Web-приложение. Предположим, что мы разрабатываем музыкальный магазин. Ниже представлен его внешний вид:


Рис. 1. Интерфейс музыкального магазина

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

СОВЕТ

Это особенно актуально, если вы используете для тестов браузер IE. Для этого браузера необходимо указать STA-модель (Single Thread Apartment, контейнер с одним потоком) из-за проблем совместимости.

Пример app.config:

<?xmlversion="1.0"?>
<configuration>
  <configSections>
    <sectionGroup name="NUnit">
      <section name="TestRunner" type="System.Configuration.NameValueSectionHandler"/>
    </sectionGroup>
    <section name="specFlow" type="TechTalk.SpecFlow.Configuration.ConfigurationSectionHandler, TechTalk.SpecFlow"/>
  </configSections>
  <NUnit>
    <TestRunner>
      <!-- WatiN can only host IE in STA mode -->
      <add key="ApartmentState" value="STA"/>
    </TestRunner>
  </NUnit>
  <specFlow>
    <unitTestProvider name="NUnit"/>
  </specFlow>
</configuration>

Создадим нашу первую спецификацию. Для этого добавим в проект новый файл с расширением .feature. Данный файл является файлом спецификации, по содержимому которого будет сгенерирован тест.

ПРЕДУПРЕЖДЕНИЕ

К сожалению, расширение .feature зарегистрировано для решений на базе Microsoft SharePoint, поэтому возможны некоторые проблемы со стороны Microsoft Visual Studio при интерпретации таких файлов.

Далее опишем поведение:

Feature: Navigation
    In order to see product list on the site
    As a user
    I want to be able to view the product page
 
Scenario: Navigation to homepage When I navigate to main page Then I should be on the product page

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

      using TechTalk.SpecFlow;

[System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.3.1.0")]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[NUnit.Framework.TestFixtureAttribute()]
[NUnit.Framework.DescriptionAttribute("Navigation")]
publicpartialclass NavigationFeature
{
  
  privatestatic TechTalk.SpecFlow.ITestRunner testRunner;
  
#line 1 "Navigation.feature"#line hidden
  
  [NUnit.Framework.TestFixtureSetUpAttribute()]
  publicvirtualvoid FeatureSetup()
  {
    testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner();
    TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Navigation", "In order to see product list on the site\r\nAs a user\r\nI want to be able to view th" +
        "e product page", ((string[])(null)));
    testRunner.OnFeatureStart(featureInfo);
  }
  
  [NUnit.Framework.TestFixtureTearDownAttribute()]
  publicvirtualvoid FeatureTearDown()
  {
    testRunner.OnFeatureEnd();
    testRunner = null;
  }
  
  publicvirtualvoid ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo)
  {
    testRunner.OnScenarioStart(scenarioInfo);
  }
  
  [NUnit.Framework.TearDownAttribute()]
  publicvirtualvoid ScenarioTearDown()
  {
    testRunner.OnScenarioEnd();
  }
  
  [NUnit.Framework.TestAttribute()]
  [NUnit.Framework.DescriptionAttribute("Navigation to homepage")]
  publicvirtualvoid NavigationToHomepage()
  {
    TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Navigation to homepage", ((string[])(null)));
#line 6
this.ScenarioSetup(scenarioInfo);
#line 7
testRunner.When("I navigate to main page");
#line 8
testRunner.Then("I should be on the product page");
#line hidden
    testRunner.CollectScenarioErrors();
  }
}

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

[Binding]
publicclass WebBrowser
{
  publicstatic Browser Current
  {
    get
    {
      if (!ScenarioContext.Current.ContainsKey("browser"))
        ScenarioContext.Current["browser"] = new IE();
      return (Browser)ScenarioContext.Current["browser"];
    }
  }

  [AfterScenario]
  publicstaticvoid Close()
  {
    if (ScenarioContext.Current.ContainsKey("browser"))
      WebBrowser.Current.Close();
  }
}

Следует обратить внимание на BindingAttribute. С его помощью осуществляется связывание сценария и кода, его реализующего. Второй класс отдалённо напоминает механизм маршрутизации в ASP.NET MVC:

      public
      class Router
{
  privatestaticreadonly Router Instance = new Router();

  publicstatic Router Current
  {
    get { return Instance; }
  }

  privatestaticreadonly IDictionary<string, string> PageUrls = new Dictionary<string, string>
  {
    { "main page", "" },
    { "cart", "Cart"}
  };

  privatestaticreadonlystring RootUrl = ConfigurationManager.AppSettings["RootUrl"];

  publicstringthis[string page]
  {
    get
    {
      return PageUrls.ContainsKey(page) ? string.Format("{0}/{1}", RootUrl, PageUrls[page]) : null;
    }
  }
}

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

[Binding]
publicclass Navigation
{
  [When(@"I navigate to (.*)")]
  publicvoid WhenINavigateTo(string page)
  {
    WebBrowser.Current.GoTo(Router.Current[page]);
  }

  [Then(@"I should be on the product page")]
  publicvoid ThenIShouldBeOnTheProductPage()
  {
    Assert.AreEqual(WebBrowser.Current.Title, "Products");
  }
}

Как видите, он достаточно простой. Единственное, на что нужно обратить внимание, – это на последовательность (.*) в тексте атрибута When. Это пример использования регулярных выражений для сокращения числа похожих тестов с разными входными данными.

Перейдём к более сложному сценарию, в котором более явно прослеживается необходимость написания тестов пользовательского интерфейса. Предположим, что пользователь совершает покупки в нашем музыкальном магазине. Для этого он нажимает на кнопку “Добавить в корзину”. Что должно произойти после этого? В некоторых интернет-магазинах после этого действия открывается покупательская корзина с предложением оформить заказ, т.е. технически происходит перенаправление на страницу с покупками. В более интерактивных магазинах при нажатии на кнопку добавления покупки нужный товар оказывается в корзине, а вы можете продолжить делать покупки, не покидая текущей страницы. Здесь за сценой всю работу выполняет AJAX. Несомненно, можно написать unit test на данное поведение, но это нетривиальная работа. Рассмотрим, как это будет выглядеть в нашем случае. Спецификация:

Feature: Purchase
    In order to buy a product
    As a user
    I want to have ability to add the product on the cart
     
Scenario: Purchase of music Given I am on products page And have 0 products on the cart When I press add product Then product count on the cart should be 1

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

[Binding]
publicclass Purchase
{
  [Given("I am on products page")]
  publicvoid GivenIAmOnProductsPage()
  {
    WebBrowser.Current.GoTo(Router.Current["main page"]);
  }

  [Given("have 0 products on the cart")]
  publicvoid Have0ProductsOnTheCart()
  {
    Assert.True(WebBrowser.Current.Span(Find.ById("caption")).Text.Contains(" 0 item"));
  }

  [When("I press add product")]
  publicvoid WhenIPressAddProduct()
  {
    var links = WebBrowser.Current.Links.Where(l => l.Text.Contains("AddToCart")).ToArray();
    var random = new Random();
    var link = links[random.Next(links.Length)];
    link.Click();
  }

  [Then("product count on the cart should be 1")]
  publicvoid ThenProductCountOnTheCartShouldBe1()
  {
    Assert.True(WebBrowser.Current.Span(Find.ById("caption")).Text.Contains(" 1 item"));
  }
}

На первом шаге нужно проверить начальные условия. В данном случае такими условиями является то, что мы находимся на странице с продуктами, и число продуктов в нашей корзине 0, т.е. она пуста. Следующий шаг – действие пользователя: добавления продукта в корзину. Сначала мы получаем все кнопки на странице с надписью “AddToCart”, затем случайным образом выбираем одну и имитируем нажатия пользователя. Последний шаг заключается в проверке полученного результата на корректность. Т.к. мы не покидаем страницу, нам нужно проверить, что текст, отображающий количество элементов в корзине, изменил своё значение с 0 на 1. Выполнение AJAX прошло прозрачно для нашего теста, поскольку WatiN имеет хорошую поддержку данной технологии.

Заключение

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

Список использованных источников

  1. http://blog.objectmentor.com/articles/2008/11/27/the-truth-about-bdd;
  2. David Chelimsky, Dave Astels, Zach Dennis, The RSpec Book. The Pragmatic Programmers LLC., 2009;
  3. Andy Hunt, Dave Thomas, Pragmatic Unit Testing in C# with NUnit. The Pragmatic Programmers LLC., 2004;


Эта статья опубликована в журнале RSDN Magazine #2-2010. Информацию о журнале можно найти здесь
    Сообщений 2    Оценка 35 [+1/-0]         Оценить