2013-12-21 90 views
0

我正在運行一個簡單的WPF應用程序,它使用基於事件/ TPL的方法來處理數據。 三大類在本例中使用(查看,演示,模型)如何單元測試WPF + TPL

Snip of Presenter: 

internal void btn_test_Click(object sender, EventArgs e) 
{ 
    Task<Person>.Factory.StartNew(() => GetPerson(id)).ContinueWith(UpdateTest, TaskScheduler.FromCurrentSynchronizationContext()); 
} 

public Person GetPerson(int id) 
{ 
    Person p = Model.GetPerson(id); 
    return p; 
} 

private void UpdateTest(Task<Person> task) 
{ 
    Person p = task.Result; 
    window.tb_test.Text = p.ID + " " + p.Name; // PROBLEM HERE 
} 

所以,我越來越從查看的事件,開始一個新的任務,從我的數據庫或服務獲取數據和更新UI之後。工作完美。

現在我想爲這個場景創建一個單元測試。顯示的值是否正確?

[TestMethod] 

SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); 

waitHandle = new ManualResetEvent(false); 

WPF.MainWindowView mwv = new MainWindowView(); 
mwv.btn_test.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); 
mwv.tb_test.TextChanged += (s, e) => waitHandle.Set(); 

waitHandle.WaitOne(); 

Assert.AreEqual("43 displayvalue", mwv.tb_test.Text); 

WPF應用程序工作正常,但在測試時會出現InvalidoperationException。我試圖用一個調度器,用於更新UI組件調用

window.tb_test.Dispatcher.BeginInvoke((ThreadStart)delegate {window.tb_test.Text = t.ID + " " + t.Name}); 
在UpdateTest

,但「tb_test.textChanged」事件沒有得到我的測試模塊調用altough應用程序本身工作完全正常。

+0

你的問題不是很清楚。 textChanged從哪裏來?我看不到它已在您的SUT(被測系統)中使用過。您想要測試什麼「特定」場景,以及您的測試方法名稱是否丟失。在你的SUT中,你有私人的無效UpdateTest(任務任務),這個被調用? – Spock

回答

0

假設window是WPF Windowtb_test是WPF TextBlock

首先讓我先說說WPF線程模型使其對活WPF對象運行單元測試有點麻煩。就個人而言,與處理所有這些問題的麻煩相比,我發現這些編碼測試的好處很小,尤其是在遵循MVVM設計模式時。將重要的邏輯移動到更多測試友好的位置(閱讀:操縱視圖模型對象的數據綁定和命令)會使這些測試看起來更加冗餘。

如果你可以重構你的設計,以便你可以在一個更可測試的位置獲得所有「重要」的邏輯,那麼這就是我推薦嘗試的方法。我不知道這個代碼庫的歷史是什麼,所以我不想讓它留在那裏。如果你不能,並且/或者你只是好奇,那就讓我們沿着兔子洞走吧......

當你在測試中設置當前SynchronizationContext時,你使用的是一個普通的SynchronizationContext,其Post是使用ThreadPool實現(即,回調可以在任何線程上執行)。因此,當TaskScheduler.FromCurrentSynchronizationContext去調度任務延續,它有「更新TextBlockText」代碼運行在什麼可能是不同的線程,打破WPF的「必須在Dispatcher線程上執行」規則。

如果Dispatcher正在運行,則您建議的修復程序使用Dispatcher.BeginInvoke可能會解決您的直接問題。在發佈的測試代碼中,我沒有看到Dispatcher.RunDispacher.PushFrame的任何地方,所以我認爲這有效地將在Dispatcher上執行的任何操作變爲無操作(進入永不讀取的隊列)。當應用程序正常運行時,Visual Studio爲您自動生成的代碼在可執行文件入口點的末尾調用Application.Run,該代碼最終會爲您調用Dispatcher.Run,因此它可以開始處理消息,如「顯示主要窗口「等。

您可能會注意到,調用Dispatcher.Run後,它會阻止調用程序在您調用它的任何線程上,直到您告訴它關閉其他線程。告訴它關閉後,無法在該線程上啓動另一個Dispatcher ...因此,實質上,每個測試都需要啓動並減少自己的單獨線程(如果您想寫更多的測試,至少對我而言,這會讓人感到厭煩),或者您可能會受益從使用MSTest的花式[AssemblyInitialize]/[AssemblyCleanup]方法,所以你可以管理一個Dispatcher泵爲該項目中的所有測試(這是我們所做的)。

一旦你越過這些,你也可能會發現在測試中取得mwv.tb_test.Text也需要在Dispatcher線程上發生。

你還冒着競爭狀態,因爲事件引發由RaiseEvent可能(取決於你如何處理線程問題)終止前的TextChanged處理程序獲取測試有線了,這意味着ManualResetEvent有時可能甚至在所有其他事情之後永遠阻止

+0

「與處理所有這些問題的麻煩相比,我發現這些編碼測試的好處很小,特別是在遵循MVVM設計模式時。」 – Crucial

+0

在第一時間,我認爲測試一個「真正的」WPF窗口,而不是僅僅代碼的邏輯部分是個好主意。我將重新考慮結構+重構不好的部分。 我很感謝你的耐心回答我的「新手」,也許不是一個明確的問題。 – Crucial