2011-01-30 5 views
12

Je suis un peu nouveau pour TDD. J'ai commencé à créer les propriétés dont j'ai besoin sur le modèle de vue en tant que propriété auto.Unité testant le Viewmodel

public string Firstname { get; set; } 

Je crée ensuite un test

[TestMethod] 
[Tag("Property")] 
public void FirstNameTest() 
{ 
    ViewModel = new CustomerViewModel(); 
    ViewModel.PropertyChanged += (s, e) => 
            { 
             Assert.AreEqual("Firstname", e.PropertyName); 
             Assert.AreEqual("Test", ViewModel.Firstname); 
            }; 
    ViewModel.Firstname = "Test"; 
} 

Alors je prolonger la mise en œuvre effective de faire le test passe comme ceci:

public string Firstname 
{ 
    get { return _contact.FirstName; } 
    set 
    { 
     if (_contact.FirstName == value) 
      return; 

     _contact.FirstName = value; 

     RaisePropertyChanged(() => Firstname); 
    } 
} 

Le problème que j'ai est ce test passe encore pour la propriété Aut. Un conseil pour moi comment je pourrais améliorer mon processus?

+3

Vous ne devez pas placer les assertions dans un lambda. Les assertions lancent des exceptions lorsqu'elles échouent. Si vous faites cela dans les lambdas, alors ceux-ci vont tirer à l'intérieur de l'objet-test et vous courez le risque que ceux-ci soient manipulés par l'objet. Vous devriez plutôt assigner des résultats à certaines variables (généralement booléennes) de la portée du test, puis vous opposer à celles-ci lorsque vous aurez retourné et déroulé la pile des appels. – Tormod

Répondre

5

Vous pouvez essayer d'écrire le test pour être asynchrone. Considérez cette méthode de test:

[TestMethod] 
[Asynchronous] 
public void TestMethod1() 
{ 
    TestViewModel testViewModel = new TestViewModel(); 

    bool firstNameChanged = false; 

    testViewModel.PropertyChanged += 
     (s, e) => 
      { 
       if (e.PropertyName == "FirstName") 
       { 
        firstNameChanged = true; 
       } 
      }; 

    EnqueueCallback(() => testViewModel.FirstName = "first name"); 
    EnqueueConditional(() => firstNameChanged == true); 
    EnqueueTestComplete(); 
} 

Notez l'attribut Asynchrone en haut de la méthode. Il y a deux méthodes importantes ici: EnqueueCallback et EnqueueTestComplete. EnqueueCallback ajoutera des expressions lambda à une file d'attente et la méthode de test attendra que le rappel en cours soit exécuté. Dans le cas présent, nous nous abonnons à l'événement PropertyChanged sur ViewModel et nous définissons une variable booléenne locale sur true lorsque la propriété FirstName notifie une modification. Nous avons ensuite mis en file d'attente deux rappels: l'un pour définir la propriété FirstName et l'autre pour affirmer que la variable booléenne locale a changé de valeur. Enfin, nous devons ajouter un appel à EnqueueTestComplete() afin que le framework sache que le test est terminé. REMARQUE: Pour obtenir EnqueueCallback et EnqueueTestComplete, vous devez hériter de SilverlightTest sur votre classe de test. Vous devez également importer Microsoft.Silverlight.Testing pour obtenir l'attribut Asynchronous. Il devrait ressembler à ceci:

using Microsoft.Silverlight.Testing; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 

namespace Foo.Example.Test 
{ 
    [TestClass] 
    public class Tests : SilverlightTest 
    { 

     // ... tests go here 
    } 
} 
+1

Merci beaucoup. Cette réponse m'a aidé à un niveau Silverlight encore plus. Cependant, je voudrais commenter une chose, Si vous utilisez EnqueueCallback sans n'importe quel cas de EnqueueConditional, ce serait la même chose que si vous rendiez les appels synchrones de toute façon. – Houman

+0

Oui, EnqueueConditional doit être utilisé pour qu'un test attende réellement qu'un événement se déclenche. Je suppose que j'ai rendu mon exemple un peu trop trivial, mes excuses. Il utilise maintenant EnqueueConditional pour attendre que l'état booléen de firstNamedChanged devienne true. – avanek

2

Vous devez avoir un autre test qui affirme réellement que votre PropertyChanged se déclenche. Lorsque vous effectuez ce test, votre propriété auto doit échouer, car l'événement ne se déclenchera jamais.

Here's an example of how to do that in Moq

+0

Merci beaucoup pour l'article, je vais l'étudier bientôt et voir ce que je pourrais en apprendre d'autre. – Houman

10

Vous pouvez faire quelque chose comme ceci:

[TestMethod] 
    [Tag("Property")] 
    public void FirstNameTest() 
    { 
     bool didFire = false; 
     ViewModel = new CustomerViewModel(); 
     ViewModel.PropertyChanged += (s, e) => 
             { 
              didFire = true; 
              Assert.AreEqual("Firstname", e.PropertyName); 
              Assert.AreEqual("Test", ViewModel.Firstname); 
             }; 
     ViewModel.Firstname = "Test"; 
     Assert.IsTrue(didFire); 
    } 
+0

Merci beaucoup pour la réponse claire. Ça fonctionne maintenant. Cependant, Assert lève une exception AssertFailedException à la dernière ligne. Je dois F5 pour continuer et ensuite je vois que le test a échoué dans les résultats. J'utilise Silverlight 4 Unit toolkit de test qui vient avec VS 2010. C'est assez ennuyeux, est-il possible de supprimer cela, je ne veux pas F5 sur 50 tests unitaires plus tard, si quelque chose s'est mal passé. :) – Houman

+1

Il suffit de ne pas exécuter les tests en mode débogage :) –

+0

Je l'ai exécuté sous Release et il arrive toujours avec une interruption. C'est vraiment agaçant. J'utilise des tests unitaires sous Silverlight 4 Test Framework. Une idée de quelqu'un? – Houman

2

Un test devrait échouer à moins que le comportement est le test est déjà mis en œuvre.

Pour tester les notifications de modification de la propriété dans ma dernière tentative, j'ai créé a helper class qui m'a aidé à écrire des tests comme celui-ci (il est dans NUnit)

[Test] 
public void NotifiesChangeIn_TogglePauseTooltip() 
{ 
    var listener = new PropertyChangeListener(_mainViewModel); 

    _mainViewModel.TogglePauseCommand.Execute(null); 

    Assert.That(listener.HasReceivedChangeNotificationFor("TogglePauseTooltip")); 
} 
1

Voilà comment je l'ai fait dans le passé (je l'ai utilisé NUnit il est peut-être un peu différent):

[Test] 
public void ShouldNotifyListenersWhenFirstNameChanges() 
{ 
    var propertiesChanged = new List<string>(); 

    ViewModel = new CustomerViewModel(); 
    ViewModel.PropertyChanged += (s, e) => propertiesChanged.Add(e.PropertyName); 

    ViewModel.Firstname = "Test"; 

    Assert.Contains("Firstname", propertiesChanged);  
    Assert.AreEqual("Test", ViewModel.Firstname); 
} 

a le beau côté -effect d'être en mesure de déboguer et de travailler sur ce qui a changé, si ce n'était pas le Firstname. Très pratique lorsque vous avez plusieurs champs calculés à partir d'autres champs.Vous pouvez également regarder l'autre aspect du comportement dans votre code:

[Test] 
public void ShouldNotNotifyListenersWhenPropertiesAreNotChanged() 
{ 
    var propertiesChanged = new List<string>(); 

    ViewModel = new CustomerViewModel(); 
    ViewModel.Firstname = "Test"; 

    ViewModel.PropertyChanged += (s, e) => propertiesChanged.Add(e.PropertyName); 

    ViewModel.Firstname = "Test"; 

    Assert.AreEqual(0, propertiesChanged.Count); 
} 
-1

Peut-être il y a plus d'arrière-plan à ce code qui n'est pas révélé, mais ce que je vois semble inutilement compliqué. Pourquoi s'embêter avec l'événement RaisePropertyChanged? Vérifiez simplement la propriété après l'avoir définie.

[TestMethod] 
[Tag("Property")] 
public void FirstNameTest() 
{ 
    var expected = "John"; 
    var sut = new CustomerViewModel(); 

    sut.Firstname = expected; 

    Assert.AreEqual(expected, sut.Firstname); 
} 

Cela transforme également le test en un véritable test unitaire.

+0

Ce serait un peu inutile. Pourquoi atout que le framework est capable de traiter correctement le setter d'une auto-propriété? Le test commence à donner un sens au moment où vous voulez vérifier que les événements 'PropertyChanged' sont levés comme prévu. ** Cela ** est la seule chose qui mérite d'être vérifiée dans un tel test. – Martin

+0

@Martin Je serais d'accord que tester le setter d'une auto-propriété ne fournirait aucun avantage. Cependant, si vous regardez le code original (posté par Hooman) vous remarquerez que la propriété est seulement définie si la valeur entrante n'est pas égale à la valeur dans _contact.FirstName. C'est la partie du code avec laquelle mon test refactorisé est concerné. – hitopp

Questions connexes