Главная страница   /   20.2. Создание работоспособной навигации (ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

Джеффри Палермо

20.2. Создание работоспособной навигации

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

Сначала мы можем создать базовый класс теста, который извлекает универсальную установку и постобработку нашего объекта веб-браузера IE, как это продемонстрировано ниже.

Листинг 20-2: Создание нашего базового класса теста
[TestFixture]
[ApartmentState(ApartmentState.STA)]
public class WebTestBase
{
	private IE _ie;
	[SetUp]
	public virtual void SetUp()
	{
		_ie = new IE("http://localhost:8084/");
	}
	[TearDown]
	public virtual void TearDown()
	{
		if (_ie != null)
		{
			_ie.Dispose();
			_ie = null;
		}
	}
	protected IE Browser
	{
		get { return _ie; }
	}
	protected virtual void NavigateLink(string rel)
	{
		Link link = Browser.Link(Find.By("rel", rel));
		link.Click();
	}
	protected FluentForm<TForm> ForForm<TForm>()
	{
		return new FluentForm<TForm>(Browser);
	}
	protected void CurrentPageShouldBe(string pageId)
	{
		Browser.TextField(Find.ByName("pageId")).Value.ShouldEqual(pageId);
	}
}

Строка 9: Создает веб-браузер

Строка 12: Запускается в конце каждого теста

Строка 20: Предоставляет экземпляр веб-браузера

Наш новый базовый класс теста создает объект веб-браузера IE с корректным стартовым URL. Если нам потребуются различные стартовые URL, то нам еще нужно будет устранить любую возможность дублирования названия хоста и номера порта.

Мы создаем метод SetUp, который выполняется перед каждым тестом, сохраняя созданный объект IE в локальном поле. В конце каждого теста выполняется метод TearDown. Первоначальный тест упаковывал жизненный цикл объекта IE в блок using. Поскольку удаление блока using не устраняет необходимость высвобождения объекта IE в нашем тесте, нам необходимо вручную освободить наш объект веб-браузера в методе TearDown.

Наконец, для того чтобы предоставить возможность производным классам теста иметь доступ к нашему созданному объекту IE, мы наделяем это поле свойством protected.

Благодаря этим изменениям наш тест пользовательского интерфейса становится легче читать:

Листинг 20-3: Класс ProductEditTester, измененный с целью использования базового класса теста
[TestFixture]
public class ProductEditTester : WebTestBase
{
	[Test]
	public void Should_update_product_price_successfully()
	{
		Browser.Link(Find.ByText("Products")).Click();
		Browser.Link(Find.ByText("Edit")).Click();
		var priceField = Browser.TextField(Find.ByName("Price"));
		priceField.Value = "389.99";
		Browser.Button(Find.ByValue("Save")).Click();
		Browser.Url.ShouldEqual("http://localhost:8084/Product");
		Browser.ContainsText("389.99").ShouldBeTrue();
	}
}

Строка 2: Наследуется от WebTestBase

Строка 7: Использует свойство Browser

Сначала мы модифицируем наш тест таким образом, чтобы он наследовался от базового класса теста, WebTestBase. Мы также могли бы удалить первоначальный блок using, который добавлял некоторые помехи в каждый тест. Наконец, мы заменили все использования переменной первоначального блока using на свойство базового класса Browser.

За несколькими исключениями, каждый из наших тестов пользовательского интерфейса должен направлять нас на наш сайт посредством нажатия на различные ссылки и кнопки. Мы могли бы вручную перейти напрямую к URL, но это блокировало бы обычную навигацию, которую использовал бы конечный пользователь. В нашем первоначальном тесте мы переходили по ссылкам строго с помощью текста типа raw, который демонстрируется конечному пользователю, но этот текст мог довольно легко изменяться. Наши потребители, возможно, захотят изменить текст ссылки Products на Catalog или ссылки Edit на Modify. В действительности, они, возможно, захотят перевести названия на странице на какой-нибудь язык. Каждое из этих изменений могло бы разрушить наш тест, но не обязательно. Мы можем вставить дополнительную информацию в наш HTML для того, чтобы помочь нашему тесту перейти по корректной ссылке посредством ее семантического значения, а не текста, предоставляемого пользователю. На многих сайтах текст, демонстрирующийся конечным пользователям, – это данные, полученные из базы данных или системы управления контентом (CMS). Это делает навигацию посредством текста ссылок типа raw даже более трудной и хрупкой.

Тег anchor уже содержит механизм для описания взаимосвязи связанного документа с текущим документом – атрибут rel. Мы можем воспользоваться преимуществом этого информативного, но не визуального атрибута для того, чтобы в точности описать нашу ссылку. Если существует две ссылки с текстом "Products", то мы можем разграничить их с помощью атрибута rel. Но мы не хотим приниматься за тот же поиск конечного, отображаемого HTML. Вместо этого мы можем предоставить совместно используемую константу для этой ссылки, как это продемонстрировано ниже.

Листинг 20-4: Добавление атрибута rel к ссылке Products
<ul id="menu">
	<li>@Html.ActionLink("Home", "Index", "Home")</li>
	<li>@Html.ActionLink("Products", "Index", "Product",
				null, new { rel = LocalSiteMap.Nav.Products })</li>
	<li>@Html.ActionLink("About", "About", "Home")</li>
</ul>

Ссылка Products теперь добавляет в метод ActionLink для отображения атрибута rel дополнительный параметр безымянного типа. Класс LocalSiteMap – это статический класс, предоставляющий простую навигационную структуру посредством констант, как это продемонстрировано в следующем листинге.

Листинг 20-5: Класс LocalSiteMap
public static class LocalSiteMap
{
	public static class Nav
	{
		public static readonly string Products = "products";
	}
	...
}

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

Мы не хотим начинать использовать те же жестко-закодированные значения rel в нашем тесте и представлении, поэтому мы создаем простую константу, которая может совместно использоваться как кодом нашего теста, так и кодом представления. Это позволяет значению rel модифицироваться без разрушения теста, как это показано в следующем листинге.

Листинг 20-6: Тест пользовательского интерфейса, использующий вспомогательный метод для навигации по ссылкам
[TestFixture]
public class ProductEditTester : WebTestBase
{
	[Test]
	public void Should_update_product_price_successfully()
	{
		NavigateLink(LocalSiteMap.Nav.Products);
		...
	}
}

Метод NavigateLink – это вспомогательный метод, который упаковывает работу по поиску ссылки с атрибутом rel и нажимает на эту ссылку. Ниже продемонстрировано определение этого метода.

Листинг 20-7: Метод NavigateLink в нашем классе WebTestBase
protected virtual void NavigateLink(string rel)
{
	var link = Browser.Link(Find.By("rel", rel));
	link.Click();
}

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

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