Статьи

Модульное тестирование асинхронного кода Windows Phone 8 в потоке пользовательского интерфейса с VS 2012.2 CTP4

Это может быть самый загадочный заголовок, который я когда-либо использовал для поста в блоге, но он довольно точно описывает то, что я пытался сделать вчера.

Visual Studio 2012 CTP4 позволяет писать реальные Windows Phone  8 модульных тестов , которые выполняются в визуальном бегуна Studio Test Unit (в смену только на эмуляторе). Поэтому, когда я захотел изучить API-интерфейс маршрутизации, новый для Windows Phone 8, я решил не писать приложение напрямую, а начать с модульного тестирования.

Я создал новое решение с двумя проектами, как я обычно делаю: один с реальным приложением — и одна библиотека классов с моделями представлений, моделями и другой логикой, которая не имеет прямого отношения к пользовательскому интерфейсу. А потом я добавил приложение для тестирования Windows Phone 8.

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

  • Строковое свойство SearchText для заполнения пользователем
  • ObservableCollection MapLocation, называемый MapLocations, который должен быть заполнен геокодером, предназначенный для привязки к какому-либо элементу управления списком, чтобы позволить пользователю выбирать одно из найденных местоположений.
  • Свойство MapLocation SelectedLocation для хранения MapLocation, выбранного пользователем
  • Небольшой метод, чтобы фактически выполнить геокодирование
  • Команда, обертывающая этот метод.

Мой хороший и очень умный друг — и MVP, занимающийся разработкой телефонов, —  Маттео Пагани (Matteo Pagani  ) уже рассмотрел некоторые направления в этом направлении,  написав эту статью,  и вдохновленный ею, я решил также использовать библиотеку Microsoft.Bcl.Async, чтобы использовать async. / Ждите, если у вас никогда не будет слишком большого количества бета-программ в вашем проекте ?

Метод, который я хотел проверить, был довольно прост:

public async Task SearchLocation()
{
  MapLocations.Clear();
  SelectedLocation = null;
  var geoCoder = new GeocodeQuery { 
    SearchTerm = SearchText, 
    GeoCoordinate = new GeoCoordinate() };
  MapLocations.AddRange(await geoCoder.GetMapLocationsAsync());
}

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

[TestMethod]
public async Task TestLocationWrong1()
{
  var testVm = new GeocodeViewModel
    {SearchText = "Springerstraat Amersfoort Netherlands"};
  await testVm.SearchLocation();
  Assert.IsTrue(testVm.MapLocations.Any());
}

Я провел тест… и был весьма удивлен результатом. «Недопустимый доступ  к нескольким потокам ». У меня даже  нет пользовательского интерфейса. Очень интересно. Видимо, GeocodeQuery нужно запускать в потоке пользовательского интерфейса. Почему это так, я понятия не имею. Некоторые люди (привет, Мортен; -)) скажем, что если вам нужно выполнить модульное тестирование в потоке пользовательского интерфейса, вы делаете это неправильно. Возможно, это так, но, похоже, у меня здесь мало выбора, и я все еще хочу протестировать свою модель представления.

Согласно  этой странице,  существует решение UITestMethodAttribute для приложений Магазина Windows, предназначенное для решения подобных проблем, но не для Windows Phone 8 (пока), поэтому, очевидно, мне пришлось использовать Диспетчер. Так как вызов вещи из Dispatcher выполняется асинхронно, а взять 2, конечно, не получилось …

[TestMethod]
public void TestLocationWrong2()
{
  var testVm = new GeocodeViewModel 
  { SearchText = "Springerstraat Amersfoort Netherlands" };
  Deployment.Current.Dispatcher.BeginInvoke(async () => await testVm.SearchLocation());
  Assert.IsTrue(testVm.MapLocations.Any());
}

… По той простой причине, что хотя testVM.SearchLocation теперь запускается в потоке пользовательского интерфейса, Assert — нет, и он выполняется непосредственно после вызова BeginInvoke, а MapLocations все еще пуст при оценке Assert.

Я не знаю, есть ли более разумный способ сделать это, но я использовал AutoResetEvent, чтобы решить это. Я использовал это, чтобы заблокировать тестовый поток, пока поток пользовательского интерфейса не будет завершен, например:

[TestMethod]
public void TestLocationSearchHasResult()
{
  var waitHandle = new AutoResetEvent(false);
  var testVm = new GeocodeViewModel { SearchText = "Springerstraat Amersfoort Netherlands" };
  Deployment.Current.Dispatcher.BeginInvoke(async () =>
    {
      await testVm.SearchLocation();
      waitHandle.Set();
    });
  waitHandle.WaitOne(TimeSpan.FromSeconds(5));
  Assert.IsTrue(testVm.MapLocations.Any());
}

Тестовый поток ждет, пока не будет вызван waitHandle.Set () — или пять секунд, что бы ни случилось вначале — и  затем  он выполняет Assert. И это работает.

Как обычно, вы можете  скачать демонстрационное решение здесь . На самом деле это было решение, демонстрирующее API-интерфейс Route, как было сказано ранее, но я подумал, что эта тема заслуживает отдельного поста в блоге.

Как уже говорилось, этот проект требует установки  Visual Studio 2012 CTP4 . У этого есть лицензия GoLive, но это все еще программа предварительного просмотра. Установите его на свой страх и риск.