5

このクラスを単体テストするための最良の方法を見つけようとしています:

public class FileGroupGarbageCollector
{
    private Task _task;

    private readonly AutoResetEvent _event = new AutoResetEvent(false);

    public void Start()
    {
        _task = Task.Factory.StartNew(StartCollecting);

    }

    public void Stop()
    {
        _event.Set();
    }

    private void StartCollecting()
    {
        do
        {
            Process();
        }
        while (!_event.WaitOne(60000, false));            
    }

    private void Process()
    {
        /* do some work to the database and file system */
    }
}

何かを理解しようとしているだけなので、最も適切に形成されたクラスである必要はありません。

次に、プライベート 'Processs' メソッドがデータベースまたはファイルシステムに何かをしたことをアサートして、サービスを開始してから停止する単体テストを行います。

私の単体テストは次のとおりです(nunit):

    [Test]
    public void TestStart()
    {
        var fg = new FileGroupGarbageCollector(30000);

        fg.Start();

        Thread.Sleep(5000); // i hate this!

        fg.Stop();

        // assert it did what i wanted it to do!
    }

Thread.Sleep() を回避できるように、ここで使用できる方法や素敵なパターンはありますか? 単体テストで眠るという考えは嫌いですが (プロダクション コードは言うまでもなく)、プライベートな機能をテストすることは拒否します! このクラスのパブリック インターフェイスをテストしたいと思います。

どんな答えでも大歓迎です:)

回答後に更新

私はIoCの方法を採用しましたが、それは本当にうまく機能します:)

public interface IEventFactory { IEvent Create(); }

public interface IEvent
{
    bool WaitOne(int timeout);
    void Set();
}

次に、私のモック オブジェクト (Moq を使用):

 var mockEvent = new Mock<IEvent>();
 var mockEventFactory = new Mock<IEventFactory>();

 mockEvent.Setup(x => x.WaitOne(It.IsAny<int>())).Returns(true);
 mockEvent.Setup(x => x.Set());

 mockEventFactory.Setup(x => x.Create()).Returns(mockEvent.Object);

IEvent.WaitOne() を呼び出すとすぐに true が返されて終了するため、Thread.Sleep() は必要ありません。

:)

4

2 に答える 2

7

基本的に、ここで制御の反転パターンを適用する必要があります。コードは高度に結合されているため、テストで問題が発生しています。

すべてのエンティティを明確に分離し、対応するインターフェイスに対して配置する必要があります。エンティティがインターフェースを介して使用される場合、それをモックするのは簡単です。

public interface ITaskFactory {}
public interface IThreadManager {}
public interface ICollectorDatabase {}
public interface IEventFactory {} 

public class FileGroupGarbageCollector 
{
  ITaskFactory taskFactory;
  IThreadManager threadManager;
  ICollectorDatabase database;
  IEventFactory events;

  FileGroupGarbageCollector (ITaskFactory taskFactory,
    IThreadManager threadManager, ICollectorDatabase database,
    IEventFactory events)
  {
     // init members..
  }
}

すべての依存関係が分離されるとすぐに、FileGroupGarbageCollectorはそれらのいずれも直接使用しません。テストでは、IEventFactoryモックはEventを返しますが、WaitOneメソッドが呼び出されても何も起こりません。したがって、コードでスリープする必要はありません。

モック、制御の反転、依存性注入のパターンなど、できる限り多くのことを見つけてください。

于 2011-07-08T16:44:02.133 に答える
2

Thread.Sleep不十分に設計されたプログラムの特徴です。ただし、単体テストでは非常に役立ちます。

他の唯一のオプションは、「時間」の使用法を変更することです。Rxチームは、この分野でいくつかの素晴らしい仕事をしました。それらのスケジューラーはすべてテスト可能です。しかし、それはあなたの特定の状況を助けません(あなたがRxスケジューラーに変換しない限り)。

Thread.Sleep単体テストで本当に避けたい場合は、時間に依存する部分を抽象化する必要があります(制御の反転またはMicrosoft Molesなどのインターセプトライブラリを使用)。問題は、「時間」の完全で一貫した抽象化を作成するのが非常に難しいことです。Thread.Sleep個人的には、ユニットテストで眠りにつくことはありません。

于 2011-07-08T16:52:08.353 に答える