程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 測試運行 - 使用套接字進行WCF服務測試

測試運行 - 使用套接字進行WCF服務測試

編輯:關於.NET

在本月的專欄中我有一位合作者 Carlos Figueira,他是 Windows Communication Foundation (WCF) 測試團隊的一位高級軟件開發工程師。在他的幫助下,我將向您介紹如何使用基於網絡套接字的方法測試 WCF 服務。

要想了解本文的主題內容,一個好的方法是看一下圖 1、2 和 3 中的屏幕快照。圖 1 是一個 WinForm 應用程序,其中承載一個簡單但頗具代表性的 WCF 服務,該服務名為 MathService。在後台, MathService 包含一個名為 Sum 的運算,該運算接受兩個雙精度類型的值,然後計算並返回這兩個值之 和。

圖 1 待測試的 WCF MathService 服務

圖 2 是一個典型的 WCF ASP.NET Web 應用程序客戶端,它接受用戶輸入的兩個值,將這兩個值發送 給 MathService 服務,提取來自該服務的響應,然後將結果顯示在 ListBox 控件中。

圖 2 典型 WCF ASP.NET 客戶端

圖 3 是一個對 MathService 服務執行功能驗證的控制台應用程序測試工具。該測試工具使用網絡套 接字直接向 MathService 發送 SOAP 消息,接受來自該服務的響應,然後對預期結果和實際結果進行比 較,從而確定測試是通過還是失敗。在測試用例 #001 中,該測試工具向待測試的 WCF 服務發送 3.4 和 5.7 並收到預期值 9.1。測試用例 #002 是一個故意偽造的失敗測試,僅供演示使用。

在接下來的幾節中,我首先簡要介紹圖 1 中所示的待測試 WCF 服務,從而方便您了解構建基於 WCF 套接字的測試自動化時哪些因素是比較重要的。下面,我將簡要說明一下演示 Web 客戶端,以便您更深 入地了解基於套接字的測試在哪些情況下比其他測試方法更為合適。然後,我將詳細解釋創建測試工具的 代碼,這樣您將能夠根據自身需要改編我在這裡提供的編碼方法。本文假定您具備中級 C# 編碼技能。

圖 3 WCF 測試工具運行

待測試的 WCF 服務

我使用 Visual Studio 2008 創建的待測試 WCF 服務。WCF 服務的便利功能之一就是它們可承載於許 多不同類型的應用程序之中。我決定在 WinForm 應用程序中承載 WCF MathService 服務,不過我在本文 中提供的方法適用於任何類型的 WCF 主機。

創建一個空的 WinForm 之後,添加兩個 Button 控件和一個 ListBox 控件。然後,就在 Form 定義 的下方,添加聲明 WCF 服務所需的簡單代碼:

[ServiceContract]
   public interface IMathService {
   [OperationContract]
    double Sum(double x, double y);
   }

應用於接口的 ServiceContract 特性會生成 WCF 接口所需的所有代碼。如果要從 Web 服務移至 WCF ,可以將 OperationContract 特性看成等同於 WebMethod 特性。實現 WCF 服務十分簡單:

public class MathService : IMathService {
  public double Sum(double x, double y) {
   double answer = x + y;
   return answer;
  }
}

完成 WCF 基礎搭建後,在項目中添加對 System.ServiceModel.dll 的引用以及存放 WCF 功能的 .NET 程序集。然後,向包含在該程序集中的兩個主要 .NET 命名空間添加 WCF 服務所需的 using 語句 :

using System.ServiceModel;
using System.ServiceModel.Description;

ServiceModel 命令空間用於保存 ServiceHost 類及定義 WCF 綁定的幾個類。Description 命令空間 用於保存 ServiceMetadataBehavior 類,該類用於發布 WCF 服務的相關信息。接下來,向 Button1 控 件事件處理程序添加服務實例化邏輯:

try {
  string address =
   "http://localhost:8000/MyWCFMathService/Service";
  Uri baseAddress = new Uri(address);
  serviceHost =
   new ServiceHost(typeof(MathService), baseAddress);
  serviceHost.AddServiceEndpoint(typeof(IMathService),
   new WSHttpBinding(SecurityMode.None), "MathService");
  . . .

在此您應注意一個重要因素,就是我使用 WSHttpBinding 創建了一個 WCF 終結點。WCF 綁定是一個 集合,其中包含有關安全性和可靠性設置、傳輸協議及編碼類型的信息。WSHttpBinding 是一個極好的常 規用途綁定,適用於非雙工通信。默認情況下 WSHttpBinding 使用加密傳輸,但我在此指定了 SecurityMode.None,以便您可以更容易地查看請求/響應數據。

接下來,通過“添加服務引用”機制添加代碼,使服務對客戶端可見:

ServiceMetadataBehavior smb =
  new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
serviceHost.Description.Behaviors.Add(smb);
. . .

現在准備添加啟動 WCF 服務的代碼:

serviceHost.Open();
int count = 0;
while (serviceHost.State !=
     CommunicationState.Opened &&
     count < 50) {
  System.Threading.Thread.Sleep(100);
  ++count;
}
. . .

我使用一種簡單但十分有效的方法來檢測啟動時是否發生服務掛起,這種方法就是執行一個延遲循環 ,每個延遲的時間為 0.1 秒,最多可包含 50 個延遲。在延遲循環終止後,要麼 WCF 服務已經啟動,要 麼就是已達到最大延遲數:

if (serviceHost.State == CommunicationState.Opened)
  listBox1.Items.Add("WCF MathService is started");
else
  throw new Exception(
   "Unable to start WCF MathService in a timely manner");

我采用了許多不適合在編寫成品代碼時采用的捷徑,比如在嘗試打開 serviceHost 對象之前不檢查該 對象是否已打開。Button2 控件事件處理程序中的邏輯可關閉 WCF MathService,因此我將使用這一相同 模式來啟動該服務:

int count = 0;
serviceHost.Close();
while (serviceHost.State !=
     CommunicationState.Closed &&
     count < 50) {
  System.Threading.Thread.Sleep(100);
  ++count;
}

典型 Web 應用程序客戶端

我使用 Visual Studio 2008 創建圖 2 中所示的 WCF Web 應用程序客戶端。首先單擊“文件”|“新 建”|“網站”。在“新建網站”對話框中,設置以 Microsoft .NET Framework 3.5 為目標,選擇“空 網站”模板,選擇使用 C# 語言的文件系統位置,然後將項目命名為 WCFMathServiceWebClient。

然後,在解決方案資源管理器中,右擊項目,從上下文菜單中選擇“添加新項”選項。在出現的對話 框中,選擇“Web 窗體”項。我取消選中了“將代碼放在單獨的文件中”選項,這樣可以將所有應用程序 代碼放入同一個文件中。

添加 Web 窗體後,我又添加了服務器端控制標記,以便創建十分簡單的 Web 應用程序 UI:

<asp:Label ID="Label1" runat="server"
  Text="Enter first number: "/>
<asp:TextBox ID="TextBox1" runat="server" /><p />
<asp:Label ID="Label2" runat="server"
  Text="Enter second number: "/>
<asp:TextBox ID="TextBox2" runat="server" /><p />
<asp:Button ID="Button1" runat="server" Text="Compute Sum"
  onclick="Button1_Click" /><p />
<asp:ListBox ID="ListBox1" runat="server" />

隨後,我按下了 F5 鍵,指示 Visual Studio 生成該應用程序,此時系統會提示我是否允許為項目自 動生成 Web.config 文件。在確認創建 Web.config 文件後,我啟動 WCF MathService 服務,以便客戶 端項目能夠看到該服務。

在解決方案資源管理器中右擊客戶端項目,然後從上下文菜單中選中“添加服務引用”選項。在“添 加服務引用”對話框中,輸入 WCF 服務的位置 (http://localhost:8000/MyWCFMathService/Service), 然後單擊“執行”按鈕。由於 WCF 服務正在運行,並且該服務會發布自身的相關元數據,因此 Visual Studio 工具會找到該服務。我將該服務命名空間從默認名稱 ServiceReference1 重命名為 WCFMathServiceReference。

在後台,Visual Studio 會通過創建一個 <system.serviceModel> 條目,向 Web.config 文件 中添加有關 WCF 服務的信息。該條目包含帶有特性 binding=”wsHttpBinding” 的 <endpoint> 元素,這樣 Web 應用程序客戶端便可知道如何與 WCF 服務通信。在設計視圖中,雙擊 Button1 控件以 注冊其事件處理程序,然後添加用於訪問該服務的邏輯:

try {
  WCFMathServiceReference.MathServiceClient sc =
   new WCFMathServiceReference.MathServiceClient();
  double x = double.Parse(TextBox1.Text);
  double y = double.Parse(TextBox2.Text);
  double sum = sc.Sum(x, y);
  . . .
  ListBox1.Items.Add(
   "The response from the WCF service is " + sum);

保存 Sum 操作的類的名稱是 MathServiceClient,即在派生自 WCF 約定接口 (IMathService) 的類 (MathService) 的名稱後追加“Client”一詞。

測試工具

我還使用 Visual Studio 2008 創建了一個名為 WCFTestHarness 的 C# 控制台應用程序項目。在 Visual Studio 生成的 Program.cs 文件的最頂部,添加如下 using 語句:

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

由於 TCP 在字節級別工作,因此我需要使用 System.Text 命名空間中的類將文本轉換為字節數組。 需要使用 System.Net 命令空間中的類創建作為 IP 地址抽象的對象。還需要使用 System.Net.Sockets 命令空間創建一個執行實際發送和接收操作的套接字對象。我向 Main 方法中添加了一個簡要的記錄消息 ,然後將測試用例數據設置為字符串數組:

namespace WCFTestHarness {
  class Program {
   static void Main(string[] args) {
    try {
     Console.WriteLine(
     "\nBegin WCF MathService testing via sockets run");

     string[] testCases = new string[] {
      "001,3.4,5.7,9.1",
      "002,0.0,0.1,1.0",
      "003,6.7,6.7,13.4"
     };
     . . .

每個測試用例數據字符串都包含四個逗號分隔的字段:一個測試用例 ID、Sum 運算的兩個輸入以及一 個預期響應值。我使用測試用例數據來創建測試工具主處理循環:

foreach (string testCase in testCases) {
  Console.WriteLine("\n============\n");
  string[] tokens = testCase.Split(',');
  string caseID = tokens[0];
  string input1 = tokens[1];
  string input2 = tokens[2];
  string expected = tokens[3];
  . . .

我使用 Split 方法將每個字符串拆分為四個字段。

下面是使用套接字將輸入發送到 WCF 服務的關鍵部分之一。我創建了一條 SOAP 消息,如圖 4 中所 示。

圖 4 用於測試 WCF 服務的 SOAP 消息

string soapMessage = "<s:Envelope xmlns:s='http://www.w3.org/2003/05/soap- envelope'";
soapMessage += "  xmlns:a='http://www.w3.org/2005/08/addressing'><s:Header>";
soapMessage += "<a:Action  s:mustUnderstand='1'>http://tempuri.org/IMathService/Sum</a:Action>";
soapMessage += "<a:MessageID>urn:uuid:510b1790-0b89-4c85-8015- d1043ffeea14</a:MessageID>";
soapMessage +=  "<a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Ad dress>";
soapMessage += "</a:ReplyTo><a:To s:mustUnderstand='1'>";
soapMessage +=  "http://localhost:8000/MyWCFMathService/Service/MathService</a:To></s:Header>" ;
soapMessage += "<s:Body><Sum xmlns='http://tempuri.org/'>";

soapMessage += "<x>" + input1 + "</x>" + "<y>" + input2 +  "</y>";

soapMessage += "</Sum></s:Body></s:Envelope>";

您會看到,測試用例輸入值 input1 和 input2 嵌入到 SOAP 消息中接近消息末尾的 <Sum> 元 素中。但我如何確定這一不太簡單的消息結構呢?

有多種方法可確定所需的 SOAP 消息結構以及 WCF 服務的數據。最簡單的方法是在演練客戶端應用程 序期間使用 netmon 或 Fiddler 之類的網絡通信檢查工具捕獲數據,這也正是我所用的方法。換言之, 在運行 WCF 服務時,通信量捕獲工具也會隨之運行(我使用的是 Fiddler)。我啟動了圖 2 中所示的 Web 應用程序客戶端程序,並使用該客戶端向 WCF 服務發送請求。該網絡通信捕獲工具向我顯示從客戶 端發送到 WCF 服務的那條 SOAP 消息。我利用該信息在測試工具中創建 SOAP 消息。

構建 SOAP 消息後,隨後我會按預期將測試用例輸入數據顯示到 shell:

Console.WriteLine(
  "Test Case  : " + caseID);
Console.WriteLine(
  "Input    : <s:Envelope..." + "<x>" +
  input1 + "</x>" + "<y>" + input2 +
  "</y>...</s:Envelope>");
Console.WriteLine("Expected  : " + expected);

然後,我將設置待測試的目標 WCF MathService 服務的 IP 地址:

string host = "localhost";
   IPHostEntry iphe = Dns.Resolve(host);
   IPAddress[] addList = iphe.AddressList;
   EndPoint ep = new IPEndPoint(addList[0], 8000);

Dns.Resolve 方法將與特定主機名相關聯的 IP 地址(可能有多個)列表作為一個 IPHostEntry 對象 返回。IPHostEntry 對象有一個 AddressList 屬性,該屬性是 IPAddress 對象的數組。EndPoint 對象 由 IPAddress 對象和端口號組成。現在,即可創建套件字:

Socket socket = new Socket(AddressFamily.InterNetwork,
  SocketType.Stream, ProtocolType.Tcp);
socket.Connect(ep);
if (socket.Connected)
  Console.WriteLine(
   "Connected to socket at " + ep.ToString());
else
  Console.WriteLine(
   "Error: Unable to connect to " + ep.ToString());

套接字對象實現 Berkeley 套接字接口,該接口是用於發送和接收網絡通信的抽象機制。Socket 構造 函數的第一個參數指定尋址方案。在此我使用 InterNetwork,即常用的 IPv4。

Socket 構造函數的第二個參數指定在六種可能的套接字類型中要創建哪一種。Stream 表示 TCP 套接 字。Dgram 是另一種常見類型,它用於 UDP(用戶數據報協議)套接字。

Socket 構造函數的第三個參數指定套接字支持哪些協議。Socket.Connect 方法接受 EndPoint 對象 。

下面構建標頭信息:

string header =
  "POST /MyWCFMathService/Service/MathService HTTP/1.1\r\n";
header += "Content-Type: application/soap+xml; charset=utf-8\r\n";
header += "SOAPAction: 'http://tempuri.org/IMathService/Sum'\r\n";
header += "Host: localhost:8000\r\n";
header += "Content-Length: " + soapMessage.Length + "\r\n";
header += "Expect: 100-continue\r\n";
//header += "Connection: Keep-Alive\r\n\r\n";
header += "Connection: Close\r\n\r\n";

就像對待 SOAP 消息一樣,我將通過使用網絡通信監視工具確定標頭信息。您會看到,每行均以一個 \r\n(回車換行)終結符而不是單個 \n(換行)標記結尾,並且最後一行以兩個 \r\n 結尾。正如上述 帶注釋的代碼行所示,根據一些因素的不同,您可能需要將 Keep-Alive 或 Close 參數發送給 Connection 標頭條目。同樣,SOAPAction 標頭也不是 WSHttpBinding 所必需的。

現在,可將標頭與 SOAP 消息合並,將整個消息轉換為字節,然後發送請求:

string sendAsString = header + soapMessage;
byte[] sendAsBytes = Encoding.UTF8.GetBytes(sendAsString);
Console.WriteLine("Sending input to WCF service");
int numBytesSent = socket.Send(sendAsBytes, sendAsBytes.Length,
  SocketFlags.None);

Socket.Send 方法具有多個重載。在此我將發送整個請求,不帶任何特殊的發送或接收選項。我沒有 使用返回值,但使用該值可以檢查是否已發送完整消息。現在,可以提取來自 WCF 服務的響應:

byte[] receivedBufferAsBytes = new byte[512];
string receiveAsString = "";
string entireReceive = "";
int numBytesReceived = 0;

while ((numBytesReceived =
  socket.Receive(receivedBufferAsBytes, 512,
  SocketFlags.None)) > 0) {
  receiveAsString =
   Encoding.UTF8.GetString(receivedBufferAsBytes, 0,
   numBytesReceived);
  entireReceive += receiveAsString;
}

由於多數情況下您無法預知響應中的字節數,因此最好創建一個緩沖區,並讀取響應塊,直到響應全 部處理完。在此我使用一個 512 字節的緩沖區。每當收到一組 512 字節時,就會將其轉換為文本並追加 到聚合結果字符串中。

收到響應後,我會檢查響應是否包含當前測試用例預期值:

Console.WriteLine("Response received");
if (entireReceive.IndexOf(expected) >= 0)
  Console.WriteLine("Test result : Pass");
else
  Console.WriteLine("Test result : **FAIL**");

 

我此處使用的方法適合十分簡單的測試方案,但如果您的測試方案比較復雜,可能有必要另外添加一 些邏輯。最後我將編寫收尾部分,從而完成測試工具的創建:

. . .
     } // main loop
     Console.WriteLine(
      "\n=======================================");
     Console.WriteLine("\nEnd test run");
    } // try 
    catch (Exception ex)
    {
     Console.WriteLine("Fatal: " + ex.Message);
    }
   } // Main()
  } // Program 
} // ns

總結

WCF 測試有很多備選方法,但 WCF 服務測試的基於套接字的方法極其靈活。由於 TCP 和套接字是低 級別結構,因此它們適合各種方案,特別適合在異質技術構造環境中進行測試。例如,您可以測試承載於 非 Windows 客戶端平台上的 WCF 服務。雖然您不得不根據客戶端支持的語言(通常是 C++ 或 Perl)修 改我在此提供的具體 C# 代碼,但總體方法是相同的。

此外,基於套接字的方法對於安全性測試(您必須假定任何 WCF 服務都將會遭到不友好的套接字探測 )和性能測試(套接字方法十分直觀,可提供基線往返計時數據)也十分有用。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved