Май 2015

Том 30 выпуск 5


Современные приложения - Комплексное тестирование современных веб-сайтов и приложений

Rachel Appel | Май 2015

Рэчел Аппель(Rachel Appel)Программное обеспечение становится все сложнее и сложнее. Будь то приложение для Windows, iOS, Web, Android, устройства для Интернета вещей (Internet of Things, IoT) или умных часов (smartwatch) — это ПО делает много всяких вещей. Соответственно ПО должно быть корректным. Оно должно работать согласно спецификациям. А значит, разработчики должны тестировать свое ПО.

Без тестирования трудно быть уверенным в том, что код делает то, что должен. Кроме того, стало труднее изолировать код, чтобы исправлять ошибки. Хотя тестирование важно, гораздо важнее тестирование по обоснованным причинам, а не просто ради самого тестирования. Небольшие смарт-тесты всегда одерживают победу над бессмысленными тестами, которые охватывают всю кодовую базу. В этой статье рассказывается, как выполнять модульное тестирование кода на серверной и клиентской стороне, а также как автоматизировать тестирование UI с помощью кодированных тестов UI (coded UI tests, CUIT). Эти три основные области тестирования позволят проверить главные аспекты функциональности вашего ПО.

Модуль за модулем

Модульное тестирование помогает уменьшить количество ошибок и предоставляет документацию для тестируемого кода. Также важны регрессия, одобрение пользователем (user acceptance), автоматизация UI и другие показатели качества. Бюджеты зачастую весьма ограничены, а группы малы, поэтому приходится выбирать, что можно сделать. Вы должны решать, сколько усилий по обеспечению качества будет вложено в ваше приложение. По самому минимуму модульное тестирование и тесты UI помогут сохранить качество на высоком уровне.

Существует ряд коммерческих и с открытым исходным кодом пакетов для модульного тестирования. Многие популярные наборы для тестирования публикуются в виде NuGet-пакетов, поэтому вы можете легко загрузить их прямо в Visual Studio. Несколько наиболее популярных инфраструктур тестирования для C# — это xUnit, nUnit, MSTest и TestDriven.NET. Для JavaScript имеются qUnit, JSUnit и YUI Test.

Хотя модульное тестирование часто является типом тестирования по умолчанию, существуют и другие способы тестирования ПО. К ним относятся автоматизация UI или CUIT, тестирование на одобрение пользователем, регрессионное тестирование, интеграционное тестирование и все типы специализированных тестов. В Википедии перечисляются десятки способов тестирования ПО (bit.ly/14066q8).

Хорошие модульные тесты проверяют только одну функцию за один тест. Принцип единственной обязанности (single responsibility principle, SRP) — один из способов обеспечить модульность вашего кода. SRP означает, что данный код сфокусирован на одной операции и выполняет ее хорошо — будь то код реализации или сам тест. Модульные тесты не должны напрямую обращаться к базам данных, файлам и другим ресурсам. Они должны полагаться на имитирующие объекты (mock objects), которые симулируют эти ресурсы; кроме того, как и любой другой код, модульные тесты должны быть компактными.

Другой способ написания хороших тестов — соблюдение принципов разработки на основе тестов (test-driven development, TDD). TDD — это метод разработки кода на основе набора тестов. Разработчик создает тесты, которые проваливаются, затем пишет подходящий код, удовлетворяющий тесту, а потом выполняет рефакторинг. Я называю этот метод «Red, Green, Refactor». Он помогает структурировать и писать согласованный код. А согласованность — это суть разработки ПО. Хотя рассмотрение TDD выходит за рамки этой статьи, изучите этот метод и опробуйте его в своей группе.

В этой статье обсуждается модульное тестирование с помощью xUnit и qUnit. Я также опишу тестирование автоматизации UI, используя CUIT. Благодаря этому вы ознакомитесь с наиболее важными типами тестов, которые обязательно понадобятся вам.

Тестирование серверного кода на C# с помощью xUnit

Вы приятно удивитесь тому, насколько легко интегрируется пакет модульного тестирования в Visual Studio. Многие инфраструктуры доступны в виде NuGet-пакетов. Например, если вы предпочитаете использовать xUnit, просто запустите NuGet Package Manager, укажите поиск «xUnit», а затем одним щелчком установите оба его пакета (базовый и средство запуска тестов). С помощью xUnit можно тестировать веб-сайты ASP.NET, приложения Windows Forms, Windows Presentation Foundation (WPF), Windows Store и Windows Phone на любом языке, который при компиляции выдает MSIL-код (Microsoft Intermediate Language). Вы можете тестировать даже код на F#, используя xUnit, — все это технологии разработки Microsoft.

Чтобы использовать xUnit, добавьте проект Class Library в свое решение. Простоты ради вы, возможно, предпочтете придерживаться распространенных соглашений по именованию и писать нечто вроде ProjectName.Tests, где ProjectName — имя тестируемого проекта. В тесте добавьте ссылку на этот проект. Потом присвойте имя и добавьте в тест файл класса, а также класс для тестирования чего-либо.

На рис. 1 показан пример класса теста xUnit с одним тестом и кодом, который он тестирует. Код на рис. 1 определяет статус клиента на основе того, какую сумму он потратил. Тесты на рис. 1 гарантируют корректность кода.

Рис. 1. Тестирование примера xUnit-кода

// Модульные тесты, проверяющие характеристики,
// связанные с клиентом
public class CustomerTests
{
  [Theory]
  [InlineData(99D)]
  [InlineData(1001D)]
  [InlineData(5001D)]
  [InlineData(10001D)]
  public void VerifyCustomerStatus(double TotalSpent) {
    // Arrange           
    var status = new Status();
    var testStatus=OrderForm.Models.Status.StatusLevel.None;
    // Act
    var currentStatus = status.GetCurrentStatus(TotalSpent);
    // Assert           
    Assert.True(testStatus <= currentStatus);
  }
}
// Логика, которая устанавливает статус клиента
public class Status
{
  public StatusLevel GetCurrentStatus(decimal ForAmount)
  {
    var level = StatusLevel.None;
    var amt = Convert.ToInt32(ForAmount);
    if (amt > 1000 && amt <= 5000) { level = StatusLevel.Silver; }
    else if (amt > 5000 && amt <= 10000) { level = StatusLevel.Gold; }
    else if (amt > 10000) { level = StatusLevel.Platinum; }
    return level;
  }
  public enum StatusLevel
  {
    None = 0,
    Silver = 1,
    Gold = 2,
    Platinum = 3
  }          
}

В методах модульного тестирования вы обнаружите популярный шаблон Arrange, Act, Assert (или AAA), который тоже представлен на рис. 1. Сначала вы задаете условия теста, потом действуете по этим условиям и, наконец, проверяете вывод.

AAA применим к чему угодно, но, если вы хотите получить качественное ПО, то должны иметь осмысленные тесты. Инфраструктуры тестирования поставляются с уймой проверок, таких как Equal, NotEqual, Same, NotSame, Contains, DoesNotContain, InRange, IsNotEmpty и ряд других. Конечно, точное именование или методы в разных инфраструктурах отличаются, но каждая из них дает вам достаточно широкий выбор.

Вероятно, вы заметили атрибуты [Theory] и [InlineData], дополняющие класс теста, вместо распространенного атрибута [Test]. Когда у методов тестов нет параметров, в xUnit можно использовать атрибут [Fact], чтобы пометить метод как метод теста. Эти аннотации данных позволяют тесту принимать параметр TotalSpent, который вы видите в сигнатуре метода теста на рис. 1. Один тест будет выполняться для каждого атрибута [InlineData] в методе и отображать результаты каждого теста в Visual Studio Text Explorer (рис. 2).

Метод теста VerifyCustomerStatus с четырьмя точками данных в Test Explorer
Рис. 2. Метод теста VerifyCustomerStatus с четырьмя точками данных в Test Explorer

Вы можете использовать контекстное меню на тестах в Test Explorer, чтобы в любой момент получить доступ к параметрам выполнения, отладки, анализа, профилирования и т. д. Всякий раз, когда вы изменяете код или тесты, вы должны заново компилировать и выполнять тесты. Вы можете указать вариант автоматического выполнения тестов при каждой сборке, выбрав в Visual Studio из меню Test команду Test Settings, а затем указав Run Tests After Build.

Вы не сможете далеко зайти с модульным тестированием, не столкнувшись с необходимостью в имитирующих объектах. В принципе, когда вы пытаетесь тестировать базу данных, вы не хотите модифицировать содержащиеся в ней данные. А значит, нужно использовать имитацию как способ симуляции реальной базы данных, веб-сервиса или бизнес-объекта. Имитация выходит за рамки этой статьи, так что, когда вы дойдете до этой стадии, поищите в Интернете Moq, Telerik JustMock или Rhino Mocks. Это популярное ПО для имитации с полной документацией и поддержкой.

Тестирование клиентского JavaScript-кода с помощью qUnit

QUnit — это инфраструктура тестирования JavaScript-кода на клиентской стороне. Она облегченная, простая и удобная в использовании. Вы можете применять qUnit для тестирования любого кода на JavaScript или jQuery. Поскольку вы имеете дело с клиентским JavaScript-кодом, в UI могут происходить какие-то операции, которые вам не надо тестировать. Вы можете пропускать такие аспекты, как задержку курсора мыши над ссылкой (link hovers) или изменения в оформлении UI. Кроме того, вы можете задействовать тестирование автоматизации UI (UI automation testing) для проверки UI. Ваши бизнес-правила, API-код и проверки на серверной стороне должны охватываться тестами в высокой степени. Любой JavaScript-код, который выполняет логику и проверки, тоже нужно тестировать.

В отличие от скомпилированных языковых тестов, напрямую интегрируемых в Visual Studio, вам придется собрать свое окружение тестов (test harness) qUnit. Однако это довольно легко и требует примерно 17 строк кода, которые можно скопировать с рис. 3.

Рис. 3. HTML-окружение тестов qUnit

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Qunit Unit Testing</title>
  <link href="Content/qunit.css" rel="stylesheet" />
</head>
<body>
  <h1 id="qunit-header">QUnit Test Suite</h1>
  <h2 id="qunit-banner"></h2>
  <div id="qunit-testrunner-toolbar"></div>
  <h2 id="qunit-userAgent"></h2>
  <ol id="qunit-tests"></ol>
  <script src="Scripts/qunit.js"></script>      
  <script src="Scripts/logic.js"></script>
  <script src="Scripts/tests.js"></script>   
</body>
</html>

На рис. 3 показаны ссылки на qunit.css, затем qunit.js, logic.js и tests.js. Этот порядок важен. Вы должны загружать qunit.js до вашей логики, а логику — до тестов. Файл logic.js можно назвать как угодно, поскольку это ваш код. Между qunit.js и тестами может быть несколько файлов .js. HTML-структура страницы окружения тестов (test harness page) содержит несколько тегов head, а также упорядоченный список, который отображает результаты индивидуальных тестов. Все стили находятся в файле qunit.css. Рекомендуется помещать только JavaScript-ссылки в конец веб-страницы сразу за закрывающим тегом </body>, как показано на рис. 3.

Для тестирования с помощью qUnit можно поместить тесты в файл .js и назвать егоtests.js. Я вызываю qUnit-функцию test, которая принимает строковое имя вызываемой функции и анонимную функцию, реально выполняющую тест. Имя, передаваемое функции test, появляется в средстве запуска тестов (test runner) как имя теста (рис. 4). Вместо передачи параметров в тест, используйте динамическую природу JavaScript для неоднократного вызова нужного кода с использованием нескольких контрольных выражений (asserts). На рис. 5 показан вывод от этих выражений в функции теста VerifyCustomerStatus.

Рис. 4. QUnit тестирует функцию getCurrentStatus и саму логику в JavaScript-коде

// Тесты для проверки статусов клиентов
test('VerifyCustomerStatus', function () {
  var silver = getCurrentStatus(1001);
  equals(silver, "Silver", "Pass");
  var gold = getCurrentStatus(5001);
  equals(gold, "Gold", "Pass");
  var platinum = getCurrentStatus(10001);
  equals(platinum, "Platinum", "Pass");
})
// Логика присвоения статусов клиентов
function getCurrentStatus(amt) {
  if (amt > 1000 && amt <= 5000) { return "Silver"; }
  else if (amt > 5000 && amt <= 10000) { return "Gold"; }
  else if (amt > 10000) { return "Platinum"; }
  return "None";
}

Результаты тестов qUnit в браузере
Рис. 5. Результаты тестов qUnit в браузере

Если вы пишете приложения Windows Store или Windows Phone, используя Windows Library for JavaScript (WinJS), не волнуйтесь. Существует qUnitMetro, который можно скачать по ссылке bit.ly/1F2RsO7. Этот пакет позволяет qUnit выполняться в контейнере приложения так, будто это веб-страница. Кодирование и способ написания тестов идентичны применяемым в qUnit, потому что это и есть qUnit.

Тестирование UI с помощью CUIT

Когда нужно автоматизировать тестирование «родных» UI, переключайтесь на CUIT. CUIT — малоизвестное средство тестирования в Visual Studio. Вы можете создавать CUIT в Visual Studio для веб-приложений, приложений Windows Store и Windows Phone. Visual Studio CUIT способны тестировать любой UI в Windows. Вам даже не понадобится исходный код для теста UI. Вы можете конфигурировать эти тесты под любую выполняемую программу.

В Visual Studio есть шаблоны для базовых UI: для Web, Windows Store и Windows Phone. Когда вы создаете проект по одному из этих шаблонов, Visual Studio сразу же выведет первое диалоговое окно, показанное на рис. 6, и спросит у вас, что вы хотите — записать новые тесты или отредактировать существующие. Выберите Record actions, edit UI map or add assertions, и появится окно инструментов (рис. 7). Тот же диалог и окно инструментов будут отображаться при каждом добавлении нового файла CUIT в проект (File | New Item). В этот момент вы можете щелкнуть кнопку записи, а затем приступить к взаимодействию с UI вашей веб-страницы или приложения.

Диалог, выводимый Visual Studio для записи или редактирования кодированных тестов UI (CUIT)
Рис. 6. Диалог, выводимый Visual Studio для записи или редактирования кодированных тестов UI (CUIT)

Инструменты для записи и редактирования контрольных выражений
Рис. 7. Инструменты для записи и редактирования контрольных выражений

Закончив, щелкните кнопку Add and Generate внизу окна (рис. 8), добавьте подробные и описательные метаданные и разрешите Visual Studio сгенерировать код.

Диалоги, которые создают кодированные тесты UI
Рис. 8. Диалоги, которые создают кодированные тесты UI

Когда вы создаете проект Coded UI и добавляете в него Coded UI Test, Visual Studio генерирует приличный объем кода. Однако Visual Studio достаточно «умен» в том, как он генерирует код теста. Вывод очень четкий, особенно для сгенерированного кода. Visual Studio генерирует код, записывая ваши действия при использовании конкретной программы. Если вы щелкнете кнопки записи в окне инструментов (рис. 7), средство записи отслеживает, что вы делаете, чтобы генерировать код, выполняющий те же действия. Вы можете и сами написать этот код, но автоматически генерировать его все же легче.

Как только вы заканчиваете работу с Coded UI Test Builder, Visual Studio создает несколько файлов, первый из которых является Coded UI Test (CUIT). Это файл .cs, который содержит класс с методами, выполняющими те же действия, которые выполнял бы пользователь в вашем UI. Остальные файлы включают UIMap.uitest, UIMap.cs и UIMap.designer.cs. В отличие от традиционных дизайнеров Visual Studio, которые печально известны тем, что генерируют слишком много кода, этот дизайнер ведет себя гораздо лучше. Вы можете легко модифицировать тесты без участия дизайнера. В любой момент дизайнер можно изменить, щелкнув правой кнопкой мыши файл .uitest и выбравEdit with Coded UI Test Builder. Это приведет к запуску окна инструментов, которое вы видели на рис. 7. И вновь вы можете вручную писать свои тесты. Прежде чем решиться на это, изучите ту часть сгенерированного кода, которая выполняет следующие четыре теста.

  1. Проверка корректности создания нового клиента.
  2. Проверка создания нового клиента без ввода его имени.
  3. Проверка создания нового клиента без ввода его адреса.
  4. Проверка создания нового клиента с вводом почтового кода в неправильном формате.

Вы найдете эти четыре теста в файле дизайнера; код в классе Coded UI Test вызывает их последовательно, как видно на рис. 9.

Рис. 9. Фрагмент контента Coded UI Test и файлов сопоставлений дизайнера

// В C#-файле Coded UI Test
[CodedUITest]
  public class CodedUITest1
  {
    [TestMethod]
    public void CodedUITestMethod1()
    {
      this.UIMap.CreateNewCustomerCorrectly();
      this.UIMap.CreateNewCustomerWithoutName();
      this.UIMap.CreateNewCustomerWithoutAddress();
      this.UIMap.CreateNewCustomerWihtoutValidPostalCode();       
    }
    // Какой-то другой код инфраструктуры для Coded UI
  }
// В файле UIMap.Designer.cs для Coded UI
public void CreateNewCustomerWithoutName()
    {
      #region Variable Declarations
      // Щелкаем ссылку Create New
      Mouse.Click(uICreateNewHyperlink, new Point(51, 8));
      // Вводим '' в текстовое поле Name
      uINameEdit.Text = this.CreateNewCustomerMissingNameParams.UINameEditText;
      // Вводим '{Tab}' в текстовое поле Name
      Keyboard.SendKeys(uINameEdit,
        this.CreateNewCustomerMissingNameParams.UINameEditSendKeys,
        ModifierKeys.None);
      // Вводим '234 Lane St' в текстовое поле Address
      uIAddressEdit.Text =
        this.CreateNewCustomerMissingNameParams.UIAddressEditText;
      // Вводим '{Tab}' в текстовое поле Address
      Keyboard.SendKeys(uIAddressEdit,
        this.CreateNewCustomerMissingNameParams.UIAddressEditSendKeys,
        ModifierKeys.None);
  }
}

Кодированные тесты UI являются такими же, как и любые другие тесты. Они предназначены для взаимодействия с UI так, как это делал бы пользователь. К счастью, инструмент Coded UI отлично справляется с записью ваших действий и генерацией кода, который делает то же самое. В любой момент можно удалить эти тесты и сгенерировать заново или модифицировать их безо всякой мороки. Кроме того, как и модульные тесты, вы можете запускать кодированные тесты UI из Visual Studio Test Explorer. Они появятся вслед за модульными тестами.

Повысьте качество своего ПО

Вы получаете реальную выгоду от различных форм тестирования. Важно тестировать даже малые приложения Windows Store или Windows Phone, особенно если вы продаете их в магазине приложений.

Вы можете выполнять модульное тестирование с применением библиотек вроде xUnit и qUnit и использовать автоматическое тестирование веб- и «родных» приложений с помощью CUIT, не говоря уже о других типах важных тестов, таких как тестирование на одобрение пользователем, тестирование интеграции, комплексное тестирование и др. Их добавление после того, как у вас появится устоявшийся набор модульных и UI-тестов, значительно улучшит качество вашего ПО.


Rachel Appelконсультант, автор, преподаватель и бывший сотрудник Microsoft более чем с 20-летним опытом работы в ИТ-индустрии. Она выступает на важнейших отраслевых конференциях, в частности Visual Studio Live!, DevConnections, MIX и др. Эксперт в области разработки приложений для бизнеса на основе стека технологий Microsoft и открытых стандартов Web. Если вы хотите узнать о ней больше, посетите ее сайт rachelappel.com.

Выражаю благодарность за рецензирование статьи эксперту Орену Новотони (Oren Novotony).