Автоматизированное тестирование веб-приложения (MS Unit Testing Framework + Selenium WebDriver C#). Часть 1: Введение

image

Введение

Всем Buenos Dias! В своей статье я хотел бы максимально лаконично и просто рассказать о том, как построить процесс автоматизированного тестирования web-приложения с нуля. Первым делом нужно правильно расставить приоритеты и выбрать приемлемое соотношение цена/качество. Сразу определимся — это будет не решение «на коленках» из зоопарка скриптов, которыми часто пользуются при ручном тестировании. В тоже время мы не будем тратить много усилий на проектирование нашего «фреймворка» для автоматизации. Наша цель — предоставить результаты своей бурной деятельности перед руководством в максимально короткие сроки, при этом система должна быть:
  • максимально простой, чтобы тесты могли писать даже специалисты по ручному тестированию
  • гибкой и расширяемой, поскольку мы не можем адекватно оценить весь объем работ на данном этапе
  • кроссплатформенной (Selenium WebDriver C# поддерживает Firefox, Chrome и IE)

В своем примере я буду успользовать .NET (Microsoft Unit Testing Framework) и Selenium WebDriver C#.

Ссылки

Часть 1: Введение
Часть 2.1: Selenium API wrapper — Browser
Часть 2.2: Selenium API wrapper — WebElement
Часть 3: WebPages — описываем страницы
Часть 4: Наконец-то пишем тесты
Публикация фреймворка

Поехали

Итак, первым делом необходимо отобрать некоторое количество тестовых сценариев высокого приоритета, но довольно простых.
Далее создадим в студии новый solution. Вполне логично будет создать в нем 3 проекта: тесты, описание страниц и утилиты. Это и будут наши три базовые сущности.

image

Схема будет довольно простая: тест работает со страницами, запрашивая на них какие-либо данные или выполняя там какие-либо действия. В утилитах мы разместим классы, которые будут отвечать за работу c браузером и web-элементами на страницах посредством Selenium WebDriver. Вполне логично написать обертки (wrapper), поскольку Selenium WebDriver API имеет множество недостатков и может показаться довольно неудобным. Именно в этих обертках мы инкапсулируем (спрячем) весь специфический и некрасивый код. Для примера, создадим классы Browser и WebElement, которые предоставят разработчикам автотестов только тот футкционал, который им нужен. В дальнейшем я хотел бы описать этот процесс в отдельной статье, поэтому не буду останавливаться.

Тесты

Определимся с тем, что будут представлять собой наши тесты. Не будем изобретать велосипед — в идеале тест состоит из 4 частей:
  • входные (тестовые) данные
  • предусловие, т.е. некоторое состояние системы, при котором мы сможем выполнить необходимую проверку
  • взаимодействие с web-приложением
  • проверка — сравнение ожидаемого результата с полученным. В MS Unit Testing Framework для этого существует отдельный класс Assert

При этом важно обеспечить атомарность тестов — тест должен проверять одну логическую операцию и в идеале иметь одну единственную проверку. Плюсы данного подхода следующие:
  • сокращение времени анализа результатов тестов
  • не нужно тратить время на реализацию масштабного логирования (не стоит забывать, что MS Unit Testing Framework умеет собирать всевозможную диагностическую информацию в процессе выполнения теста, например: стэк вызовов, лог событий, IntelliTrace, запись фото и видео, анализ кодового покрытия)

Описание web-страниц тестируемого продукта

Что же будут представлять из себя описания страниц?
Во-первых, это описания элементов, с которыми мы будем работать, т.е. способ распознавания их по id, классу, имени, xpath и др. Не надо описывать сразу все имеющиеся на странице элементы, это нерациональная трата времени. При этом все эти описания должны быть приватными и не выходить за рамки класса страницы.
Во вторых, страница будет содержать свойства (getters) и методы, с помощью которых тесты смогут получить информацию со страницы, например значение какого-нибудь текстового поля.
И в третьих, страница будет содержать методы для совершения действий на самой странице, например клик по кнопке. Важно отметить, что описание страницы не должно содержать никакой логики и никаких проверок! Проверки должны быть в тестах. И в то же время, здесь не должно быть никаких вызовов Selenium WebDriver API.

Утилиты

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

Заключение и пример

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

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

[TestClass]
public class LogOnTests : TestsBase
{
    [TestMethod]
    public void LogOnWithEmptyLogin()
    {
        #region TestData

        const string login = null;
        const string password = "password";
        const string error = "Empty login!";

        #endregion

        Browser.Open(...);
        LogOnPage.LogOn(login, password);

        Assert.AreEqual(error, LogOnPage.Error,
            "Error expected.");
    }
}

public static class LogOnPage
{
    private static readonly WebElement LoginEdit = new WebElement().ById("Login");
    private static readonly WebElement PasswordEdit = new WebElement().ById("Password");
    private static readonly WebElement LogOnButton = new WebElement().ById("LogOn");
    private static readonly WebElement LogOnValidationError = new WebElement().ById("LogOnValidation");

    public static void LogOn(string login, string password)
    {
        LoginEdit.Text = login;
        PasswordEdit.Text = password;
        LogOnButton.Click();
    }

    public static string Error
    {
        get { return LogOnValidationError.Text; }
    }
}


P.S. Вспомнилась старинная русская поговорка: кто рано встает, тот отлаживает тесты.
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 15
  • +1
    А как насчет использовать кат?
    • 0
      Готово, извиняюсь)
      • +1
        Введение в топике настраивает на долгое повествование с примерами, графиками, проблемами и прочей технической информацией, но в реальности всё быстро обрывается так и не начавшись. Вы собираетесь писать продолжение, или ограничитесь этим?
      • +1
        Такой пример можно было посмотреть и в документации селениума
        • 0
          Не судите строго) Не вижу смысла во введении приводить сложные примеры, поскольку в первой статье я не хотел описывать подробности.
          • 0
            Спасибо за начало, но после прочтения заголовка рассчитывал увидеть подробное описание
            предусловие, т.е. некоторое состояние системы, при котором мы сможем выполнить необходимую проверку
            Взять тот же пример с тестом авторизации пользователя, как лучше этого пользователя добавить в базу, что бы обеспечить атомарность тестов. Хотелось бы получить ответ в следующей статье. Спасибо.
            • 0
              Конкретно на ваш вопрос ответить могу и сейчас: How to: Create a data-driven unit test. Вообще хранение тестовых данных напрямую не связано с атомарностью тестов, под этим термином я понимаю применение шаблона функционального дизайна, т.е. один тест — одна логическая проверка.
              • 0
                Спасибо за ответ, почитаю статью.
                P.S. Я под атомарностью тестов понимаю:
                1. один тест — одна логическая проверка
                2. работа тестов не зависит от порядка из запуска
                3. до начала и после завершения теста система находиться в одинаковом состоянии
                • 0
                  Не за что) По поводу последнего пункта не соглашусь, это не зона ответственности теста. Для выполнения этих операции предназначены [TestInitialize] и [TestCleanup], по сути это отдельные тесты.

                  [TestInitialize]
                  public void TestInitialize()
                  {
                  	Playback.PlaybackSettings.SearchTimeout = 1000;
                  }
                  
                  [TestCleanup]
                  public void TestCleanup()
                  {
                  	Browser.Quit();
                  }
                  
                  • 0
                    Я имел ввиду немного другой случай. Попробую пояснить на примере:
                    Мы хотим автоматически тестировать web приложение, используем для этих целей MSTest\NUnit\etc… + Selenium WebDriver\WatiN\etc…
                    Соответственно перед запуском тестов мы делаем publish\запускаем iis express\и т.д., в web.config connectionString указывает на тестовый инстанс базы. Потом наш web driver открывает определенны url (по которому доступен сайт). Запускается тест проверки логина юзера, перед началом данного теста нам нужно добавить юзера в тестовую базу, проверить функционал, и удалить его по окончании теста. Потом запускается следующий тест который имеет другие предусловия.
                    Вот у меня и возникло несколько вопросов:
                    1. Как удобней реализовать инициализацию и очистку данных при тестировании ASP.NET MVC 4 приложений в вышеописанном сценарии?
                    2. Стоит ли использовать слой доступа к данным приложении для инициализации и очистки БД?

                    Спасибо.
                    • 0
                      У нас делается тоже самое.
                      Первым делом разделите действия: разворот тестового окружения и тестовая инициализация. Разворот окружения делается отдельно от тестов, это скрипты или батники в простом случае, либо, например, подготовка окружения средствами Team Foundation Server. При развороте происходит откат виртуальной машины к чистому снапшоту, установка и настройка продукта. Обычно после разворота происходит снятие «снимка системы» — бэкап базы либо вообще снапшот виртуальной машины.
                      А вот инициализацию теста (восстановление базы из бэкапа, импорт пользователей) логично размещать в TestInitialize, как и очистку (удаление пользователя либо откат базы) в TestCleanup. В этом случае инициализация это уже часть тестового проекта.
                      Насчет слоя — конечно стоит, не будут же низкоуровневые запросы к БД находиться в тестах? Реализуйте отдельный хелпер, например DatabaseInitializer.
                      • 0
                        Спасибо за пояснение :) Буду пробовать.
            • 0
              -
              • 0
                Вспомнилась другая поговорка: кто рано встаёт, тот потом весь день спит.

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