0

Considérons le code:test unitaire pour la condition de la course

class TestClass 
{ 
    private bool _someFlag; 
    private object _sharedObject = new object(); 
    private readonly object _syncObject = new object(); 

    public object Read() 
    { 
     //lock (_syncObject) 
     { 
      _someFlag = false; 
      return _sharedObject; 
     } 
    } 

    public void Write(object obj) 
    { 
     //lock (_syncObject) 
     { 
      _someFlag = true; 
      _sharedObject = obj; 
     } 
    } 
} 

Il a question de condition de course. Lorsque nous appelons Read() certains thread peuvent appeler Write() entre _someFlag = false; et return _sharedObject; lignes. Je vais résoudre le problème par l'opérateur lock. Mais pourriez-vous s'il vous plaît m'aider à tester ce problème de condition de course.

Je ne veux pas changer _someFlag à public à des fins de test ou quelque chose comme ça. Je veux faire quelque chose comme ça:

[Fact] 
public void RaceConditionTest() 
{ 
    var correctObject = new object(); 
    var test = new TestClass(); 

    for (int i = 0; i < 1000; i++) 
    { 
     test.Write(correctObject); 
     var assertTask = Task.Run(() => 
     { 
      var actualObj = test.Read(); 
      Assert.True(object.ReferenceEquals(correctObject, actualObj), $"Failed on {i} iteration"); 
     }); 
     //Thread.Sleep(50); 
     var failTask = Task.Run(() => test.Write(new object())); 

     Task.WaitAll(assertTask, failTask); 
    } 
} 

Mais comment puis-je être sûr que assertTask sera lancé avant failTask? Ou peut-être y a-t-il une autre façon de tester ce cas? Merci d'avance.

+0

Les conditions de la course risquent d'être impossibles à tester unitairement, que ce soit dans le positif, et surtout dans le négatif. Pour que le code fasse ce que vous ne voulez pas, vous devez mettre en place des mécanismes de synchronisation qui forcent les opérations dans un ordre fiable, afin que vous puissiez reproduire de façon fiable le comportement indésirable. Mais pour ajouter dans la synchronisation pour produire de façon fiable le comportement indésirable, vous avez * forcé * le code à se comporter mal; vous pouvez tout aussi bien l'écrire pour se comporter correctement, et si vous n'êtes pas sûr de ce que vous avez, vous ne pouvez pas vous fier au test. – Servy

Répondre

0

moi avons séjourné avec cette approche. Mais toujours à la recherche d'un meilleur moyen ... Ce test échoue sur certaines itérations, mais si vous décommentez les opérateurs lock, le test sera réussi.

class TestClass 
{ 
    private IEventRecorder _eventRecorder; 


    private bool _someFlag; 
    private object _sharedObject = new object(); 
    private readonly object _syncObject = new object(); 

#if DEBUG 
    public void SetEventRecorder(IEventRecorder eventRecorder) => _eventRecorder = eventRecorder; 
#endif 

    public object Read() 
    { 
     //lock (_syncObject) 
     { 
#if DEBUG 
      _eventRecorder?.Record(nameof(Read)); 
#endif 
      _someFlag = false; 
      return _sharedObject; 
     } 
    } 

    public void Write(object obj) 
    { 
     //lock (_syncObject) 
     { 
#if DEBUG 
      _eventRecorder?.Record(nameof(Write)); 
#endif 
      _someFlag = true; 
      _sharedObject = obj; 
     } 
    } 

    public interface IEventRecorder 
    { 
     void Record(string eventName); 
    } 
} 

public class TestClassTests 
{ 
    private class EventRecorder : TestClass.IEventRecorder 
    { 
     private string _events = string.Empty; 

     public void Record(string eventName) => _events += eventName; 

     public string Events => _events; 

     public void Reset() => _events = string.Empty; 
    } 

    [Fact] 
    public void RaceConditionTest() 
    { 
     var correctObject = new object(); 
     var eventRecorder = new EventRecorder(); 
     var test = new TestClass(); 
     test.SetEventRecorder(eventRecorder); 

     for (int i = 0; i < 1000; i++) 
     { 
      test.Write(correctObject); 
      var assertTask = Task.Run(() => 
      { 
       var actualObj = test.Read(); 
       if (eventRecorder.Events.StartsWith("WriteRead")) 
        Assert.True(object.ReferenceEquals(correctObject, actualObj), $"Failed on {i} iteration"); 
      }); 
      var failTask = Task.Run(() => test.Write(new object())); 

      Task.WaitAll(assertTask, failTask); 
      eventRecorder.Reset(); 
     } 
    } 
} 
0

vous pouvez vérifier si assertTask est en cours d'exécution ou terminé avant de commencer failTask:

while (assertTask.Status != Running && assertTask.Status != RanToCompletion) 
Thread.Sleep(50); 
+0

Comment cela aide-t-il à tester l'état de la course? J'ai besoin d'avoir les deux tâches exécutées simultanément. Si j'ai terminé l'assertTask, je ne suis pas capable de reproduire l'état de la course. – Serg046

+0

la réponse à "comment puis-je être sûr ** que assertTask sera ** démarré ** avant que failTask" soit de vérifier l'état de la tâche comme je l'ai suggéré ci-dessus. Il n'y a aucun moyen garanti de reproduire l'état de la course. Vous ne pouvez reproduire en exécutant cette boucle plusieurs fois, plus de 1000 fois –