Когда дело касается модульного тестирования механизмов маршрутизации, то определение области тестирования не выглядит очевидным. Что необходимо тестировать? В простейшем случае, когда ваш проект на MVC Framework содержит всего один маршрут Default, определенный по умолчанию, модульное тестирование теряет большую часть смысла. Но в случаях, когда маршрутов в проекте много, повсеместно используется ограничение и игнорирование маршрутов, модульное тестирование обретает широкую область для применения.
Здесь мы на нескольких примерах рассмотрим, как включить в свой проект тесты маршрутов, ограничений и маршрутов игнорирования.
Тестирование определенных разработчиком маршрутов — достаточно простое, но важное дело. Так как маршрутизация — это один из краеугольных механизмов, который осуществляет работу веб-приложения, правильно функционирующие маршруты — важнейшая часть любой системы. Ошибка при переопределении маршрута или добавлении нового маршрута может стоить выхода из строя как отдельного функционала, так и всего сайта.
Для тестирования маршрутов воспользуемся тремя популярными средствами:
□ NUnit (http://www.nunit.org/index.php) — альтернативное средство тестирования;
□ RhinoMocks (http://ayende.com/projects/rhino-mocks.aspx) — позволяет создавать фальшивые, так называемые, "мок-объекты";
□ MvcContrib (http://www.codeplex.com/MVCContrib) — содержит функционал, расширяющий возможности MVC Framework, в том числе и в сфере модульного тестирования.
Для того чтобы использовать инструменты тестирования в своем проекте, необходимо проделать следующие действия. Во-первых, установите пакет тестирования NUnit. Во-вторых, добавьте в проект ссылки на сборки:
MvcContrib.TestHelper.nunit.framework и Rhino.Mocks так, как показано на рис. 6.6.
После этих двух шагов вы готовы для создания модульных тестов механизма маршрутизации. Для тестирования в MvcContrib существует набор специальных методов расширения, которые позволяют значительно упростить создание тестов. Это метод ShouldMapTo<T>, который позволяет протестировать строку определения маршрута, и метод ShouldBeIgnored, который позволяет протестировать правило игнорирования маршрута.

Для демонстрации создания модульных тестов первым делом определим набор маршрутов, которые будут подвергнуты тестам. Далее представлен фрагмент кода с определением ряда маршрутов:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Product",
"Product/{id}",
new { controller = "Product", action = "GetById" }
);
routes.MapRoute("ProductList",
"ProductList/{year}",
new { controller = "Product", action = "List" },
new { year = new YearConstraint() }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
Как вы можете видеть, в коде определяется три маршрута: Product, ProductList и маршрут по умолчанию Default. Кроме того, определено стандартное правило игнорирования маршрута для игнорирования запросов к
AXD-ресурсам. Обратите внимание, что маршрут с названием ProductList содержит ограничение для параметра year (ранее в этой главе мы рассматривали это ограничение).
Для определения тестов необходимо создать простой класс, например, представленный во фрагменте:
namespace Routing {
using System.Web.Routing;
using MvcContrib.TestHelper;
using NUnit.Framework;
using Routing.Controllers;
[TestFixture]
public class TestRoutes
{
}
}
Обратите внимание на использование атрибута TestFixture. Это атрибут инструмента NUnit, который позволяет определить класс с набором тестов. Добавим в класс простой тест:
[Test]
public void TestSimpleRoute()
{
"~/".Route().ShouldMapTo<HomeController>(x => x.Index());
}
Этот тест направлен на проверку определения маршрута при доступе к сайту по URL без каких-либо параметров или относительных путей. Здесь ожидается, что по умолчанию должен сработать маршрут Default и выполниться действие Index контроллера HomeController.
Для того чтобы запустить наш тест, необходимо скомпилировать проект с тестом и запустить среду NUnit, главное окно которой представлено на рис. 6.7.
C помощью пункта меню File | Open Project добавим в среду сборку нашего проекта тестирования. В моем случае это сборка Routing.dll. После того как сборка загрузится, нажмите кнопку Run для запуска тестов. Как можно убедиться, наш тест не пройден (рис. 6.8).
Для того чтобы тест был пройден, в наш класс модульного тестирования нужно добавить следующий код, который инициализирует механизм


маршрутизации и создает маршруты, необходимые для последующего тестирования:
[TestFixtureSetUp]
public void SetUp()
{
MvcApplication.RegisterRoutes(RouteTable.Routes);
}
[TestFixtureTearDown]
public void TearDown()
{
RouteTable.Routes.Clear();
}
Обратите внимание на атрибуты TestFixtureSetUp и TestFixtureTearDown. Эти атрибуты необходимы для определения кода, который будет вызван перед тестом и сразу после него. В нашем случае перед тестами создаются маршруты, а после теста — разрушаются.
Скомпилируем проект и запустим тест еще раз. На этот раз тест пройден (рис. 6.9).

Для интереса вы можете теперь попробовать модифицировать маршрут Default так, чтобы наш тест перестал работать, например, можно переименовать название действия по умолчанию с Index на Index2. После этого тест будет провален.
Добавим тест для проверки маршрута под названием Product, в нем маршрут определяется простым шаблоном без участия имени действия:
[Test]
public void TestProduct()
{
"~/Product/750".Route()
.ShouldMapTo<ProductController>(x => x.GetById(750));
}
Добавим еще один тест, на этот раз для проверки игнорирования маршрута для AXD-запросов:
[Test]
public void TestIgnoreAxd()
{
"~/someroutetoigonre.axd".ShouldBeIgnored();
}
Запустите тесты и убедитесь, что они пройдены. Обратите внимание на используемый метод расширения ShouldBeIgnored, который предназначен как раз для тестирования игнорирования маршрутов.
Последний наш тест будет направлен на проверку ограничений маршрута. Для тестирования маршрута ProductList определим два следующих теста:
[Test]
public void TestProductListValidYear()
{
"~/ProductList/2009".Route()
.ShouldMapTo<ProductController>(x => x.List(2009));
}
[Test]
public void TestProductListInvalidYear()
{
Assert.AreNotEqual("~/ProductList/1800".Route().Values["controller"],
"Product");
}
Первый тест TestProductListValidYear производит знакомые нам действия, при правильном значении параметра year, ограничение не должно действовать и маршрут должен быть сопоставлен контроллеру Product и действию List. Второй тест TestProductListInvalidYear, наоборот, проверяет поведение маршрута при неверном с точки зрения ограничения параметре year (он должен быть более или равен 1900). Для того чтобы протестировать этот момент, сравниваются имя сопоставленного контроллера и имя контроллера Product. Для успешного прохождения теста они не должны быть равны. Теперь, если убрать ограничение из маршрута, тест будет провален.
После написания всех тестов и запуска NUnit мы должны получить следующую картину успешного прохождения всех тестов (рис. 6.10).

В листинге 6.3 приведен код перечисленных ранее тестов для тех, кто использует тестирование на базе встроенного в Visual Studio инструмента MSTest. Различий минимум и все они касаются именования атрибутов, которое в NUnit отличается от MSTest.
Листинг 6.3. Тестирование с помощью MSTest
namespace Routing
{
using System.Web.Routing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MvcContrib.TestHelper; using Routing.Controllers;
[TestClass]
public class TestRoutesMSTest
{
[TestMethod]
public void TestSimpleRoute()
{
"~/".Route().ShouldMapTo<HomeController>(x => x.Index());
}
[TestInitialize]
public void SetUp()
{
MvcApplication.RegisterRoutes(RouteTable.Routes);
}
[TestCleanup]
public void TearDown()
{
RouteTable.Routes.Clear();
}
[TestMethod]
public void TestProduct()
{
"~/Product/750".Route()
.ShouldMapTo<ProductController>(x => x.GetById(750));
}
[TestMethod]
public void TestIgnoreAxd()
{
"~/someroutetoigonre.axd".ShouldBeIgnored();
}
[TestMethod]
public void TestProductListValidYear()
{
"~/ProductList/2009".Route()
.ShouldMapTo<ProductController>(x => x.List(2009));
[TestMethod]
public void TestProductListInvalidYear()
{
Assert.AreNotEqual("~/ProductList/1800".Route()
.Values["controller"], "Product");
}
}
}
Для тестирования и проверки правильности создания маршрутов в вашем приложении может пригодиться небольшой инструмент от разработчиков MVC Framework. Данный инструмент позволяет в режиме отладки проверить все зарегистрированные маршруты на работоспособность. Кроме того, этот инструмент поможет посмотреть на то, как и в каком порядке реагируют ваши маршруты на запросы с различными адресами URL.
Инструмент ASP.NET Routing Debugger доступен для скачивания по адресу http://haacked.eom/archive/2008/03/13/u rl-routing-debugger.aspx.
Для работы с Routing Debugger необходимо добавить в проект ссылку на сборку RouteDebug.dll и в файле Global.asax добавить одну строку кода в методе Application_Start так, как показано в следующем фрагменте:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
}
После этого, запущенный проект вместо ожидаемой страницы выведет специальную страницу с отчетом о зарегистрированных в проекте маршрутах и их параметрах (рис. 6.11).
ASP.NET Routing Debugger может быть полезен, когда ваш проект содержит массу определенных маршрутов. В таком случае вы получаете наглядный инструмент, который покажет вам порядок и параметры маршрутов, и их реакцию на любой запрос. Используя Routing Debugger, вам не придется долго ломать голову над вопросом: "Почему на этот запрос вызывается этот маршрут?"
