2010-11-18 33 views
1

Je suis en train de construire une classe utilisant TDD. La classe est responsable d'attendre qu'une fenêtre spécifique devienne active, puis de déclencher une méthode.Se moquant d'un appel bloquant avec Rhino Mocks

J'utilise la bibliothèque AutoIt COM (pour plus d'informations sur AutoIt look here) puisque le comportement que je veux est en fait une seule méthode dans AutoIt.

Le code est à peu près comme suit:

public class WindowMonitor 
{ 
    private readonly IAutoItX3 _autoItLib; 

    public WindowMonitor(IAutoItX3 autoItLib) 
    { 
     _autoItLib = autoItLib; 
    } 


    public void Run() // indefinitely 
    { 
     while(true) 
     { 
      _autoItLib.WinWaitActive("Open File", "", 0); 
      // Do stuff now that the window named "Open File" is finally active. 
     } 
    } 
} 

Comme vous pouvez le voir la bibliothèque AutoIt COM implémente une Wich d'interface que je peux railler (avec NUnit et Rhino Mocks):

[TestFixture] 
public class When_running_the_monitor 
{ 
    WindowMonitor subject; 
    IAutoItX3 mockAutoItLibrary; 
    AutoResetEvent continueWinWaitActive; 
    AutoResetEvent winWaitActiveIsCalled; 


[SetUp] 
public void Setup() 
    { 
    // Arrange 
    mockAutoItLibrary = MockRepository.GenerateStub<IAutoItX3>(); 
    mockAutoItLib.Stub(m => m.WinWaitActive("", "", 0)) 
       .IgnoreArguments() 
       .Do((Func<string, string, int, int>) ((a, b, c) => 
       { 
        winWaitActiveIsCalled.Set(); 
        continueWinWaitActive.WaitOne(); 
        return 1; 
       })); 

    subject = new Subject(mockAutoItLibrary) 

    // Act 
    new Thread(new ThreadStart(subject.Run)).Start(); 
    winWaitActiveIsCalled.WaitOne(); 
    } 

    // Assert 

    [Test] 
    [Timeout(1000)] 
    public void should_call_winWaitActive() 
    { 
     mockAutoItLib.AssertWasCalled(m => m.WinWaitActive("Bestand selecteren", "", 0)); 
    } 

    [Test] 
    [Timeout(1000)] 
    public void ensure_that_nothing_is_done_while_window_is_not_active_yet() 
    { 
     // When you do an "AssertWasCalled" for the actions when the window becomes active, put an equivalent "AssertWasNotCalled" here. 

    } 

}

Le problème est que le premier test empêche l'expiration du délai. J'ai déjà découvert que lorsque le stub "WinWaitActive" est appelé, il bloque (comme prévu, sur le thread séparé), et quand le "AssertWasCalled" est appelé après cela, l'exécution ne retourne jamais. Je ne sais pas comment procéder, et je n'ai trouvé aucun exemple de simulation d'un appel bloquant.

En conclusion:

est-il un moyen de se moquer d'un appel de blocage sans le délai d'attente des tests?

(PS Je m'intéresse moins à changer le design (c'est-à-dire "Ne pas utiliser un appel bloquant") car il est possible de le faire ici, mais je suis sûr qu'il y a des cas pour changer le design, et je suis intéressé par la solution plus générale.Mais s'il est simplement impossible de se moquer des appels bloquants, des suggestions comme celle-là sont plus que bienvenue!)

+2

Que voulez-vous réellement tester? Pourquoi le simulacre doit-il bloquer? Pourquoi le multi-threading est-il nécessaire dans le test? –

Répondre

3

Je ne sais pas si je comprends le problème.

Votre code appelle simplement une méthode sur le mock (WinWaitActive). Bien sûr, il ne peut pas continuer avant que l'appel ne revienne. C'est dans la nature du langage de programmation et rien que vous devez tester. Si vous testez que WinWaitActive est appelée, votre test est terminé. Vous pouvez tester si WinWaitActive est appelé avant toute autre chose, mais cela nécessite des attentes ordonnées, ce qui nécessite l'ancienne syntaxe mq rhino mocks et ne vaut généralement pas la peine. Vous ne faites rien d'autre que d'appeler une méthode ... donc il n'y a rien d'autre à tester.

Edit: sortie la boucle infinie

Vous pouvez le faire sortir de la boucle infinie en lançant une exception des simulacres. Ce n'est pas très agréable, mais cela évite d'avoir tous ces trucs multi-threads dans le test unitaire.

mockAutoItLibrary = MockRepository.GenerateStub<IAutoItX3>(); 

    // make loop throw an exception on second call 
    // to exit the infinite loop 
    mockAutoItLib 
    .Stub(m => m.WinWaitActive(
     Arg<string>.Is.Anything, 
     Arg<string>.Is.Anything, 
     Arg<int>.Is.Anything)); 
    .Repeat.Once(); 

    mockAutoItLib 
    .Stub(m => m.WinWaitActive(
     Arg<string>.Is.Anything, 
     Arg<string>.Is.Anything, 
     Arg<int>.Is.Anything)); 
    .Throw(new StopInfiniteLoopException()); 

    subject = new Subject(mockAutoItLibrary) 
    try 
    { 
    subject.Run() 
    } 
    catch(StopInfiniteLoopException) 
    {} // expected exception thrown by mock 

    mockAutoItLib.AssertWasCalled(m => m.WinWaitActive("Open File", "", 0)); 
+0

La raison pour laquelle l'appel du bloc est bloqué est qu'il se trouve dans une boucle. Si je ne le bloque pas, il continuera à boucler toutes les instructions de la méthode Run encore et encore. C'est le comportement prévu pour l'application réelle, mais cela fait mal dans les tests unitaires. – dvdvorle

+0

Maintenant, je comprends le problème. J'ai ajouté une section à ma réponse. –

+0

Bonne réponse, je n'étais pas très heureux avec les choses multithread de toute façon. Cela ne serait pas possible s'il y avait une exception d'interception dans la méthode Run(). Mais comme ce n'est pas le cas ici, c'est exactement ce dont j'ai besoin. Merci! – dvdvorle

1

Votre test contient uniquement un appel à la méthode simulée. Par conséquent, il teste uniquement votre maquette au lieu de tout code réel, ce qui est une chose étrange à faire. Nous pourrions avoir besoin d'un peu plus de contexte pour comprendre le problème.Étant donné que vous vous moquez de l'objet COM qui effectue la vérification active de la fenêtre de blocage, vous pouvez juste attendre pendant un certain temps pour imiter le comportement, puis assurez-vous que la fenêtre est bien active par:

le rendant actif par programme. Comment bloquer ne devrait pas être important dans le test, seulement que vous bloquez pendant un certain temps significatif.

Bien que votre code ne comprenne pas comment winWaitActiveIsCancelled et continueWinWaitActive contribuent, je suppose qu'ils devraient être exclus du WinWaitActive Mock. Remplacez-les par un Thread.Sleep(500).

+0

J'utilise AutoResetEvents pour synchroniser les threads. Le comportement prévu est qu'un appel à winWaitActiveCalled.WaitOne() dans le SetUp s'assure que l'exécution dans le Run-thread est à l'appel de winWaitActive. – dvdvorle