Workflow Foundation 4 第五課 - 測試 WF 流程

原文出處: http://michaelchpeng.spaces.live.com/blog/cns!37633C3B61B57B57!1168.entry


到目前為止,我們的流程還是停留在說哈囉的層次,但是這麼一個簡單的流程,我們可以看到很多問題。首先,它顯示的訊息是寫死的,所以這支流程並沒有什麼重複應用的價值。我們除了以它為基礎建立很多個實例,然後在主控台中不斷的重複同樣的訊息之外,沒有別的事可以做。比較如下的三個流程:

流程一:在主控台顯示「Hello Workflow Foundation 4」

流程二:提供要顯示的訊息,然後在主控台中顯示這個訊息

流程三:提供要顯示的訊息主體,然後組合出最終應該顯示的訊息,交由呼叫者在期望的使用者介面上顯示

那一個流程在感覺上比較像回事?比較有重複應用的機會?其實上面這一段是多說的,我相信對這個主題或這篇文章有興趣的人,都已經具備這樣的概念。而我在這裡要做的,也是至少要將這個例子發展到第三個流程的層次,才比較有 WF 的基本水準。

第二個我們可以看到的問題是這個流程唯一的測試方法就是透過人工去進行,按下 Ctrl+F5/F5,執行應用程式,看看畫面上輸出的結果是否如同預期。就我們的應用來講,如果能夠將作業邏輯和使用者操作介面兩個部份隔離開來,就有可能針對作業邏輯的部份去做自動化的測試,而使用者操作介面則留待人工測試 (操作介面的部份不是不可能自動化測試啦,只是相對門檻會高些,也要配合適當的工具)。同樣的,如果以前面三個流程來比較,顯然第三個流程是很容易安排自動化測試的。

我個人篤信測試驅動開發 (Test Driven Development) 的精神,在需求確立的時候同時依據需求建立測試案例,接下來所有的開發都以能否通過測試案例來決定成功與否或正確與否,是確保開發結果吻合需求的一個良好做法。當需求變更時,測試案例就隨立變動,並且驗證新的開發結果是否吻合新的需求。因此在這一回的課程中,我們要開始運用測試驅動開發的方法,先建立測試案例,再做開發的工作。後面的課程都會遵循這個方法,先出現需求變動,然後調整測試案例,再修改應用程式。當我們不斷確保我們的應用很容易套用單元測試的動作時,就會發現我們的開發結果不經意的都遵循了某些 refactor 的原則,讓程式保持在可維護的狀態。這個做法不能保證我們一直產出高品質、高水準的設計,但可以確保我們的產出一直是在一個可掌握的狀態。

現在我把之前的流程三做一些小小的修正,重新定義我們的需求:

作業流程:接收呼叫者提供的名稱,然後產生一個招呼該對象的回應字串,供叫用者顯示到使用者介面上

依據這個需求,我們的第一步就是建立一個能測試這項需求的專案。首先我們在 HelloWF4 解決方案中新增一個測試專案 HelloWF4.Tests (我這裡用 Visual Basic 的範本),這個測試專案的測試案例會調用 HelloWF4 中的流程,所以我們要加入對這個專案的參照 (透過 Project 頁籤)。此外,我們會使用到 WF 的功能進行測試,所以也加入對 System.Activities 命名空間的參照 (透過 .NET 頁籤)。

接著我們將預設產生的單元測試檔 UnitTest.vb (或 cs) 改成 SayHelloFixture.vb,如果出現是否更換類別名稱的提供,回答是。這個單元測試檔中會涵蓋所有和 SayHello.xaml 流程有關的測試方法。然後我們在檔案的開頭加上以下的引用敘述:

Imports System.Activities

接著我們修改原本的 TestMethod1,改成如下的程式碼:

如果你在輸入 New SayHello() 時沒有辦法透過 IntelliSense 取得,代表 HelloWF4 尚未建置,無法產生類別資訊。所以你可以按下 Ctrl+Shift+B 來建置解決方案,並透過 IntelliSense 取得類。而當你輸入到 .UserName 時 (留意一下指定參數的括號是大括號 {}),同樣沒有 IntelliSense,因為我們還沒有定義這個流程的引數。特會我們會修改流程的設計,加入 UserName 的定義。對 WF 4 而言,輸入的參數會是以一個 Dictionary 物件來傳遞,鍵的名稱就是參數名稱,儲存的內容就是參數值。至於傳回的結果,會透過 WorkflowInvoker 的 Invoke 方法傳回一個支援 IDictionary 介面的物件,可以透過傳回參數的名稱做為索引鍵取得對應值。我們會定義的傳回參數名稱是 Greeting。如果你熟悉 WF 3,就會發現這裡傳入參數和取得傳回值的方法都簡單很多。

如果我們現在進行建置,自然會得到錯誤的訊息:

現在我們回過頭,開始修正流程的設計,加入參數的使用。開啟 SayHello.xaml,之前你可能沒留意到,在設計器的下方有一個工具列,上面有三個按鈕,分別是 Variables、Arguments和Imports。顧名思義,Variables 是用來建立流程中使用的變數,Arguments 則是建立傳入或傳出流程的引數,Imports 則是決定流程中要引用的命名空間。


建立變數、參數的新按鈕

在這個流程中,我們分別要建立 UserName 和 Greeting 兩個引數,一個是接收外來的使用者名稱,一個則是傳出組合的問候訊息。當我們按下 Arguments 按鈕,會出現一個定義引數的資料方格。我們可以分別指定引數的名稱和方向,如下:

如果你和 WF 3 比較,會發現引數的定義也簡單很多,而且不用在 field 和 property 之間選擇。既然定義了流程的引數,接著我們就重新建置解決方案。這回建置的動作應該是成功的,因為已經定義了引數了。既然建置成功,我們就來執行一下測試。選擇 Test 選單的 Run─Tests in Current Context 來執行所有的測試 (或是按下 Ctrl+R, T 的按鍵序列),結果會發現 IsReturnGreetingWithName 單元測試是失敗的:

當然是失敗的,我們的流程還是和主控台介面綁在一起的,正確的做法應該是在流程中組合出我們的問候訊息。這件事可以透過一個 Assign 活動控制項 (同樣位在 Primitives 類別中) 來完成,它可以協助我們指定運算式到變數或引數中。所以我們先刪除原本的 WriteLine 活動控制項,再拖放一個 Assign 活動控制項,控制項的左手邊可以填入變數或引數的名稱,右手邊則是要指定的運算式。同樣的,在你指定變數或引數名稱時,一樣有 IntelliSense 協助你找到正確的對象。


Assign 活動控制項


IntelliSense 輔助

至於右手邊的運算式則如下:

"Hello " & UserName & " From Workflow Foundation 4"

你可以直接在活動控制項的文字方塊中輸入,也可以透過屬性視窗中的 Value 屬性輸入。後者會有獨立的文字編輯器,對於長運算式而言是較合適的。

好,我們再測試一下,還是一樣要先建置 (是的,執行測試案例不會自動先行建置)。然後再執行測試,這回單元測試就順利通過了。

當我們的測試方法陸續增加時,你可能就不想一次跑所有的測試方法了。要執行特定的測試方法,一個簡單的方式是透過 Test View 視窗。你可以選擇 Test 選單,從 Windows 子選單中選擇 Test View。這裡會列出所有的測試方法,選定要執行的方法 (可以多重選取),再按下上方工具列的執行按鈕(),就能執行指定的測試方法。


TestView 視窗

好啦,測試通過。我們現在可以回頭來修改我們的主程式了。修正過的主程式如下:

這樣一來,我們就完成了一個可以接收傳送引數,重複使用的 WF 流程。而有了單元測試的協助,讓我們可以更輕鬆的維護作業邏輯,確保程式的品質 (這麼簡單的例子這樣講會不會誇張了些)?到這裡,我想做一個簡單的結論:

一、養成先有測試案例,再做開發動作 (或是同時進行),不一定會花更多的時間,很多時候反而讓自己後面省下很多的時間。

二、利用 WF 建立作業流程時,原則上避免在流程中直接牽涉到使用者操作介面的處理 (可以控制使用者介面的進行流程,但不是去處理畫面上輸出的內容)。WriteLine 這樣的活動控制項儘量用在除錯之類的動作上 (除非你要開發的是純粹主控台文字模式的程式),不要用在實際的應用中。這樣才能提高作業邏輯重複使用的能力,也可以讓程式的效能更佳 (避開 WF 流程執行緒要綁在使用者介面執行緒上彼此交纏)。