2009-11-19 3 views
27

Au moment où j'ai:Comment tester la méthode order d'appel avec Moq

[Test] 
    public void DrawDrawsAllScreensInTheReverseOrderOfTheStack() { 
     // Arrange. 
     var screenMockOne = new Mock<IScreen>(); 
     var screenMockTwo = new Mock<IScreen>(); 
     var screens = new List<IScreen>(); 
     screens.Add(screenMockOne.Object); 
     screens.Add(screenMockTwo.Object); 
     var stackOfScreensMock = new Mock<IScreenStack>(); 
     stackOfScreensMock.Setup(s => s.ToArray()).Returns(screens.ToArray()); 
     var screenManager = new ScreenManager(stackOfScreensMock.Object); 
     // Act. 
     screenManager.Draw(new Mock<GameTime>().Object); 
     // Assert. 
     screenMockOne.Verify(smo => smo.Draw(It.IsAny<GameTime>()), Times.Once(), 
      "Draw was not called on screen mock one"); 
     screenMockTwo.Verify(smo => smo.Draw(It.IsAny<GameTime>()), Times.Once(), 
      "Draw was not called on screen mock two"); 
    } 

Mais l'ordre dans lequel je dessine mes objets dans le code de production n'a pas d'importance. Je pourrais en faire une en premier, ou deux, peu importe. Cependant, cela devrait être important car l'ordre de tirage est important.

Comment utilisez-vous Moq pour vous assurer que les méthodes sont appelées dans un certain ordre?

Modifier

je me suis débarrassé de ce test. La méthode draw a été retirée de mes tests unitaires. Je vais juste devoir tester manuellement cela fonctionne. L'inversion de la commande a été prise dans une classe de test séparée où elle a été testée, donc tout n'est pas mauvais.

Merci pour le lien sur la caractéristique recherchée. J'espère que ça va bientôt être ajouté, très pratique.

+0

un coup d'oeil à cette réponse ainsi: http://stackoverflow.com/a/39692781/205859 –

Répondre

4

Il semble qu'il ne soit pas implémenté actuellement. Voir Issue 24: MockSequence. This thread traite du problème.

Vous pourriez envisager de réviser vos tests, cependant. Je pense généralement que l'ordre de test conduit à des tests fragiles, car il teste souvent les détails d'implémentation.

EDIT: Je ne suis pas sûr que cela répond à la question de l'OP. La réponse de Lucero peut être plus utile.

+1

Affirmer/vérifier l'ordre peut conduire à des tests fragiles mais PAS parce qu'il teste les détails d'implémentation. Si vous utilisez des Mocks, en particulier avec une classe qui utilise le modèle Inversion de contrôle (Injection de dépendances), vous testez déjà les détails d'implémentation; C'est le but. Je dirais que l'ordre de test ne devrait pas être un modèle commun dans vos tests, mais il existe des cas valides où il n'y a aucune alternative pour conduire le code correct avec TDD. –

+1

@JesseWebb - Je pense que nous sommes seulement en désaccord sur la sémantique.Convenons que certains tests ont une connaissance inappropriée des détails de mise en œuvre internes. Je préfère les tests qui ne seront pas interrompus lorsque le système testé est refactorisé, tant que l'API publique reste inchangée. Comme vous le dites, il existe des cas d'ordre de test, mais ils ne sont pas communs. – TrueWill

+0

La différence entre les tests que vous décrivez (ne concerne que l'API du système testé) et les tests qui utilisent des simulacres est que les premiers ne peuvent être de vrais tests unitaires que si la classe n'a pas de dépendances. Si la classe a des dépendances et que vous n'utilisez pas de mock, ce sont des tests fonctionnels. Je suis d'accord pour dire qu'il y a de la valeur dans les tests en boîte noire et en boîte blanche. –

3

Jetez un oeil à ce blog post, il peut résoudre votre problème.

+0

Impossible d'obtenir que cela fonctionne dans mon cas , Je ne pouvais pas utiliser une file d'attente en échange du tableau. Cela n'a pas d'importance maintenant. Voir éditer. – Finglas

+0

Beau lien, cependant! – TrueWill

1

Sinon, vous auriez pu utiliser les fonctions de rappel et incrémenter/stocker une valeur callIndex.

30

J'ai récemment créé des Moq.Sequences qui permettent de vérifier les commandes en Moq. Vous pouvez lire mon post qui décrit les éléments suivants:

  • Prise en charge des invocations de méthodes, propriété setters et getters.
  • Permet de spécifier le nombre de fois qu'un appel spécifique doit être attendu.
  • Fournit des boucles qui vous permettent d'appeler groupes dans un groupe récurrent.
  • Vous permet de spécifier le nombre fois qu'une boucle doit être attendue.
  • Les appels qui doivent être appelés dans la séquence peuvent être mélangés avec les appels qui sont attendus dans n'importe quel ordre.
  • Support multi-fileté.

L'usage typique ressemble à:

[Test] 
public void Should_show_each_post_with_most_recent_first_using_sequences() 
{ 
    var olderPost = new Post { DateTime = new DateTime(2010, 1, 1) }; 
    var newerPost = new Post { DateTime = new DateTime(2010, 1, 2) }; 
    var posts = new List<Post> { newerPost, olderPost }; 

    var mockView = new Mock<BlogView>(); 

    using (Sequence.Create()) 
    { 
     mockView.Setup(v => v.ShowPost(newerPost)).InSequence(); 
     mockView.Setup(v => v.ShowPost(olderPost)).InSequence(); 

     new BlogPresenter(mockView.Object).Show(posts); 
    } 
} 
+2

Ceci est une extension géniale pour Moq; pour moi le manque de support de séquence est la principale omission dans le projet. OMI cela devrait être tiré dans le coffre (https://github.com/dwhelan/Moq-Sequences). – briantyler

+1

qu'en est-il de lancer une invocation sur différents objets moq à l'intérieur d'une séquence - est-ce supporté? – chester89

0

De le message original que je pouvais supposer que la méthode d'essai les opérations suivantes appel:

var screenOne = new Screen(...); 
var screenTwo = new Screen(...); 
var screens = new []{screenOne, screenTwo}; 
var screenManager = new ScreenManager(screens); 
screenManager.Draw(); 

Lorsque la mise en œuvre de la méthode 'Draw' est quelque chose comme ceci:

public class ScreenManager 
{ 
    public void Draw() 
    { 
     _screens[0].Draw(); 
     _screens[1].Draw(); 
    } 
} 

De m Dans cette perspective, si l'ordre d'appel est très important, une structure supplémentaire (décrivant la séquence) doit être introduite dans le système.

La mise en œuvre plus simple: chaque écran devrait connaître son élément suivant et appeler sa méthode de dessin après dessin lui-même:

// 1st version 
public class Screen(Screen screenSubSequent) 
{ 
    private Screen _screenNext; 
    public Screen(Screen screenNext) 
    { 
     _screenNext=screenNext; 
    } 
    public void Draw() 
    { 
     // draw himself 
     if (_screenNext!=null) _screenNext.Draw(); 
    } 
} 

public class ScreenManager 
{ 
    public void Draw() 
    { 
     _screens[0].Draw(); 
    } 
} 

static void Main() 
{ 
    var screenOne = new Screen(null, ...); 
    var screenTwo = new Screen(screenOne, ...); 
    var screens = new []{screenOne, screenTwo}; 
    var screenManager = new ScreenManager(screens); 
} 

De l'un point, chaque élément d'écran devrait connaître un peu les uns des autres. Ce n'est pas toujours bon. Si oui: vous pouvez créer une classe comme 'ScreenDrawer'. Cet objet stockera propre écran et écran suivant (lui héritera probablement de la classe d'écran à l'aide d'autres mondes:.. Class « ScreenDrawer » décrit la structure du système est ici un scénario de mise en œuvre plus simple:

// 2nd version 
public class ScreenDrawer 
{ 
    private Screen _screenNext; 
    public ScreenDrawer(Screen screenNext, ...) : base (...) 
    { 
     _screenNext=screenNext; 
    } 
    public void Draw() 
    { 
     // draw himself 
     if (_screenNext!=null) _screenNext.Draw(); 
    } 
} 

public class ScreenManager 
{ 
    public void Draw() 
    { 
     _screens[0].Draw(); 
    } 
} 

static void Main() 
{ 
    var screenOne = new ScreenDrawer(null, ...); 
    var screenTwo = new ScreenDrawer(screenOne, ...); 
    var screens = new []{screenOne, screenTwo}; 
    var screenManager = new ScreenManager(screens); 
} 

2ème méthode introduire l'héritage supplémentaire, Résumé: les deux méthodes effectuent des appels sous-séquentiels et ne nécessitent pas de test de séquence, mais nécessitent un test si l'écran en cours en appelle un autre. test si 'ScreenManager' appelle la méthode 'Draw' du 1er élément en séquence

Cette approche:

  1. Plus testable (peut être mis en œuvre en utilisant la plupart du cadre de test sans nécessité de prendre en charge le «test de séquence»);
  2. Plus stable (personne ne peut facilement changer une séquence: il faudra non seulement mettre à jour le code source, mais aussi mettre à jour quelques tests);
  3. Plus orienté objet (vous travaillez avec un objet, pas avec des entités abstraites comme 'séquence');
  4. En conséquence: beaucoup plus supportable.

Merci.

10

Une solution simple: en utilisant callbacks moq

[TestMethod] 
    public void CallInOrder() 
    { 
     // Arrange 
     string callOrder = ""; 

     var service = new Mock<MyService>(); 
     service.Setup(p=>p.FirstCall()).Returns(0).CallBack(()=>callOrder += "1"); 
     service.Setup(p=>p.SecondCall()).Returns(0).CallBack(()=>callOrder += "2"); 

     var sut = new Client(service); 

     // Act 
     sut.DoStuff(); 

     // Assert 
     Assert.AreEqual("12", callOrder); 
    } 
+0

Ne fonctionne pas, il dit qu'il ne peut pas déduire le type de l'utilisation – DaveH

+0

@DaveH avez-vous essayé de débogage? Quelle ligne a le problème? –

+0

@DaveH au lieu de "p => callOrder" utiliser "() => callOrder" –

Questions connexes