2013-07-24 31 views
9

我有這個類我想單元測試:等到單元測試的所有任務完成

public class SomeClass 
{ 
    public void Foo() 
    { 
     Bar(); 
    } 

    private void Bar() 
    { 
     Task.Factory.StartNew(() => 
     { 
      // Do something that takes some time (e.g. an HTTP request) 
     }); 
    } 
} 

這是我的單元測試看起來像:

[TestMethod] 
public void TestFoo() 
{ 
    // Arrange 
    var obj = new SomeClass(); 

    // Act 
    obj.Foo(); 
    obj.Foo(); 
    obj.Foo(); 

    // Assert 
    /* I need something to wait on all tasks to finish */ 
    Assert.IsTrue(...); 
} 

所以,我需要讓單元測試線程等待,直到開始斷言之前,在Bar方法中開始的所有任務都完成了他們的工作。

重要:我不能改變SomeClass

我怎麼能這樣做?

+1

不幸的是,你的設計是壞的。你不應該在不返回任何東西給調用者等待的情況下產生新的任務。這是特別有問題的,因爲託管線程池的線程(執行任務的線程)被標記爲後臺線程,這意味着如果應用程序的前臺線程終止,您的任務可能會在完成之前中止。 – Douglas

+0

我知道,但不幸的是我無法改變它,上面的示例只是簡化了我面臨的複雜情況。 – mmutilva

回答

20

一種方法來解決這個問題,以這樣的方式,將允許定義自己的任務調度您要跟蹤嵌套任務的完成情況。例如,你可以定義一個調度程序,同步執行任務,如下圖所示:

class SynchronousTaskScheduler : TaskScheduler 
{ 
    protected override void QueueTask(Task task) 
    { 
     this.TryExecuteTask(task); 
    } 

    protected override bool TryExecuteTaskInline(Task task, bool wasPreviouslyQueued) 
    { 
     return this.TryExecuteTask(task); 
    } 

    protected override IEnumerable<Task> GetScheduledTasks() 
    { 
     yield break; 
    } 
} 

隨後,創建此同步任務調度的一個實例,並用它來執行根本任務,這反過來,會生成所有的你的「隱藏」任務。由於嵌套任務繼承了父任務的當前任務調度程序,所有內部任務也將在我們的同步調度程序上運行,這意味着我們最外面的StartNew調用將僅在所有任務完成時返回。

TaskScheduler scheduler = new SynchronousTaskScheduler(); 

Task.Factory.StartNew(() => 
{ 
    // Arrange 
    var obj = new SomeClass(); 

    // Act 
    obj.Foo(); 
    obj.Foo(); 
    obj.Foo(); 
}, 
    CancellationToken.None, 
    TaskCreationOptions.None, 
    scheduler); 

// Assert 
/* I need something to wait on all tasks to finish */ 
Assert.IsTrue(...); 

這種方法的缺點是,你將失去你的任務的所有併發性;但是,您可以通過增強自定義調度程序來解決這個問題,該調度程序是併發的,但仍允許您跟蹤正在執行的任務。

+1

這個答案可能救了我幾個小時的生活。事先找到了很多東西。謝謝! –

+0

我不能讓你的SynchronousTaskScheduler在特定的演員工作,我在這裏發佈的問題:http://stackoverflow.com/questions/26482048/currentthreadtaskscheduler-does-not-finish-synchronous – Console

4
Task.WaitAll(the, list, of, task, objects, you, need, to, wait, on); 

如果它是一個void async方法,那麼你不能做到這一點。設計被破壞。他們只是爲了火而忘了。

+3

+1爲描述性任務名稱 – Sayse

+3

對於@Toto而言,不幸的是,任務對象在其單元測試中不可用 – Hemario

+1

OP問題中沒有涉及異步。 – Console

1

不知道如果你被允許進行這種改變,但我得到它的工作這樣做:

namespace ParallelProgramming.Playground 
{ 
    public class SomeClass 
    { 
     public Task Foo() 
     { 
      return Bar(); 
     } 

     private static Task Bar() 
     { 
      return Task.Factory.StartNew(() => 
       { 
        Console.WriteLine("I fired off. Thread ID: {0}", Thread.CurrentThread.ManagedThreadId); 
        Thread.Sleep(5000); 
        return true; //or whatever else you want. 
       }); 
     } 
    } 

    [TestClass] 
    public class StackOverflow 
    { 
     [TestMethod] 
     public void TestFoo() 
     { 
      // Arrange 
      var obj = new SomeClass(); 

      var results = new ConcurrentBag<Task>(); 
      var waitForMe = Task.Factory.StartNew(() => 
       { 
        // Act 
        results.Add(obj.Foo()); 
        results.Add(obj.Foo()); 
        results.Add(obj.Foo()); 

        return true; 
       }); 


      Task.WaitAll(waitForMe); 

      // Assert 
      /* I need something to wait on all tasks to finish */ 
      Assert.IsTrue(waitForMe.Result); 
      Assert.AreEqual(3, results.Count); 
     } 
    } 
} 
+0

其實如果你在TPL中使用'Thread',它就變成了一個遺留代碼。所以這不是必要的。 – IamStalker

+0

該線程未被建議用於答案。我使用它來給這些方法一些加載時間,因爲它除了Console.Writeline之外什麼都沒做。 –

+0

我在Stephen Cleary的書中讀到過,如果你在TPL應用程序中使用Thread,它已經變成了「Legacy Code」。只是認爲值得一提的是,隊友。 – IamStalker