Автоматизация тестирования Web-приложений

  • Tutorial


Автоматизация тестирования – место встречи двух дисциплин: разработки и тестирования. Наверное поэтому, я отношу эту практику к сложным, но интересным.

Путем проб и ошибок мы пришли к следующему технологическому стеку:
  1. SpecFlow (опционально): DSL
  2. NUnit: тестовый фреймворк
  3. PageObject + PageElements: UI-абстракиця
  4. Контекст тестирования (информация о целевом окружении, пользователях системы)
  5. Selenium.WebDriver

Для запуска тестов по расписанию мы используем TFS 2012 и TeamCity.
В статье я опишу, как мы к этому пришли, типовые ошибки и пути их решения.

Зачем так сложно?

Очевидно, что автоматизация тестирования имеет множество плюсов. Автоматическое решение:
  1. Экономит время
  2. Исключает человеческий фактор при тестировании
  3. Снимает бремя рутинного регрессионного тестирования

Все, кто хоть раз занимался автоматизированным тестированиям, знают об оборотной стороне медали. Автоматические тесты могут быть:
  1. Хрупкими и «ломаться» из-за изменения UI
  2. Непонятными, содержать код «с душком»
  3. Недостоверными: тестировать неверное поведение или зависеть от особенностей окружения

Для примера, рассмотрим следующий код. По названию видно, что мы тестируем то, что по запросу «gangnam style» гугл выдаст 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();
	}
}

В этом тесте очень много проблем:
  1. Перемешаны слои приложения (драйвер, локаторы, результаты)
  2. Строки зашиты в тесте
  3. Для того, чтобы изменить веб-драйвер, например на IE придется менять все тесты
  4. Локаторы зашиты в тесте и будут дублироваться в каждом тесте заново
  5. Дублирование кода создания веб-драйвера
  6. Assert не сопровожден сообщением об ошибке
  7. Если первый Assert «упадет», то второе условие вовсе не будет проверено
  8. При первом взгляде на тест не ясно, что в нем происходит, придется вчитываться и тратить время на понимание кода

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

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

Ваши тесты – тоже код. Относитесь к ним также трепетно, как и коду в приложении. Тема слоев бизнес-приложений уже достаточно хорошо освещена. Какие слои можно выделить в тестах?
  1. Технический драйвер (WebDriver, Selenium RC, etc)
  2. Контекст тестирования (целевое окружение, пользователи, данные)
  3. Абстракция UI – страницы, виджеты, компоненты страниц (PageObject pattern)
  4. Тесты (тестовый фреймворк: NUnit, xUnit, MSTest)
  5. DSL

Проведем эволюционный рефакторинг и исправим наш тест.

Технический драйвер

В нашем случае, это Selenium.WebDriver. Сам по себе WebDriver – не инструмент автоматизации тестирования, а лишь средство управления браузером. Мы могли бы автоматизировать тестирование на уровне HTTP-запросов и сэкономить кучу времени. Для тестирования веб-сервисов нам вообще не потребуется веб-драйвер: прокси вполне достаточно.
Использование веб-драйвера хорошая идея потому что:
  1. Современные приложения – больше, чем просто запрос-ответ. Сессии, куки, java-script, веб-сокеты. Все это может быть чертовски сложно повторить программным образом
  2. Такое тестирование максимально приближено к поведению пользователя
  3. Сложность написания кода гораздо ниже

К слою технического драйвера относятся:
  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 тестирования приложения нам потребуется некоторое количество входных данных:
  1. Целевое окружение – url, порты тестируемых приложений
  2. Пользователи, с разным ролевым набором

Эта информация не относится к логике тестирования, поэтому мы вынесем это в конфигурационную секцию. Все окружения опишем в конфиге.
<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>
</environmentsConfiguration>

Вместо wd.Url = «google.com»; стало wd.Url = EnvironmentsConfiguration.CurrentEnvironmentBaseUrl;
  1. Мы не должны больше дублировать URL во всех тестах
  2. Чтобы протестировать другое окружение достаточно собрать проект с другой конфигурацией и добавить трансформацию

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

Page Objects

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

Основные преимущества Page Objects:
  1. Разделение полномочий: вся «бизнес-логика» страницы должна помещаться в Page Objects, классы тестов лишь вызывают публичные методы и проверяют результат
  2. DRY – все локаторы помещаются в одном месте. Если когда UI изменится, то мы изменим локатор лишь в одном месте
  3. Скрытие слоя технического драйвера. Ваши тесты будут работать с высокоуровневой абстракцией. В будущем, возможно, вы захотите сменить драйвер: например, использовать PhantomJS, или вообще для каких-то участков отказаться от использования WebDriver, для улучшения производительности. В этом случае, вам придется заменить только код Page Objects. Тесты останутся неизменными
  4. Page Objects позволяет записать локаторы в декларативном стиле

Чего не хватает в Page Objects

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

Частично эти проблемы можно решить с помощью наследования, но агрегация видится предпочтительнее, как с технической точки зрения, так и c точки зрения понимания кода.
Поэтому лучше воспользоваться расширенной версией паттерна – Page Elements. Page Elements – позволяет дробить страницу на более мелкие составляющие – блоки, виджеты и т.д. После чего эти блоки можно переиспользовать в нескольких страницах.
Создадим страницу:
[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 есть пакет WebDriver.Support с прекрасным методом PageFactory.InitElements.
Метод хорош, но имеет побочные эффекты. PageFactory из пакета WebDriver.Support возвращает прокси и не дожидается загрузки элемента. При этом, если все методы синхронизации работают с классом By, который пока не умеет работать с атрибутом FindsBy.
Эта проблема решается созданием базового класса Page.
/// <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)

Для того, чтобы реализовать метод WaitUntilLoaded достаточно собрать все публичные свойства с атрибутами FindBy и воспользоваться классом WebDriverWait. Я опущу техническую реализацию этих методов. Важно, что на выходе мы получим простой и изящный код:
var positionsWidget = Page.GetElement<GoogleSearchResults>();

Остался последний неудобный случай. Существуют некоторые виджеты, которые скрывают/показывают часть элементов в зависимости от состояния. Разбивать такой виджет на несколько с одним свойством каждый – нецелесообразно.
Решение тоже нашлось.
public static IWebElement WaitFor<TPage>(
            Expression<Func<TPage, IWebElement>> expression,
            int timeout = Timeout)

var firstLink = PageHelper.WaitFor<GoogleSearchResults>(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();
	}
} 

На этом этапе стало гораздо лучше:
  1. Класс тестирования снял с себя управление драйвером и делегировал эти обязанности в класс страницы
  2. Мы избавились от дублирования локаторов
  3. Читаемость тестов улучшилась

Тесты

После того, как мы вынесли локаторы и логику в Page Objects, код тестов стал лаконичнее и чище. Однако несколько вещей до сих пор не очень хороши:
  1. Логика создания веб-драйвера дублируется из теста в тест
  2. Логика создания страницы в каждом методе тоже избыточна
  3. Магические строчки, «gangnam style», «PSY — YouTube», ”http://www.youtube.com/user/officialpsy” мозолят глаза
  4. Сам сценарий теста достаточно хрупок: результаты индексации могут измениться и нам придется менять код

Создадим базовый класс тестов
public class WebDriverTestsBase<T> : TestsBase
where 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 и добавим комментарий к Assert’у
[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));
}

  1. Код теста стал понятным
  2. Повторяющиеся операции перенесены в базовый класс
  3. Мы предоставили достаточно информации, в случае падения теста все будет понятно из логов тест-ранера
  4. Можно добавить сколько угодно входных и выходных параметров без изменения кода теста с помощью атрибута TestCase

DSL

В этом коде остались проблемы:
  1. Код стал понятным и чистым, но чтобы его поддерживать в таком состоянии квалификация специалистов, поддерживающих тесты должна быть соответствующей
  2. У отдела QA, скорее всего есть свой тест-план, а наши авто-тесты пока с ним никак не коррелируют
  3. Часто одни и те же шаги повторяются сразу в нескольких сценариях. Избежать дублирования кода можно с помощью наследования и агрегации, но это уже кажется сложной задачей, особенно учитывая то, что порядок шагов может быть разным
  4. Google_SearchGangnamStyle_PsyYouTubeChanelIsOnTop(): CamelCase трудночитаем

С помощью плагина SpecFlow, мы можем решить эти проблемы. SpecFlow позволяет записать тестовые сценарии в Given When Then стиле, а затем, автоматизировать их.
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. Каждый шаг можно реализовывать лишь однажды
  2. Атрибуты Given When Then поддерживают регулярные вырадения – можно создавать повторно использующиеся «функциональные» шаги
  3. QA-отдел может записывать сценарии в проектах авто-тестов
  4. Тестировщики могут писать DSL, а автоматизацию можно поручить программистам
  5. В любой момент времени отчет о пройденных тестах, а значит и количестве разработанного функционала, доступен на CI-сервере

Подробнее о SpecFlow и управлении требованиями с Given When Then можно прочитать в этой статье.

Гайдлайн автоматизатора


  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; }
    

    Лучше всего использовать id. От id может зависеть java-script и фронт-эндщики поменяют его с меньшей вероятностью. Если вы не можете использовать id (динамическая разметка), используйте data-aid (automation-id), или аналогичный атрибут
  2. Икапсулируйте логику вашего приложения в классах страниц (Page Objects), например LogonPage, RegistrationPage, HomePage, OrderPage и т.д.
  3. Выделяйте виджеты и повторяющиеся блоки в «виджеты» (Page Elements), например: Header, Footer, LogonLogoff
  4. Группируйте элементы в виджеты в зависимости от их отображения, например: ConfirmationPopup, EditPopup, AddPopup
  5. Избегайте магических строк в коде теста, выносите их в свойства страниц и виджетов или в хелперы, например OrderSuccessMessage, RegistrationSuccessMessage, InvalidPasswordMessage. Это позволит избежать лишнего кода ожидания загрузки/появления элементов
  6. Выносите повторяющиеся операции в базовые классы тестов, используйте SetUp, TearDown: создание и уничтожение драйвера, создание скриншотов с ошибками, аутентификация, улучшайте читаемость теста
  7. Вынесите Page Objects в отдельную сборку, это поможет избежать дублирования виджетов/страниц. Сборок с тестами может быть много
  8. Используйте Assert только в коде тестов, 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. Избегайте использования Thread.Sleep в качестве таймаута для загрузки элементов. Используйте высокоуровневые абстракции, гарантирующие загрузку необходимых DOM-элементов, например:
    Page.GetElement<GoogleSearchResults>();
    var firstLink = PageHelper.WaitFor<GoogleSearchResults>(w => w.FirstLink);
    

  12. Если вы не используете DSL пишите название тестов в формате [Страница_]Сценарий_Ожидаемое Поведение. Это поможет людям понять, какое именно поведение тестируется. Это может быть особенно важно при смене требований
  13. В DSL группируйте шаги семантически и так, чтобы максимизировать повторное использование шагов
    Не правильно:
    I have logged as a user with empty cart
    

    Правильно:
    I have logged in
    And my cart is empty
    

  14. В DSL сравнивайте кортежи одним шагом, это позволит писать более лаконичный код и получать больше информации при падении теста
    Не правильно:
    When I open Profile page
    I 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 page
    I can see profile info: Patrick Jane +123 45-67-89
    

  15. Используйте black-box тестирование, вынесите инициализацию тестовых данных из кода тестов. Для этого хорошо подойдет, например SSDT-проект
  16. Используйте CI для регулярного запуска тестов, сделайте так, чтобы результаты тестов были общедоступны и понятны


Очень хороший доклад от eBay об автоматизации тестирования можно посмотреть здесь: www.youtube.com/watch?v=tJ0O8p5PajQ
Share post

Similar posts

Comments 18

    +1
    Как бы проверить оба варианта (а лучше все три, тот что с DSL тоже) на десятке живых программистов?
    И оценить такие параметры:
    — Время затраченное на понимание упавшего теста
    — время на исправление теста, например чтобы проверялось второе место
    — количество заблуждений после знакомства с тестом
      +1
      У меня есть статистика только о себе. DSL обычно отлаживается за несколько минут. Чаще всего причина ложных срабатываний: локаторы.
      Если все свалено в одну кучу, то без бутылки отладчика не разберешься. На это может уйти от 10 минут до часа. Понимание логики такого теста сравнимо с постижением дзэн.
        0
        Я испол'зую немного проще связку: SpecFlow + Watin + ООП. Общие методы доступа к Элементам страницы инкапсулированы в сервис-классы. На выходе получаю MSTest. Достаточно легко отлаживать. Запускается все с помощью TeamCity. Тесты називаю Acceptance Tests. Unit Testing для бизнес логики и JavaScript никто не отменял. Есть вопрос, зачем используете TeamCity когда есть TFS?

        Пример теста:

        Scenario: Uncomplete an existing Expense Claim
                Given I have opened "Expense Claim List" screen
                And I have sorted "Expense Claim List" grid by "Completed" column
        	And I have clicked "first" row on "Expense Claim List"
        	And I have "un-checked" the "Complete" tickbox on header
        	When I click "Save" button
        	Then the "Expense Claim successfully saved." success message should appear
        	And the "Expense Claim Detail" grid "should" be editable
        
          0
          # language: ru
          @javascript
          Функционал: Управление записями на осмотр
            Предыстория:
              Допустим я создал компанию
              Допустим есть пользователь
              Допустим пользователь принадлежит этой компании
              Допустим я создал schedule на завтра для этой компании
          
            Сценарий: Отображение списка записей на осмотр
              Пусть я залогинился
              Если я зайду на '/schedules'
              То я должен увидеть 'Антон'
                  И я должен увидеть '###'
                  И я должен увидеть '+7 ### ###-##-##'
              Если я нажму на '#edit_schedule'
              То я должен увидеть 'Текущая'
          
          
            0
            Недавно обсуждали похожу ситуацию. Договорились в таких случаях делать проверку одним шагом

            То я должен увидеть имя: Антон, телефон: ****, не знаю, что у вас во втором пункте: ***
            В этом случае, если тест упадет, вы получите больше информации. Например, вы видите не «Антон», а «Вася». Это значит, что кто-то сменил имя пользователя или это ошибка приложения, которая выдала не того юзера?
            Это не противоречит принципу «проверять одну вещь за один раз». Просто «одна вещь» = профиль пользователя.
            0
            TeamCity сейчас используем в тестовом режиме: сравниваем. Есть несколько преимуществ:
            1. «из коробки» тест-ранер показывает информацию горзадо удобнее, чем ТФС
            2. тим-сити работает быстрее
            3. тим-сити крутится на отдельной машине и не путается с CI-билдами и выкладками, можно было бы ограничиться дополнительным билд-агентом, если бы не пункт 1

            Сейчас разбираемся с новыми фишками 2012 ТФС-а. Включим тесты в отчеты, посмотрим, что получится.
              0
              Интересно было бы почитать о миграции с TFS на TC. Переезд базы кода. И результаты сравнения в целом.
                0
                Мы не мигрировали. TFS используется для nightly builds, CI builds, прогона unit-test'ов, выкладок на окружения. TeamCity стоит на отдельной машине и используется только для прогона системных тестов. Получилось быстрее и удобнее, чем выносить билд-агент TFS на отдельную машину. Тесты не путаются с остальными билдами, привязали SpecFlow-отчеты. Получилось довольно удобно. Сравнивать TFS и TeamCity не корректно. Тут надо говорить о связке, скажем, GIT + Jira +TeamCity VS TFS. Вообще есть плюсы и минусы и там и там. У TFS самое слабое место — это чудовищный VCS. Ждем релиза с GIT (пока доступен только в облаке).
          0
          Чтобы проверялось второе место, нужно писать два теста
            0
            Не совсем.
            Можете передать ожидаемую позицию результата и немного подправить метод CheckResults.

            Автору респект, разложено по полочкам. Мог бы — плюсанул.
              0
              например чтобы проверялось второе место

              Я имел ввиду, что исправление теста, которые должен сделать разработчик, обусловлено изменением требований.
          0
          Автору респект, разложено по полочкам. Мог бы — плюсанул.


          +1
            +1
            Отличная статья, вы молодец. Не ожидал на Хабре настолько подробной статьи по автоматизации тестирования, не самая популярная тут тема.
            Я занимался разработкой подобного фреймворка на похожем стеке (без SpecFlow и c MbUnit вместо NUnit). От SpecFlow отказались по причине проблем с распараллеливанием тестов, т.к. он только с костылями это умеет делать. Вам не критична параллелизация тестов, или вы нашли хорошее решение?
              0
              С параллелизмом есть еще веб-драйвер, который может «зависнуть». Как вариант делать через remote, но там тоже есть свои прелести.
              Мы решили разбивать тесты на сборки «по-интересам»: есть одна сборка для смоуков, дальше по компонентам системы. Каждую сборку можно запустить параллельно. На предыдущем проекте не заморачивались с параллелизмом, на этом, возможно будем. Я подумаю об этом, когда тесты будут выполняться 6-8 часов.
              0
              Спасибо за статью.
              Вы можете выложить код рассмотренного примера?
                0
                Пока нет. Вы можете достаточно просто воссоздать этот пример с помощью copy+paste, alt+enter и nuget. Возможно, в будущем фреймворк уйдет в open source.
                0
                Нет ли планов использовать PhantomJS WebDriver? Есть надежда, что он должен быть быстрее и надёжнее не headless драйверов, но пока не было возможности проверить.
                  0
                  У нас есть поддержка Phantom. Кое-где работает лучше, но есть свои глюки. Пока лучшие показатели дал Firefox. Самое удивительное, что он может работать в non-interactive environment, т.е. при прогоне тестов окна браузера на билд-машине не видно.

                Only users with full accounts can post comments. Log in, please.