Это может быть самый загадочный заголовок, который я когда-либо использовал для поста в блоге, но он довольно точно описывает то, что я пытался сделать вчера.
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, но это все еще программа предварительного просмотра. Установите его на свой страх и риск.