Il presente articolo è stato tradotto automaticamente.

MVVM

Creazione di un livello di presentazione verificabile con MVVM

Brent Edwards

Scaricare il codice di esempio

Con applicazioni tradizionali dai giorni Windows Form, la prassi standard per il test era di stendere una vista, scrivere codice nel codebehind della visualizzazione, quindi eseguire l'applicazione come test.Fortunatamente, le cose sono evolute un po' da allora.

L'avvento della Windows Presentation Foundation (WPF) ha portato il concetto di associazione di dati a un livello completamente nuovo.Esso ha permesso un nuovo modello di progettazione denominato Model-View-ViewModel (MVVM) ad evolvere.MVVM consente di separare la logica di presentazione dalla presentazione effettiva.In sostanza, vuol dire per la maggior parte che è possibile evitare la scrittura di codice nel codebehind della visualizzazione.

Questo è un miglioramento importante per coloro che sono interessati a sviluppare applicazioni verificabili.Ora invece di avere la logica di presentazione allegata al code-behind della visualizzazione, che ha un proprio ciclo di vita a complicare test, è possibile utilizzare un oggetto CLR vecchio normale (POCO).Visualizzazione modelli non hanno i vincoli del ciclo di vita che fa una vista.Potete solo creare un'istanza di un modello di visualizzazione in uno unit test e prova di distanza.

In questo articolo, prenderò uno sguardo a come affrontare la scrittura di uno strato di presentazione testabile per applicazioni di utilizzo MVVM.Per illustrare il mio approccio, includerò codice di esempio da un framework open source ha scritto, chiamato Charmed e un'app di accompagnamento, chiamato lettore incantato.Le applicazioni di esempio e quadro sono disponibili su GitHub presso GitHub (github.com/brentedwards/Charmed)。

Charmed quadro presentato nel mio articolo di luglio 2013 (msdn.microsoft.com/magazine/dn296512) come un'applicazione di esempio e quadro di Windows 8.Poi nel mio settembre 2013 articolo (msdn.microsoft.com/magazine/dn385706), discusso rendendolo cross -­piattaforma come un app di Windows 8 e Windows Phone 8 quadro e campione.In entrambi questi articoli, ho parlato di decisioni per mantenere l'app testabile.Ora, io rivisitare quelle decisioni e mostrare come effettivamente andare a testare l'app.Questo articolo utilizza il codice di Windows 8 e Windows Phone 8 per gli esempi, ma è possibile applicare i concetti e le tecniche per qualsiasi tipo di applicazione.

Circa l'applicazione di esempio

L'applicazione di esempio che illustra come approccio di scrittura di uno strato di presentazione testabile è chiamato lettore incantato.Lettore incantato è un semplice blog reader app che funziona sia su Windows 8 e Windows Phone 8.Ha le funzionalità minime necessarie per illustrare i punti chiave che voglio coprire.È multipiattaforma e funziona principalmente lo stesso su entrambe le piattaforme, con l'eccezione che le app di Windows 8 sfrutta alcune funzionalità di Windows 8-specifiche.Mentre l'applicazione è semplice, c'è abbastanza funzionalità di unit test.

Che cos'è il test Unit?

L'idea di unit test è prendere discreti pezzi di codice (unità) e scrivere i metodi di prova che utilizzano il codice nel modo previsto, quindi verificare il che ottengono i risultati attesi.Questo codice di test viene eseguito utilizzando una sorta di framework di test harness.Esistono diversi Framework di cablaggio di prova che funziona con Visual Studio 2012.Nell'esempio di codice, utilizzare MSTest, che è costruito in Visual Studio 2012 (e versioni precedenti).L'obiettivo è quello di avere un obiettivo di metodo di prova unità singola uno scenario specifico.A volte ci vuole unità di diversi metodi di prova per coprire tutti gli scenari che ci si aspetta il metodo o la proprietà di ospitare.

Un metodo di unit test dovrebbe seguire un formato coerente per rendere più facile per gli altri sviluppatori a capire.Il formato è generalmente considerato una best practice:

  1. Organizzare
  2. Atto
  3. Assert

In primo luogo, ci può essere qualche codice di installazione che è necessario scrivere per creare un'istanza della classe da testare, così come eventuali dipendenze che potrebbe avere.Questa è la sezione disponi dello unit test.

Dopo l'unità di prova è fatto ponendo le basi per il test vero e proprio, è possibile eseguire il metodo o la proprietà in questione.Questa è la sezione atto della prova.È possibile eseguire il metodo o proprietà in questione con parametri (quando applicabile) istituito durante la sezione Arrange.

Infine, quando tu hai eseguito il metodo o la proprietà in questione, il test deve verificare il metodo o la proprietà ha fatto esattamente quello che doveva per fare.Questa è la sezione di asserzione della prova.Durante la fase di asserzione, vengono chiamati metodi assert per confrontare i risultati effettivi con i risultati attesi.Se i risultati effettivi sono come previsto, lo unit test.Se così non fosse, il test ha esito negativo.

Seguendo questo formato di pratica migliore, mio test solitamente simile al seguente:

[TestMethod]
public void SomeTestMethod()
{
  // Arrange
  // *Insert code to set up test
  // Act
  // *Insert code to call the method or property under test
  // Assert
  // *Insert code to verify the test completed as expected
}

Alcune persone usano questo formato senza includere commenti di chiamare le varie sezioni del test (Arrange/Act/Assert). Io preferisco avere commenti che separano le tre sezioni solo per assicurarsi di che non perdere la traccia di ciò che un test è in realtà agendo su o quando sto solo creazione.

Un ulteriore vantaggio di avere una suite completa di ben scritto unit test è che esse agiscono come documentazione vivente dell'app. Nuovi sviluppatori visualizzazione codice sarà in grado di vedere come vi aspettavate il codice da utilizzare esaminando i diversi scenari dell'unità test di copertura.

Pianificazione di testabilità

Se si desidera scrivere un'applicazione testabile, aiuta davvero a pianificare in anticipo. Ti consigliamo di progettare l'architettura dell'applicazione quindi è favorevole allo unit test. Metodi statici, sigillato classi, accesso a database e chiamate di servizi Web, che tutti possono fare app difficili o impossibili a unit test. Tuttavia, con una pianificazione, è possibile minimizzare l'impatto che hanno sulla vostra applicazione.

Il lettore affascinato app è tutto leggendo il post di blog. Scaricando questi post comporta l'accesso Web ai feed RSS, e può essere piuttosto difficile da unità che la funzionalità di test. In primo luogo, dovrebbe essere in grado di eseguire unit test rapidamente e in stato disconnesso. Basandosi sull'accesso Web in uno unit test potenzialmente viola tali principi.

Inoltre, uno unit test deve essere ripetibile. Perché Blog solitamente vengono aggiornati regolarmente, potrebbe diventare Impossibile ottenere gli stessi dati scaricati nel corso del tempo. Sapevo in anticipo che unit test la funzionalità che carica il post sarebbe impossibile se non pianificare in anticipo.

Ecco quello che sapevo doveva accadere:

  1. Il MainViewModel necessario a caricare tutti i post che l'utente vuole leggere in una sola volta.
  2. Quei post doveva essere scaricato dai vari feed RSS l'utente ha salvato.
  3. Una volta scaricato, il post doveva essere analizzato nei dati oggetti di trasferimento (DTOs) e reso disponibile per la visualizzazione.

Se metto il codice per scaricare il RSS feed in MainViewModel, improvvisamente sarebbe responsabile di più di caricamento dati e lasciando il databind vista ad esso per la visualizzazione. MainViewModel sarebbe quindi responsabile per fare le richieste Web e l'analisi di dati XML. Quello che voglio è avere chiamata MainViewModel fuori a un supporto per rendere il Web richiesta e analizzare i dati XML. MainViewModel occorrerebbe quindi le istanze degli oggetti che rappresentano i post da visualizzare. Questi sono chiamati DTOs.

Sapendo questo, io posso astratta RSS feed caricamento e l'analisi in un oggetto di supporto che può chiamare MainViewModel. Questo non è il fine della storia, tuttavia. Se basta creare una classe helper che fa il RSS feed dati lavoro, qualsiasi unit test che scrivo per MainViewModel intorno a questa funzionalità finirebbe anche chiamando quella classe helper per fare l'accesso Web. Come accennato prima, che va contro l'obiettivo di unit test. Così, ho bisogno di fare un passo ulteriore.

Se creo un'interfaccia per il feed RSS funzionalità di caricamento dati, posso avere il mio vista modello di lavoro con l'interfaccia anziché una classe concreta. Quindi posso fornire diverse implementazioni dell'interfaccia per quando sono in esecuzione di unit test invece di eseguire le app. Questo è il concetto dietro beffardo. Quando eseguo l'app per davvero, voglio oggetto reale che carica il reale RSS feed di dati. Quando eseguire gli unit test, voglio un oggetto fittizio che finge solo di caricare i dati RSS, ma mai effettivamente esce sul Web. L'oggetto fittizio può creare dati coerenti che sono ripetibile e non cambia mai. Allora, mio unit test può sapere esattamente cosa aspettarsi ogni volta.

Con questo in mente, mia interfaccia per il caricamento del post di blog assomiglia a questo:

public interface IRssFeedService
{
  Task<List<FeedData>> GetFeedsAsync();
}

C'è un solo metodo, GetFeedsAsync, che MainViewModel può utilizzare per caricare i dati di post di blog. MainViewModel non ha bisogno di cure come IRssFeedService carica i dati o come analizza i dati. Tutti i bisogni di MainViewModel a cura di è quella chiamata GetFeedsAsync in modo asincrono restituisce dati di post di blog. Ciò è particolarmente importante data la natura multipiattaforma del app.

Windows 8 e Windows Phone 8 hanno modi diversi di download e l'analisi di dati di feed RSS. Rendendo l'interfaccia IRssFeedService e avendo MainViewModel interagire con esso, invece di scaricare direttamente il feed del blog, evitare costringendo MainViewModel di avere più implementazioni della funzionalità stessa.

Mediante iniezione di dipendenza, posso fare sicuro di dare MainViewModel istanza corretta di IRssFeedService al momento giusto. Come ho detto, ti fornisco una finta istanza di IRssFeedService durante i test di unità. Una cosa interessante sull'utilizzo di codice di Windows 8 e Windows Phone 8 come fondamento per una discussione di test di unità c'è non sono alcun reale dinamici quadri beffardo attualmente disponibili per queste piattaforme. Perché beffardo è una grande parte di come io mio codice unità di prova, dovevo venire con mio modo semplice per creare simulazioni. Il RssFeedServiceMock risultante è mostrato Figura 1.

Figura 1 RssFeedServiceMock

public class RssFeedServiceMock : IRssFeedService
{
  public Func<List<FeedData>> GetFeedsAsyncDelegate { get; set; }
  public Task<List<FeedData>> GetFeedsAsync()
  {
    if (this.GetFeedsAsyncDelegate != null)
    {
      return Task.FromResult<List<FeedData>>(this.GetFeedsAsyncDelegate());
    }
    else
    {
      return Task.FromResult<List<FeedData>>(null);
    }
  }
}

Fondamentalmente, voglio essere in grado di fornire un delegato che può impostare come i dati vengono caricati. Se non sono lo sviluppo di Windows 8 o Windows Phone 8, c'è una probabilità abbastanza buona, è possibile utilizzare un dinamico quadro beffardo come Moq, Rhino Mocks o NSubstitute. Rotolare il tuo simulazioni o utilizzare un dinamico quadro beffardo, si applicano gli stessi principi.

Ora che ho l'interfaccia di IRssFeedService creato e iniettato nel MainViewModel, il MainViewModel chiamata GetFeedsAsync sull'interfaccia IRssFeedService e la RssFeed­ServiceMock creato e pronto all'uso, è il momento a unit test interazione di MainViewModel con IRssFeedService. Gli aspetti importanti che voglio testare in questa interazione sono che MainViewModel chiama correttamente GetFeedsAsync e i dati dei feed che viene restituiti sono lo stessi dati feed che MainViewModel rende disponibile tramite la proprietà FeedData. Lo unit test in Figura 2 verifica questo per me.

Figura 2 test alimentano la funzionalità di caricamento

[TestMethod]
public void FeedData()
{
  // Arrange
  var viewModel = GetViewModel();
  var expectedFeedData = new List<FeedData>();
  this.RssFeedService.GetFeedsAsyncDelegate = () =>
    {
      return expectedFeedData;
    };
  // Act
  var actualFeedData = viewModel.FeedData;
  // Assert
  Assert.AreSame(expectedFeedData, actualFeedData);
}

Ogni volta che sono unit test, un modello di visualizzazione (o qualsiasi altro oggetto, per quella materia), mi piace avere un metodo di supporto che mi dà l'istanza effettiva del modello vista alla prova. Modelli di vista rischiano di cambiare nel tempo, che possa coinvolgere diverse cose sta iniettate nel modello di visualizzazione, che significa i parametri del costruttore diverso. Se creare una nuova istanza del modello vista in tutte le mie prove di unità e quindi modificare la firma del costruttore, devo cambiare un intero mazzo di unit test con essa. Tuttavia, se creo un metodo di supporto per creare la nuova istanza di modello di visualizzazione, devo solo fare il cambiamento in un unico luogo. In questo caso, GetViewModel è il metodo di supporto:

private MainViewModel GetViewModel()
{
  return new MainViewModel(this.RssFeedService, 
    this.Navigator, this.MessageBus);
}

Io uso anche l'attributo TestInitialize per verificare che le dipendenze di MainViewModel sono create nuovamente prima dell'esecuzione di ogni test. Ecco il metodo TestInitialize che rende questo accada:

[TestInitialize]
public void Init()
{
  this.RssFeedService = new RssFeedServiceMock();
  this.Navigator = new NavigatorMock();
  this.MessageBus = new MessageBusMock();
}

Con questo, ogni unit test in questa classe di test avrà il nuovissime istanze di tutte le simulazioni quando corrono.

Guardando indietro la prova stessa, il codice seguente crea mio attesi dati feed e imposta il finto RSS feed servizio per restituirlo:

var expectedFeedData = new List<FeedData>();
this.RssFeedService.GetFeedsAsyncDelegate = () =>
  {
    return expectedFeedData;
  };

Si noti che non sto aggiungendo istanze reali FeedData all'elenco di expectedFeedData perché non ho bisogno di. Ho solo bisogno di garantire che l'elenco stesso è ciò che MainViewModel finisce con. Non mi interessa cosa accade quando quella lista ha effettivamente FeedData istanze in esso, almeno per questo test.

La porzione di atto del test ha la seguente riga:

var actualFeedData = viewModel.FeedData;

Posso quindi affermare che il actualFeedData è la stessa istanza dell'expectedFeedData. Se non sono la stessa istanza, quindi MainViewModel non fare il suo lavoro e lo unit test dovesse fallire.

Assert.AreSame(expectedFeedData, actualFeedData);

Un altro importante pezzo di voglio testare l'applicazione di esempio è la navigazione. Il lettore affascinato campione app utilizza vista basata su modello navigazione perché voglio mantenere la vista e la vista modelli separati. Charmed Reader è un'applicazione multipiattaforma, e i modelli di visualizzazione a creare sono utilizzati su entrambe le piattaforme, anche se le opinioni devono essere differenti per Windows 8 e Windows Phone 8. Ci sono un certo numero di motivi perché, ma si riduce al fatto che ogni piattaforma ha XAML leggermente diverso. Per questo motivo, non volevo mio modelli vista per conoscere le mie opinioni, confondere le acque.

Astrarre le funzionalità di navigazione dietro un'interfaccia era la soluzione per diversi motivi. Il primo e più importante è che ogni piattaforma ha diverse classi coinvolte nella navigazione, e non volevo che il mio modello di visualizzazione a preoccuparvi di queste differenze. Inoltre, in entrambi i casi, le classi coinvolte nella navigazione non possono essere deriso. Così, ho sottratto quelle questioni dal modello di visualizzazione e creato l'interfaccia INavigator:

public interface INavigator
{
  bool CanGoBack { get; }
  void GoBack();
  void NavigateToViewModel<TViewModel>(object parameter = null);
#if WINDOWS_PHONE
  void RemoveBackEntry();
#endif // WINDOWS_PHONE
}

Iniettare INavigator nel MainViewModel tramite il costruttore e MainViewModel utilizza INavigator in un metodo denominato ViewFeed:

public void ViewFeed(FeedItem feedItem)
{
  this.
navigator.NavigateToViewModel<FeedItemViewModel>(feedItem);
}

Quando lo guardo come ViewFeed interagisce con INavigator, vedo due cose che voglio verificare mentre scrivo lo unit test:

  1. FeedItem che è passato nel ViewFeed è FeedItem stesso passato nel NavigateToViewModel.
  2. Il tipo di visualizzazione modello passato al NavigateToViewModel è FeedItemViewModel.

Prima di scrivere effettivamente il test, è necessario creare un altro finto, questa volta per INavigator. Figura 3 illustrato la derisione per INavigator. Ho seguito lo stesso schema come prima con i delegati per ogni metodo come un modo per eseguire il test di codice quando viene chiamato il metodo effettivo. Ancora una volta, se stai lavorando su una piattaforma con supporto framework beffardo, non devi creare il tuo finto.

Figura 3 Mock per INavigator

public class NavigatorMock : INavigator
{
  public bool CanGoBack { get; set; }
  public Action GoBackDelegate { get; set; }
  public void GoBack()
  {
    if (this.GoBackDelegate != null)
    {
      this.GoBackDelegate();
    }
  }
  public Action<Type, object> NavigateToViewModelDelegate { get; set; }
  public void NavigateToViewModel<TViewModel>(object parameter = null)
  {
    if (this.NavigateToViewModelDelegate != null)
    {
      this.NavigateToViewModelDelegate(typeof(TViewModel), parameter);
    }
  }
#if WINDOWS_PHONE
  public Action RemoveBackEntryDelegate { get; set; }
  public void RemoveBackEntry()
  {
    if (this.RemoveBackEntryDelegate != null)
    {
      this.RemoveBackEntryDelegate();
    }
  }
#endif // WINDOWS_PHONE
}

Con la mia classe finto navigatore sul posto, posso metterlo da utilizzare in uno unit test, come mostrato Figura 4.

Figura 4 test di navigazione utilizzando il navigatore finto

[TestMethod]
public void ViewFeed()
{
  // Arrange
  var viewModel = this.GetViewModel();
  var expectedFeedItem = new FeedItem();
  Type actualViewModelType = null;
  FeedItem actualFeedItem = null;
  this.Navigator.NavigateToViewModelDelegate = (viewModelType, parameter) =>
    {
      actualViewModelType = viewModelType;
      actualFeedItem = parameter as FeedItem;
    };
  // Act
  viewModel.ViewFeed(expectedFeedItem);
  // Assert
  Assert.AreSame(expectedFeedItem, actualFeedItem, "FeedItem");
  Assert.AreEqual(typeof(FeedItemViewModel), 
    actualViewModelType, "ViewModel Type");
}

Che cosa questo test si preoccupa veramente è che FeedItem passati in giro è corretta e il modello di visualizzazione, cui si accede è corretto. Quando si lavora con le simulazioni, è importante tenere a mente quello che dovrebbe preoccupano per un test particolare e ciò che non si occupano. Per questo test, perché ho l'interfaccia INavigator che MainViewModel sta lavorando contro, non ho bisogno di preoccuparsi se la navigazione avviene in realtà. Che viene gestito da qualunque INavigator implementa per l'istanza di runtime. Ho solo bisogno di cura che INavigator è dato i parametri corretti quando si verifica lo spostamento.

Testabile riquadri secondari

La zona finale per cui vado a guardare il test è riquadri secondari. Riquadri secondari sono disponibili in Windows 8 e Windows Phone 8, e hanno lasciato gli utenti pin elementi di un'app a loro schermi domestici, creando un legame profondo in una parte specifica dell'app. Tuttavia, riquadri secondari vengono gestiti in modo completamente diverso nelle due piattaforme, il che significa che devo fornire implementazioni specifiche della piattaforma. Nonostante la differenza, io sono in grado di fornire un'interfaccia per i riquadri secondari che posso usare su entrambe le piattaforme:

public interface ISecondaryPinner
{
  Task<bool> Pin(TileInfo tileInfo);
  Task<bool> Unpin(TileInfo tileInfo);
  bool IsPinned(string tileId);
}

La classe TileInfo è un DTO con proprietà per entrambe le piattaforme combinate per creare un riquadro secondario. Poiché ogni piattaforma utilizza una diversa combinazione di proprietà da TileInfo, ogni piattaforma ha bisogno di essere testato in modo diverso. Guarderò in particolare la versione di Windows 8. Figura 5 come il mio modello di visualizzazione utilizza ISecondaryPinner.

Ci sono in realtà due cose in corso nel metodo Pin in Figura 5. La prima è l'effettivo blocco del riquadro secondario. Il secondo è risparmio FeedItem nella memoria locale. Così, ecco due cose che ho bisogno di testare. Poiché questo metodo modifica la proprietà IsFeedItemPinned su modello di visualizzazione sulla base dei risultati di tentare di bloccare il FeedItem, inoltre è necessario testare le due possibili risultati del metodo Pin su ISecondaryPinner: true e false. Figura 6 indica il primo test implementato, quali prove lo scenario di successo.

Figura 5 utilizzando ISecondaryPinner

public async Task Pin(Windows.UI.Xaml.FrameworkElement anchorElement)
{
  // Pin the feed item, then save it locally to make sure it's still available
  // when they return.
var tileInfo = new TileInfo(
    this.FormatSecondaryTileId(),
    this.FeedItem.Title,
    this.FeedItem.Title,
    Windows.UI.StartScreen.TileOptions.ShowNameOnLogo |
      Windows.UI.StartScreen.TileOptions.ShowNameOnWideLogo,
    new Uri("ms-appx:///Assets/Logo.png"),
    new Uri("ms-appx:///Assets/WideLogo.png"),
    anchorElement,
    Windows.UI.Popups.Placement.Above,
    this.FeedItem.Id.ToString());
  this.IsFeedItemPinned = await this.secondaryPinner.Pin(tileInfo);
  if (this.IsFeedItemPinned)
  {
    await SavePinnedFeedItem();
  }
}

Figura 6 prove per un Pin di successo

[TestMethod]
public async Task Pin_PinSucceeded()
{
  // Arrange
  var viewModel = GetViewModel();
  var feedItem = new FeedItem
  {
    Title = Guid.NewGuid().ToString(),
    Author = Guid.NewGuid().ToString(),
    Link = new Uri("https://www.bing.com")
  };
  viewModel.LoadState(feedItem, null);
  Placement actualPlacement = Placement.Default;
  TileInfo actualTileInfo = null;
  SecondaryPinner.PinDelegate = (tileInfo) =>
    {
      actualPlacement = tileInfo.RequestPlacement;
      actualTileInfo = tileInfo;
      return true;
    };
  string actualKey = null;
  List<FeedItem> actualPinnedFeedItems = null;
  Storage.SaveAsyncDelegate = (key, value) =>
    {
      actualKey = key;
      actualPinnedFeedItems = (List<FeedItem>)value;
    };
  // Act
  await viewModel.Pin(null);
  // Assert
  Assert.AreEqual(Placement.Above, actualPlacement, "Placement");
  Assert.AreEqual(string.Format(Constants.SecondaryIdFormat,
    viewModel.FeedItem.Id), actualTileInfo.TileId, "Tile Info Tile Id");
  Assert.AreEqual(viewModel.FeedItem.Title,
    actualTileInfo.DisplayName, "Tile Info Display Name");
  Assert.AreEqual(viewModel.FeedItem.Title,
    actualTileInfo.ShortName, "Tile Info Short Name");
  Assert.AreEqual(viewModel.FeedItem.Id.ToString(),
    actualTileInfo.Arguments, "Tile Info Arguments");
  Assert.AreEqual(Constants.PinnedFeedItemsKey, actualKey, "Save Key");
  Assert.IsNotNull(actualPinnedFeedItems, "Pinned Feed Items");
}

C'è installazione un po' più coinvolto con questo che con i test precedenti. In primo luogo, dopo il controller, ho creato un'istanza FeedItem. Si noti che io chiamo ToString su GUID per titolo e autore. Ecco perché non mi interessa quali sono i valori effettivi, mi interessa solo che hanno valori che posso confrontare con la sezione di asserzione. Perché il Link è un Uri, ho bisogno di un Uri valido per questo lavoro, così fornito uno. Ancora una volta, qual è l'effettivo Uri non importa, basta che è valido. Il resto del setup coinvolge assicurando che cattura le interazioni per appuntare e risparmio per il confronto nella sezione asserzione. La chiave per garantire questo codice test in realtà lo scenario di successo è che il PinDelegate restituisce true, che indica il successo.

Figura 7 illustrato praticamente lo stesso test, ma per lo scenario soccombente. Il fatto che PinDelegate restituisce false è ciò che assicura che il test si sta concentrando sullo scenario soccombente. Nello scenario soccombente, anche necessario verificare nella sezione asserzione che non era chiamato SaveAsync.

Figura 7 la prova per un infruttuoso Pin

[TestMethod]
public async Task Pin_PinNotSucceeded()s
{
  // Arrange
  var viewModel = GetViewModel();
  var feedItem = new FeedItem
  {
    Title = Guid.NewGuid().ToString(),
    Author = Guid.NewGuid().ToString(),
    Link = new Uri("https://www.bing.com")
  };
  viewModel.LoadState(feedItem, null);
  Placement actualPlacement = Placement.Default;
  TileInfo actualTileInfo = null;
  SecondaryPinner.PinDelegate = (tileInfo) =>
  {
    actualPlacement = tileInfo.RequestPlacement;
    actualTileInfo = tileInfo;
    return false;
  };
  var wasSaveCalled = false;
  Storage.SaveAsyncDelegate = (key, value) =>
  {
    wasSaveCalled = true;
  };
  // Act
  await viewModel.Pin(null);
  // Assert
  Assert.AreEqual(Placement.Above, actualPlacement, "Placement");
  Assert.AreEqual(string.Format(Constants.SecondaryIdFormat,
    viewModel.FeedItem.Id), actualTileInfo.TileId, "Tile Info Tile Id");
  Assert.AreEqual(viewModel.FeedItem.Title, actualTileInfo.DisplayName,
    "Tile Info Display Name");
  Assert.AreEqual(viewModel.FeedItem.Title, actualTileInfo.ShortName,
    "Tile Info Short Name");
  Assert.AreEqual(viewModel.FeedItem.Id.ToString(),
    actualTileInfo.Arguments, "Tile Info Arguments");
  Assert.IsFalse(wasSaveCalled, "Was Save Called");
}

Applicazioni verificabili di scrittura è una sfida. È particolarmente impegnativo per testare il livello di presentazione dove è coinvolto l'interazione dell'utente. Sapendo in anticipo che stai per scrivere un'applicazione testabile consente di prendere decisioni ad ogni passo in favore di testabilità. Si può anche guardare fuori per le cose che rendono la tua app meno verificabili e trovare modi per risolverli.

Nel corso di tre articoli, ho discusso scrittura apps verificabile con il pattern MVVM, specifico per Windows 8 e Windows Phone 8. Nel primo articolo, ho guardato di scrittura di applicazioni Windows 8 verificabili, mentre ancora sfruttando le funzionalità di Windows 8 specifiche non facilmente verificabile da soli. Il secondo articolo si è evoluta la discussione per includere sviluppando testabile apps multipiattaforma con Windows 8 e Windows Phone 8. Con questo articolo, ho mostrato come approccio di testare le applicazioni che lavorato così duramente per rendere testabili in primo luogo.

MVVM è un argomento vasto, con un numero di diverse interpretazioni. Sono contento di aver potuto condividere la mia interpretazione di un tema così interessante. Trovo un sacco di valore nell'utilizzo MVVM, soprattutto per quanto riguarda testabilità. Ho anche trovare esplorando la testabilità per essere utile e stimolante e io sono felice di condividere il mio approccio alla scrittura di un'applicazione testabile.

Brent Edwards è un consulente principale di piombo per Magenic, una società di sviluppo di applicazioni personalizzate che si concentra sulla pila di Microsoft e lo sviluppo di applicazioni mobile. Egli è anche cofondatore dell'utente Twin città Windows 8 gruppo di Minneapolis. Contattarlo al brente@magenic.com.

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Jason Bock (Magenic)
Jason Bock è una pratica di condurre con Magenic (www.magenic.com). È anche co-autore di Metaprogramming in .NET (www.manning.com/ hazzard). Contattarlo al jasonbock.net o su twitter: @jasonbock.