測試回合
WCF 測試簡介
Dr. James McCaffrey

目錄
不論您是 Windows® Communication Foundation (WCF) 的新手,或者已有使用經驗,有些測試技巧和原則都可以讓您的 WCF 工作更加輕鬆。WCF 究竟是什麼?答案會因思考模式而異,但我傾向於將 WCF 服務想成是 Web 服務的主要延伸。WCF 服務和 Web 服務一樣,可以讓您使用以服務導向的架構來建立分散式系統。不過,WCF 服務可提供更大的彈性 (例如,可以選擇傳輸通訊協定) 和更多的功能 (例如,交易和安全性)。WCF 遠不只是 Web 服務的延伸,不過您若是 WCF 的新手,一開始以這種想法看待 WCF 服務也是很恰當的。
[圖 1] 顯示一個簡單但有代表性的 WCF 案例。Internet Explorer® 在此做為用戶端程式,存取的是接受使用者所輸入文字的 ASP.NET Web 應用程式,然後會計算其加密雜湊。ASP.NET Web 應用程式會在幕後呼叫 WCF 服務,以實際執行雜湊計算。在此特定案例中,WCF 服務是由 IIS 裝載,由 ASP.NET Web 應用程式使用,但是我接著會說明,除了 IIS 之外,WCF 服務可以利用許多方式裝載,而且幾乎可由任何類型的應用程式或其他服務使用。
圖 1 典型的 WCF 應用程式案例 (按一下影像以放大圖片)
最基本的 WCF 服務測試類型,會涉及驗證服務作業功能的正確性。其中的一種方法是,透過應用程式 UI 手動測試 WCF 服務。不過,雖然有必要手動測試,但是使用這種方法測試 WCF 服務的基本功能很花時間、很容易出錯、沒有效率,而且非常無聊。
較好的做法是撰寫測試自動化的程式,類似 [圖 2] 中所示。擷取畫面顯示的是我撰寫的主控台應用程式測試控管,這會直接將輸入文字送入後端 WCF 服務、從服務擷取回應訊息,然後決定測試案例通過或失敗的結果。[圖 3] 中的圖表是 [圖 1] 和 [圖 2] 之程式關係的簡化檢視和摘要。在大部分的情況下,WCF 服務都會從後端資料庫擷取資訊,或者從 Web 服務或 WCF 服務擷取資訊,不過 [圖 3] 中並未包含這些情況。
圖 2 測試 WCF 服務 (按一下影像以放大圖片)
圖 3 簡化的關係 (按一下影像以放大圖片)
接下來我要說明後端 WCF 服務,讓您能夠確實了解受測的是什麼、扼要地討論使用 WCF 服務的 ASP.NET Web 應用程式 (如 [圖 1] 所示),然後再詳細介紹測試控管。最後我將討論其他幾種 WCF 測試情況做為總結。
受測試的系統
受測試的系統包括一個後端 WCF 服務和使用該服務的 ASP.NET Web 應用程式。WCF 服務非常有彈性。建立 WCF 服務時,主要的設計決策之一,包括服務裝載機制的選擇。主要選項有四個:使用 IIS、使用 Windows® Service、自主裝載,以及使用 Windows Activation Service (WAS)。您應該已熟悉 IIS 和 Windows 的使用。自主裝載會涉及在 Microsoft® .NET Framework Managed 程式中 (例如主控台應用程式) 裝載 WCF。WAS 是 Windows Server® 2008 和 Windows Vista® 中提供的新處理序啟動機制。每一種 WCF 裝載選項都有其優缺點,這會因您的開發情況而異。針對本專欄中的範例 WCF 服務,我決定使用 IIS。這樣可充分發揮 IIS 的優勢,包括內建整合式管理和監控、處理序回收、閒置關機及訊息式啟動等功能。
由 IIS 裝載的 WCF 服務極為容易建立。首先我在 Windows Server 2003 上開啟 Visual Studio® 2008。請注意,如果您決定在執行 Windows Server 2008 或 Windows Vista 的機器上開發 WCF 服務,則在開發過程中,必須處理與系統增強式安全性的相關問題。不過,由於篇幅的限制,我無法討論這些問題。
接著,我從 Visual Studio 功能表選取 [檔案] | [新增] | [網站] 選項。然後我從 [新網站] 對話方塊中選擇 WCF Service 範本 (Visual Studio 2008 預設安裝的),並以 .NET Framework 3.5 為目標。我在 [位置] 欄位選取 HTTP,並指定 localhost/WCF/CryptoHashService。此方法會在我的開發用電腦上的 C:\Inetpub\wwwroot\WCF\CryptoHashService 目錄中,建立一個完整的 Web 應用程式和 IIS 虛擬目錄;我也可以選取電腦檔案系統上的另一個位置,並使用內建的 Visual Studio Web 程式開發伺服器。
我決定使用 C# 做為實作語言;不過,WCF 服務也可以使用 Visual Basic® .NET 實作。按下對話方塊上的 [確定] 按鈕後,Visual Studio 會建立一個可完全作用的 WCF 服務,其中會有名為 GetData 和 GetDataUsingDataContract 兩個範例作業。如果您檢視 [方案總管] 視窗,會看到 Visual Studio 產生了四個主要檔案:IService.cs、Service.cs、Service.svc 及 web.config。IService.cs 檔案會保存 WCF 作業的介面定義,而 Service.cs 檔案則會保存作業的實際實作。在此案例中,我決定將這兩個檔案分別重新命名為 ICryptoHashService.cs 和 CryptoHashService.cs。然後我將 ICryptoHashService.cs 載入 Visual Studio 程式碼編輯器中、刪除範例介面程式碼,並以下面這段程式碼取代:
[ServiceContract]
public interface ICryptoHashService
{
[OperationContract]
string GetCryptoHash(string s);
}
此處我只有一項作業,亦即 GetCryptoHash,不過也可以新增其他作業。請注意,[SeviceContract] 和 [OperationContract] 屬性將在幕後為我執行大部分的實際程式碼產生作業。接著,我編輯實作檔 CryptoHashService.cs,方法是新增對 System.Security.Cryptography 命名空間的 using 陳述式參考,並撰寫下列程式碼:
public class CryptoHashService : ICryptoHashService
{
public string GetCryptoHash(string s)
{
byte[] ba = Encoding.Unicode.GetBytes(s);
MD5CryptoServiceProvider sp = new MD5CryptoServiceProvider();
byte[] hash = sp.ComputeHash(ba);
string result = BitConverter.ToString(hash);
return result;
}
}
首先,我變更 CryptoHashService 類別和衍生它之來源類別的名稱,使名稱和我在介面定義中使用的名稱相符。在我的 GetCryptoHash 方法中,我直接使用 GetBytes 方法將輸入引數轉換為位元組陣列、具現化 MD5CryptoServiceProvider 類別的執行個體,再使用 ComputeHash 方法將位元組陣列轉換為 16 位元組的 MD5 加密雜湊。我使用靜態 BitConverter.ToString 方法,將產生的雜湊由位元組陣列轉換為易記的字串,然後傳回該字串。為了保持範例的簡潔,我省略了會在實際執行環境中使用的正常錯誤檢查,例如,檢查我的輸入引數、攔截例外狀況...等等。接下來,我編輯 Service.svc 檔案以反映從「Service」到「CryptoHashService」的命名變更。
<%@ ServiceHost Language="C#" Debug="true" Service="CryptoHashService"
CodeBehind= "~/App_Code/CryptoHashService.cs" %>
最後,我更新了 web.config 中的兩個名稱參考 (如下所示),便完成名稱編輯作業:
<system.serviceModel>
<services>
<service name="CryptoHashService" behaviorConfiguration="Servic Behavior">
<!-- Service Endpoints -->
<endpoint address=""binding="wsHttpBinding" contract="ICryptoHashService">
請注意 binding="wsHttpBinding" 項目。WCF 繫結是一個資訊集合,其中會指定 WCF 服務如何與用戶端通訊,包括服務使用的傳輸通訊協定、使用的文字編碼配置...等等。您可以使用內建繫結或建立自訂繫結。wsHttpBinding 是一個預先設定好的繫結,在您建立 IIS 裝載的 WCF 服務時會預設使用。Visual Studio 產生的 web.config 檔案的另一部分如下:
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange"/>
此項目會指示我的 WCF 服務公開自己的中繼資料,讓用戶端程式得以探查服務,以決定如何和服務互動。此時,我可以從主功能表選取 [建置] | [建置方案],成功地建置我的 WCF 服務。我也可以按 F5 快速鍵,同時建置我的 WCF 服務並取得建立用戶端程式的指示。因為我的服務是由 IIS 裝載,所以不必明確地啟動服務;只要 IIS 是在執行中,WCF 服務即可接受連入的 WCF 訊息。
接著我要介紹 [圖 1] 中所示之 ASP.NET Web 應用程式的建立。我啟動新的 Visual Studio 執行個體,並發出 [檔案] | [新增] | [網站] 命令。在 [新網站] 對話方塊上,我選取 [ASP.NET 網站] 範本,再於架構目標的下拉式控制項中,選取 .NET Framework 3.5。我使用 HTTP [位置],並選取 C# 語言。我在位置欄位中輸入 localhost/WCF/UtilitiesAndTools,隱含地為我的 Web 應用程式命名。我也可以指定開發用電腦上的檔案系統位置,並使用程式開發 Web 伺服器代替 IIS。接著,我新增小量的 UI 程式碼到我的 Web 應用程式。
[圖 4] 中的程式碼是基本 UI,沒有字型樣式和 <hr/> 標記等格式化詳細資料。本專欄所附的程式碼下載中,有完整的 UI 程式碼和本文中提出的所有程式碼。

圖 4 ASP.NET Web 應用程式 UI 程式碼
<body>
<form id="form1" runat="server">
<div>
<asp:Label runat="server" ID="Label"
Text="Demo Utilities Featuring WCF Services" />
<asp:Label runat="server" ID="Label2"
Text="Enter text here:" />
<asp:TextBox runat="server" ID="TextBox1"
Height="100px" Width="320px" />
<asp:Button runat="server" ID="Button1"
Text="Get MD5 Crypto-Hash" onclick="Button1_Click"
Width="150px" />
<asp:Button runat="server" ID="Button2"
Text="Get SHA1 Crypto-Hash"
Width="150px" />
<asp:Label runat="server" ID="Label3"
Text="Crypto-Hash of your text (computed by WCF Service) is:" />
<asp:TextBox runat="server" ID="TextBox2"
Width="320px" />
</div>
</form>
</body>
此時我的 ASP.NET Web 應用程式還不知道我的 WCF 服務,不過,WCF 和 ASP.NET 兩種技術的設計可以密切配合。我在 Web 應用程式專案的 [方案總管] 視窗中,用滑鼠右鍵按一下專案名稱,再從內容功能表中選取 [加入服務參考]。請注意,這是補充較舊的 [加入參考] (通常用於 DLL 程式庫和 .NET 命名空間) 和 [加入 Web 參考] (通常用於 ASP.NET Web 服務) 等選項的新選項。
我在出現的 [加入服務參考] 對話方塊中輸入 localhost/WCF/CryptoHashService/Service.svc,再按 [執行] 按鈕。然後 [加入服務參考] 工具會掃描指定的位置是否有可用的服務,並顯示找到的服務,在此例中是 CryptoHashService WCF 服務。在對話方塊的 [命名空間] 欄位中,我接受簡單的預設名稱 ServiceReference1 (雖然不具描述性),然後按一下 [確定] 按鈕。Visual Studio 會產生我的應用程式連接到 WCF 服務所需的一切 Proxy 程式碼。尢其是,我會取得名為 CryptoHashServiceClient 的類別 (就是在 WCF 服務名稱後面附加「Client」),這可以讓我和 CryptoHashService 服務通訊。
我在設計檢視中按兩下 Button1 控制項,指示 Visual Studio 註冊控制項的事件處理常式。然後我將此程式碼新增至 Button1_Click 方法:
try
{
string s = TextBox1.Text;
ServiceReference1.CryptoHashServiceClient c =
new ServiceReference1.CryptoHasahServiceClient();
string h = c.GetCryptoHash(s);
TextBox2.Text = h;
}
catch(Exception ex)
{
TextBox2.Text = ex.Message;
}
幾乎是太容易了。我擷取 TextBox1 中的文字、具現化自動產生之 CryptoHashServiceClient 類別的執行個體、呼叫物件的 GetCryptoHash 方法,然後在 TextBox2 中顯示產生的 MD5 (訊息摘要第 5 版) 加密雜湊。[圖 1] 中的 Web 應用程式使用者介面,顯示計算 SHA-1 (安全雜湊演算法第 1 版) 加密雜湊的按鈕控制項,但是我並未實作該功能以模擬正在開發的系統。建置好 Web 應用程式後,使用者可以啟動 Internet Explorer 並巡覽至應用程式、輸入一些文字,然後取得由後端 WCF 服務計算的 MD5 加密雜湊文字,如 [圖 1] 所示。
測試控管
現在來看看 [圖 2] 所示的簡單測試控管的程式碼。測試控管程式本質上只是一個 WCF 用戶端,此用戶端會將輸入訊息傳送至受測試的 WCF 服務,並確認傳回的訊息正確。首先我啟動新的 Visual Studio 執行個體,再建立名為 TestHarness 的 C# 主控台應用程式。在 [圖 2] 所示的範例中,請注意我將控管的位置設定為 C:\Inetpub\wwwroot 的子目錄,這只是為了讓控管程式碼靠近受測試的系統。不過,我也可以將控管放在任何位置,包括在另一部測試主機電腦上。[圖 5] 說明測試控管的整體結構。

圖 5 測試控管結構
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace TestHarness
{
class Program
{
static void Main(string[] args)
{
try
{
// display startup messages
// set up test case data collection, testCases
// set up WCF binding object, wsb
// set up WCF address object, epa
CryptoHashServiceClient c =
new CryptoHashServiceClient(wsb, epa);
foreach (TestCaseData tcd in testCases)
{
// echo case ID, input, expected values
// call GetCryptoHash method, fetch return
// compare actual return with expected
// print pass/fail result
}
Console.WriteLine("\nDone");
}
catch (Exception ex)
{
Console.WriteLine("Fatal error: " + ex.Message);
}
}
}
class TestCaseData
{
public readonly string caseID;
public readonly string input;
public readonly string expected;
public TestCaseData(string caseID, string input, string expected)
{
this.caseID = caseID; this.input = input; this.expected =
expected;
}
}
}
我先新增一個指向 System.ServiceModel 命名空間的 using 陳述式,來開始撰寫測試控管的程式碼。此命名空間會保存核心的 WCF 服務功能。接下來,我開始設定我的測試案例資料。在 [圖 5] 中,您可以看到我建立了一個簡單的 TestCaseData 類別,以保存測試案例 ID、輸入,以及需要的值。在實際執行情況中,您也可以新增其他欄位,包括 WCF 服務繫結資訊,我很快就會加以解說。接著我建立一個泛型 List 物件,並在其中填入 TestCaseData 物件,以直接將測試案例資料內嵌到我的控管:
List<TestCaseData> testCases = new List<TestCaseData>();
testCases.Add(new TestCaseData("001", "Hello world",
"E6-76-0D-55-5C-32-F6-6F-5E-15-93-31-DB-20-FD-8E"));
testCases.Add(new TestCaseData("002", "Goodbye world",
"1A-05-3C-C0-4A-18-13-06-0E-AC-EA-BA-46-EC-CF-B1"));
testCases.Add(new TestCaseData("003", "",
"D4-1D-8C-D9-8F-00-B2-04-E9-80-09-98-EC-F8-42-7E"));
我也可以在外部存放區設定測試案例資料,例如 XML 檔案或 SQL 資料表。我在此處使用三個測試案例,不過在實際運用中,可能會有幾千個。如何建立可徹底操作受測試系統的測試案例,以及如何判斷應有的結果,這是軟體測試最困難的部分。
雖然您可以從頭開始撰寫 WCF 用戶端程式碼,不過使用 Visual Studio 2008 所附的 svcutil.exe 命令列工具來產生 Proxy 程式碼和 WCF 設定檔,會容易許多。在此案例中,我啟動 Visual Studio 命令殼層 (它知道 svcutil.exe 的位置),然後輸入命令:
> svcutil.exe http://localhost/WCF/CryptoHashService/Service.svc
Svcutil.exe 會使用 WCF 服務的位置執行,而且不需任何其他引數,即可從服務讀取 WCF 中繼資料,並為我產生兩個檔案。第一個檔案是 CryptoHashService.cs (目標 WCF 服務的名稱後面附加 .cs 副檔名),此檔案會保存 C# Proxy 程式碼,可以讓我向 WCF 服務傳送和接收訊息。第二個檔案是 output.config,其中會保存目標 WCF 服務所使用的 WCF 繫結資訊 (傳輸通訊協定、逾時設定、文字編碼...等等)。產生這兩個檔案後,我用滑鼠右鍵按一下測試控管專案,再將 CryptoHashService.cs 檔案新增至我的專案。我也可以選擇複製程式碼,再直接貼到我的測試控管中。
有兩種方式可以在 output.config 檔案中使用繫結資訊。一種是將檔案重新命名為 app.config,再將檔案新增至您的用戶端專案。第二種方法是檢查 output.config 檔案中的資料,再撰寫程式碼,以程式設計的方式將 output.config 中所示的值指派到繫結物件。撰寫正常的 WCF 用戶端程式時,app.config 方法通常會比以程式設計的方式更好。由於 app.config 檔案是普通文字,而且是由用戶端程式在執行階段讀取,因此,若相關 WCF 服務的繫結資訊有所變更,只需變更您的 app.config 檔案即可變更用戶端的對應繫結資訊,不必重新編譯用戶端。不過,在 WCF 測試控管用戶端的特殊情況下,有時候以程式設定的方式來指定繫結資訊會比較好。以程式設計的方式可讓您輕鬆將繫結資訊當成測試案例輸入的一部分傳入。此處我選擇使用以程式設計的方式。我具現化一個 WSHttpBinding 物件:
WSHttpBinding wsb = new WSHttpBinding();
接下來我就可以指派值給繫結物件的 Name 屬性和各種時間屬性:
wsb.Name = "WSHttpBinding_ICryptoHashService";
wsb.CloseTimeout = TimeSpan.Parse("00:01:00");
wsb.OpenTimeout = TimeSpan.Parse("00:01:00");
wsb.ReceiveTimeout = TimeSpan.Parse("00:10:00");
wsb.SendTimeout = TimeSpan.Parse("00:01:00");
我目視檢查 svcutil.exe 工具所產生的 output.config 檔案,以查看繫結項目的屬性,來確定這些值。在此案例中,我使用的是在建立 WCF 服務時,Visual Studio 所產生的所有預設值。其餘的繫結屬性也同樣會有指定值:
wsb.BypassProxyOnLocal = false;
wsb.TransactionFlow = false;
wsb.HostNameComparisonMode =
System.ServiceModel.HostNameComparisonMode.StrongWildcard;
wsb.MaxBufferPoolSize = 524288;
wsb.MaxReceivedMessageSize = 65536;
wsb.MessageEncoding =
System.ServiceModel.WSMessageEncoding.Text;
wsb.TextEncoding = System.Text.Encoding.UTF8;
wsb.UseDefaultWebProxy = true;
wsb.AllowCookies = false;
接下來我就可以設定 Proxy 物件:
string uri =
"http://vte014.vte.local/WCF/CryptoHashService/Service.svc";
EndpointAddress epa = new EndpointAddress(uri);
CryptoHashServiceClient c =
new CryptoHashServiceClient(wsb, epa);
CryptoHashServiceClient 類別是在我使用 svcutil.exe 建立的自動產生 CryptoHashService.cs 檔案中定義。該類別建構函式會接受繫結物件 (我剛剛以程式設計的方式設定) 以及指向 WCF 服務的 EndPointAddress 物件。我的用戶端物件具現化之後,就可以逐一執行每一個測試案例,並操作受測試的 WCF 服務。
在 [圖 6] 中,我只是針對每個測試案例傳送通過/失敗訊息至主控台。然而,在實際執行環境中,您應該會執行更多工作,包括:追蹤通過的案例總數和失敗的案例總數,以及如果有一個或多個測試案例失敗,則以程式設計的方式傳送電子郵件訊息,以及將結果儲存到外部資料存放區...等等。如果您使用的是 Team Foundation Server,就可以根據我在 2008 Launch 裡的<測試回合:使用 Team System 自訂測試自動化>(請參閱 msdn.microsoft.com/magazine/cc164248) 中所提供的技巧,來管理此一測試控管。

圖 6 顯示每個測試案例的狀態
foreach (TestCaseData tcd in testCases)
{
Console.WriteLine("Case ID = " + tcd.caseID);
Console.WriteLine("Input = " + tcd.input);
Console.WriteLine("Expected = " + tcd.expected);
string actual = c.GetCryptoHash(tcd.input);
Console.WriteLine("Actual = " + actual);
if (actual == tcd.expected)
Console.WriteLine("* Pass *");
else
Console.WriteLine("** FAIL **");
}
其他考量
我在本月介紹的技巧,是開始使用基本 WCF 服務測試的穩固基礎。不過,未來的專欄中還會介紹到 WCF 測試的其他面向。這些多元化的測試主題,將展現 WCF 的極佳彈性。例如,WCF 可以讓系統在傳輸層級以及在較低層級使用安全性 (例如,使用 HTTPS)。雖然 WCF 服務可以使用 HTTP,不過 WCF 也可以讓系統使用其他許多機制 (包括 TCP 和具名管道) 進行通訊。如同我前面提過的,WCF 服務可以使用 IIS 裝載,但是也可以使用其他方式裝載,包括透過 Windows 服務和自主裝載 Managed 應用程式。WCF 服務可支援多個端點,且每個端點都可以有不同的位址、繫結及合約。WCF 可以支援要求-回覆式的訊息和雙工式的訊息。這一切 WCF 案例和其他許多案例,對於如何徹底測試都個別會有需要注意的事項。
本專欄中介紹的基本 WCF 功能測試案例,只是 WCF 徹底測試的一小部分。由於我的 Dummy WCF 加密雜湊服務很簡單,因此整個邏輯都包含在單一 GetCryptoHash 方法中。在實際的情況中,您可能會有封裝商務邏輯的程式碼,以及封裝服務功能的另一段程式碼。此方法可以讓您分別測試商務邏輯和服務,以簡化您的測試工作。
使用 Visual Studio Team System 建立 WCF 服務時,若使用以測試導向的開發方式,就可以利用內建的單元測試支援。您也可以使用 Visual Studio 2008 所附的 WcfTestClient.exe 測試用戶端公用程式,來執行 WCF 服務的手動測試,以輔助我在本專欄中介紹的自動化測試 (請參閱我的 MSDN
® Magazine 同事 Juval Lowy 所撰寫的專欄
msdn.microsoft.com/magazine/cc163289)。除了純綷的功能測試以外,您還可以使用 Visual Studio 中的整合式負載測試工具,來執行負載測試。