2015-12-13 44 views
2

我創建了一個類,SenderClass,它將從其構造函數啓動並運行後臺工作。 方法RunWorker()的運行是一個while(true)循環,它將從隊列中彈出元素,通過方法SendMessage()發送它們,並休眠一段時間以允許將新元素添加到隊列中。單元測試後臺線程與接口

問題在於如何測試從隊列中發送元素而不將它暴露給使用該類的人的方法?

實現:

public class SenderClass : ISenderClass 
{ 
    private Queue<int> _myQueue = new Queue<int>(); 
    private Thread _worker; 

    public SenderClass() 
    { 
     //Create a background worker 
     _worker = new Thread(RunWorker) {IsBackground = true}; 
     _worker.Start(); 
    } 

    private void RunWorker() //This is the background worker's method 
    { 
     while (true) //Keep it running 
     { 
      lock (_myQueue) //No fiddling from other threads 
      { 
       while (_myQueue.Count != 0) //Pop elements if found 
        SendMessage(_myQueue.Dequeue()); //Send the element 
      } 
      Thread.Sleep(50); //Allow new elements to be inserted 
     } 
    } 

    private void SendMessage(int element) 
    { 
     //This is what we want to test 
    } 

    public void AddToQueue(int element) 
    { 
     Task.Run(() => //Async method will return at ones, not slowing the caller 
     { 
      lock (_myQueue) //Lock queue to insert into it 
      { 
       _myQueue.Enqueue(element); 
      } 
     }); 
    } 
} 

通緝接口:

public interface ISenderClass 
{ 
    void AddToQueue(int element); 
} 

所需接口,用於測試目的:

public interface ISenderClass 
{ 
    void SendMessage(int element); 
    void AddToQueue(int element); 
} 

有一個非常簡單的解決方案,說我創造了我的課不正確由於到Single Responsability Principle,我班的目的不是發送消息,而是實現盟友運行發送它們的東西。

我應該有的是另一個類TransmittingClass,它通過自己的接口公開方法SendMessage(int)。 這樣我可以測試該類,SenderClass應該只是通過該接口調用該方法。

但我還有什麼其他選擇與當前的實施?

我可以使我想測試的所有私有方法(所有這些方法)都有[assembly:InternalsVisibleTo("MyTests")],但是是否存在第三個選項?

+0

假設爲了測試的目的你暴露了'SendMessage',你會如何測試它?你不會簡單地通過在別的地方收到郵件來檢查郵件是否被髮送了嗎? – Kenney

+1

發送消息邏輯應該在具有單獨接口的單獨類中實現。這個類應該把新類作爲依賴。你可以單獨測試其他類。 – axlj

+0

我補充說,作爲解決方案@axlj - 謝謝。 –

回答

1

發送消息邏輯應該在具有單獨接口的單獨類中實現。這個類應該把新類作爲依賴。您可以單獨測試新課程。

public interface IMessageQueue 
{ 
    void AddToQueue(int element); 
} 

public interface IMessageSender 
{ 
    void SendMessage(object message); 
} 

public class SenderClass : IMessageQueue 
{ 
    private readonly IMessageSender _sender; 
    public SenderClass(IMessageSender sender) 
    { 
     _sender = sender; 
    } 
    public void AddToQueue(int element) 
    { 
     /*...*/ 
    } 

    private void SendMessage() 
    { 
     _sender.SendMessage(new object()); 
    } 
} 

public class DummyMessageSender : IMessageSender 
{ 
    //you can use this in your test harness to check for the messages sent 
    public Queue<object> Messages { get; private set; } 

    public DummyMessageSender() 
    { 
     Messages = new Queue<object>(); 
    } 
    public void SendMessage(object message) 
    { 
     Messages.Enqueue(message); 
     //obviously you'll need to do some locking here too 
    } 
} 

編輯

爲了解決您的評論,在這裏是用Action<int>的實現。這使您可以在測試類中定義消息發送操作,以模擬SendMessage方法,而無需擔心創建另一個類。 (就我個人而言,我仍然更願意明確定義類/接口)。

public class SenderClass : ISenderClass 
    { 
     private Queue<int> _myQueue = new Queue<int>(); 
     private Thread _worker; 
     private readonly Action<int> _senderAction; 

     public SenderClass() 
     { 
      _worker = new Thread(RunWorker) { IsBackground = true }; 
      _worker.Start(); 
      _senderAction = DefaultMessageSendingAction; 
     } 

     public SenderClass(Action<int> senderAction) 
     { 
      //Create a background worker 
      _worker = new Thread(RunWorker) { IsBackground = true }; 
      _worker.Start(); 
      _senderAction = senderAction; 
     } 

     private void RunWorker() //This is the background worker's method 
     { 
      while (true) //Keep it running 
      { 
       lock (_myQueue) //No fiddling from other threads 
       { 
        while (_myQueue.Count != 0) //Pop elements if found 
         SendMessage(_myQueue.Dequeue()); //Send the element 
       } 
       Thread.Sleep(50); //Allow new elements to be inserted 
      } 
     } 

     private void SendMessage(int element) 
     { 
      _senderAction(element); 
     } 

     private void DefaultMessageSendingAction(int item) 
     { 
      /* whatever happens during sending */ 
     } 

     public void AddToQueue(int element) 
     { 
      Task.Run(() => //Async method will return at ones, not slowing the caller 
      { 
       lock (_myQueue) //Lock queue to insert into it 
       { 
        _myQueue.Enqueue(element); 
       } 
      }); 
     } 
    } 

    public class TestClass 
{ 
    private SenderClass _sender; 
    private Queue<int> _messages; 

    [TestInitialize] 
    public void SetUp() 
    { 
     _messages = new Queue<int>(); 
     _sender = new SenderClass(DummyMessageSendingAction); 
    } 

    private void DummyMessageSendingAction(int item) 
    { 
     _messages.Enqueue(item); 
    } 

    [TestMethod] 
    public void TestMethod1() 
    { 
     //This isn't a great test, but I think you get the idea 
     int message = 42; 
     _sender.AddToQueue(message); 
     Thread.Sleep(100); 
     CollectionAssert.Contains(_messages, 42);    
    } 
} 
+0

這樣,''隊列 Messages''只會用於測試目的,因爲它用於傳出消息。我寧願Mock「IMessageSender」並檢查是否已經調用了「SendMessage(...)」,然後再單獨進行測試。 但SRP很好地顯示。 –

1

它看起來像SenderClass應該不會執行任何發送。它應該簡單地維護隊列。通過發送的構造函數注入Action<int>。這樣,您可以將SendMessage移動到其他地方,然後按照您的喜好調用它。

作爲一個額外的好處,您的SendMessage測試沒有與隊列管理混亂。

看到你的編輯,你似乎不喜歡這種方法,你似乎也不喜歡InternalsVisibleTo方法。您可以通過單獨的界面公開SendMessage並明確實現該界面。這種方式SendMessage仍然可以通過該界面進行調用,但默認情況下,如果沒有一些鑄造扭曲,則不可訪問。它也不會顯示在intellisense自動完成列表中。

+0

我在看到這個之前修改了我的答案 - 你指的是單一責任原則,對嗎? –

+0

是的。 ____ – usr

+0

讓我看看''動作'''因爲我不熟悉它。 –