2010-10-07 16 views
2

Comment créer un test unitaire pour tester une classe qui se trouve dans une boucle?TDD asynchrone - Blocs de classe de test

Voici le scénario.

J'ai une classe qui est injectée avec une référence à un port série.

La classe a une méthode appelée Send (String data);

Cette méthode publique appelle une méthode privée aysnchronous pour effectuer le travail réel.

La classe en cours de test (CUT) doit effectuer les opérations suivantes lorsque cette méthode est appelée.

1) diviser la chaîne en caractères.
2) envoyer char
3) attendre que le char soit repris
4) envoyer le prochain char (répéter jusqu'à ce que tous les caractères envoyés)

Donc, après l'envoi du premier caractère de la CUT siégera dans une boucle attendre un écho jusqu'à ce qu'il en reçoive un ou expire.

Le problème que j'ai une fois que le CUT est entré dans cette boucle, il va bloquer la classe de test jusqu'à ce qu'il expire.

Depuis que j'ai besoin de la classe de test pour envoyer l'écho à la CUT im coincé.

Pour tester cela, j'ai créé un faux port série et j'utilise NUnit.

Le test est ci-dessous. L'idée est que, après avoir envoyé la chaîne de test, j'attends que la CUT réponde. Chaque fois que le CUT écrit un caractère sur le port série, l'attente est annulée et j'écris un écho sur le port série et le CUT répond à cela en envoyant le caractère suivant.

[Test] 
    public void Test() 
    { 
     _serialPort.DataSentEvent += new EventHandler(_serialPort_DataSentEvent); 

     _completedSync = new ManualResetEvent(false); 

     _wrapperPort.Send("TEST"); 

     _completedSync.WaitOne(1000); // wait for first char 

     Assert.AreEqual("T", _serialPort.BufferOUT); //first char sent 

     _serialPort.BufferIN = "T"; //write the echo 

     _completedSync.WaitOne(1000); //wait for second char 

     Assert.AreEqual("E", _serialPort.BufferOUT); //second char sent 

     //...etc 
    } 

    void _serialPort_DataSentEvent(object sender, EventArgs e) 
    { 
     _completedSync.Set(); 
    } 

Mais ce qui se passe est les blocs de coupe dès que Send (« TEST ») est appelé et le contrôle que revient à la classe de test une fois que la CUT a dépassé le délai d'attente pour un écho.

Étant donné que la méthode Send est en cours d'achèvement sur un autre thread, pourquoi bloque-t-elle la classe de test?

+0

serait sympa de voir le code 'CUT' – Gutzofter

Répondre

1

Je pense que vous mélangez trop de détails d'implémentation dans votre test.

Vous avez spécifié le protocole de port série très précise, donc je définiriez une interface qui décrit ce protocole:

public interface ISerialPort 
{ 
    event EventHandler Echo; 

    void Send(char c); 
} 

Cette force d'interface me envoyer un caractère à la fois. L'encapsuleur de port série fonctionnera correctement lorsqu'il utilise correctement cette interface. Cette approche nécessite une classe wrapper supplémentaire pour le port série réel. C'est cette classe qui doit gérer les opérations asynchrones afin d'implémenter correctement l'interface.

Avec un TestSerialPort, je peux écrire mon test comme suit:

[TestMethod] 
    public void Send_data_as_chars_to_serial_port() 
    { 
     const string data = "TEST"; 
     var serialPort = new TestSerialPort(); 
     var wrapperPort = new WrapperPort(serialPort); 

     wrapperPort.Send(data); 

     Assert.AreEqual(data, serialPort.Buffer); 
     Assert.AreEqual(data.Length, serialPort.SendCount); 
    } 

Le TestSerialPort implémente l'interface, simule le comportement réel du port série, et expose les données du test peut affirmer:

public class TestSerialPort : ISerialPort 
{ 
    public string Buffer; 
    public int SendCount; 

    public void Send(char c) 
    { 
     SendCount++; 
     Buffer += c; 
     Echo.Invoke(this, EventArgs.Empty); 
    } 

    public event EventHandler Echo; 
} 

Une implémentation possible qui satisfait à l'essai ci-dessus:

public class WrapperPort 
{ 
    private readonly ISerialPort serialPort; 
    private Queue<char> buffer; 
    private char current; 

    public WrapperPort(ISerialPort serialPort) 
    { 
     this.serialPort = serialPort; 
     serialPort.Echo += OnEcho; 
    } 

    public void Send(string data) 
    { 
     buffer = new Queue<char>(data); 
     SendNextCharacter(); 
    } 

    private void OnEcho(object sender, EventArgs e) 
    { 
     SendNextCharacter(); 
    } 

    private void SendNextCharacter() 
    { 
     if (buffer.Any() == false) return; 
     current = buffer.Dequeue(); 
     serialPort.Send(current); 
    } 
}