3.2.1. Giới thiệu
Các kỹ thuật trong phần này chỉ cho chúng ta làm thế nào để kiểm thử Web services ASP.NET. Web services thường xuất dữ liệu từ cơ sở dữ liệu SQL. Ví dụ, giả sử một công ty nào đó bán sách. Công ty đó muốn dữ liệu sách của họ có sẵn cho các công ty khác có thể khai thác và mở rộng. Tuy nhiên Công ty đó không muốn cho phép người khác truy cập trực tiếp vào cơ sở dữ liệu của họ. Một giải pháp cho vấn đề
này chính là tạo một Web service để xuất dữ liệu sách, đơn giản, chuẩn hoá và lại vừa đảm bảo an toàn dữ liệu.
Web service này chấp nhận yêu cầu dữ liệu từ ứng dụng web trên máy khách và lấy dữ liệu thích hợp từ Back-end cơ sở dữ liệu SQL và trả dữ liệu về ứng dụng web để hiển thị. Web service này có 2 phương thức:
GetTitles(): nó chấp nhận một chuỗi cần tìm như là một đối đầu vào và trả về một DataSet thông tin các sách mà tiêu đề sách chứa chuỗi cần tìm
CountTitles(): nó cũng có đối là một chuỗi cần tìm nhưng nó trả về tổng số sách mà có tiêu đề sách chứa chuỗi cần tìm.
Kiểm thử các phương thức của web service là một khái niệm tương tự như là kiểm thử các hàm API sử dụng trong các ứng dụng Desktop. Chúng ta cần đối số đầu vào của phương thức và lấy giá trị đầu ra của phương thức đó, sau đó so sánh giá trị nhận được với một giá trị mong đợi tại thời điểm thực thi. Điểm khác nhau chính ở đây là các phương thức Web nằm trên một máy chủ từ xa và được gọi trên mạng, nó có thể được gọi theo một vài cách khác nhau.
Giao thức giao tiếp cơ bản của web service là SOAP (Simple Object Access Protocol). Như chúng ta biết SOAP không có gì khác hơn một form XML. Chính vì điều này mà đôi khi web service còn được gọi là dịch vụ web XML. Mặc dù web service là giao thức chuyển giao độc lập, trên thực tế các web service hầu như luôn luôn liên kết và sử dụng giao thức HTTP. Vì vậy khi một web service được tạo thì cần được đóng gói trong các gói SOAP/XML. Đó là các gói được đóng gói lần lượt trong một gói HTTP. Các gói HTTP sau đó được gửi thông qua giao thức TCP/IP. Các gói TCP/IP cuối cùng được gửi thông qua 2 sockets mạng theo byte (Visual Studio.NET đã ẩn quá trình phức tạp này trong dịch vụ web proxy). Vì vậy, có 4 cách cơ bản để gọi một phương thức của web service trong ASP.NET:
Using a Proxy Mechanism.
Using HTTP.
Using TCP.
Using Sockets.
Mỗi tình huống kiểm thử nên chạy 2 lần: đầu tiên gửi dữ liệu nhập và trả về giá trị ở cấp cao dùng máy proxy. Thứ 2, gửi và nhận dữ liệu ở cấp thấp dùng máy TCP.
Mục đích của ý tưởng trên là dùng 2 cách khác nhau để kiểm tra sự đồng nhất của kết quả trả về. Kiểm thử trả về kết quả đúng nếu 2 cách kiểm thử đều trả về một kết quả, ngược lại thì phải kiểm tra lại
Chú ý rằng tình huống 2 đưa ra kết quả là “pass” khi gọi phương thức GetTitles() với dữ liệu nhập là And thông qua TCP, nhưng lại là fail khi chúng ta gọi thông qua máy Proxy. Xác minh liên quan chặt chẽ tới kiểm chứng. Chúng ta thường nói rằng kiểm chứng yêu cầu SUT làm việc phải đúng đắn trong khi đó xác minh yêu cầu nếu chúng ta kiểm thử là đúng đắn.
Để tạo một bảng dữ liệu và chèn dữ liệu vào bảng ta có thể dùng SQL Script sau: Create Database dbBooks
Use dbBooks
Create table tblBooks
(
bookid char(3) primary key, booktitle varchar(50) not null, bookprice money not null )
go
insert into tblBooks values('001','First Test Automation Principles',11.11) insert into tblBooks values('002','Theory and Practice of Testing',22.22)
insert into tblBooks values('003','Build Better Software through
Automation',33.33) insert into tblBooks values('004','Lightweight Testing Techniques',44.44) insert into tblBooks values('005','Testing Principles and
Algorithms',55.55) go
exec sp_addlogin 'webServiceLogin', 'secret' go
Cơ sở dữ liệu dbBooks chứa một bảng là tblBooks, nó có 3 cột là: bookid, booktitle, bookprice, bảng này có 5 bản ghi và với tên đăng nhập SQL là webServiceLogin. 2 Stored Procedures ở trong Database là:
create procedure usp_GetTitles @filter varchar(50)
as
select * from tblBooks where booktitle like '%' + @filter + '%' go
create procedure usp_CountTitles @filter varchar(50)
as
declare @result int
select @result = count(*) from tblBooks where booktitle like '%' +
@filter + '%' return @result
go
Stored procedure usp_GetTitles() với một đối số kiểu chuỗi và nó lọc theo chuỗi đó trả về tập các bản ghi mà có cột booktitle chứa chuỗi đó. Stored procedure còn lại đếm số bản ghi mà cột booktitle chứa chuỗi cần tìm.
Web service trong kiểm thử của ví dụ trên tên là BookSearch. Dịch vụ có 2 phương thức, phương thức đầu tiên tên là GetTitles() và được định nghĩa:
[WebMethod]
public DataSet GetTitles(string filter) {
try {
string connStr ="Server=(local);Database=dbBooks;
UID=webServiceLogin;PWD=secret"; SqlConnection sc = new SqlConnection(connStr);
SqlCommand cmd = new SqlCommand("usp_GetTitles", sc); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add("@filter", SqlDbType.VarChar, 50);
cmd.Parameters["@filter"].Direction = ParameterDirection.Input; cmd.Parameters["@filter"].Value = filter;
SqlDataAdapter sda = new SqlDataAdapter(cmd); DataSet ds = new DataSet();
sda.Fill(ds); sc.Close(); return ds; } catch { return null; }
} // GetTitles
Phương thức GetTitles() gọi stored procedure usp_GetTitles() và đẩy vào một đối tượng DataSet. Tương tự phương thức web CountTitles() gọi stored procedure usp_CountTitles():
[WebMethod]
public int CountTitles(string filter) {
try {
string connString ="Server=(local);Database=dbBooks;
UID=webServiceLogin;PWD=secret"; SqlConnection sc = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand("usp_CountTitles", sc); cmd.CommandType = CommandType.StoredProcedure;
SqlParameter p1 = cmd.Parameters.Add("ret_val", SqlDbType.Int, 4); p1.Direction = ParameterDirection.ReturnValue; SqlParameter p2 = cmd.Parameters.Add("@filter", SqlDbType.VarChar, 50); p2.Direction = ParameterDirection.Input; p2.Value = filter; sc.Open(); cmd.ExecuteNonQuery();
int result = (int)cmd.Parameters["ret_val"].Value; sc.Close(); return result; } catch { return -1; } } // CountTitles()
Ngoại trừ thuộc tính [WebMethod] còn lại không có gì khác so với các phương thức bình thường. Dưới đây là đoạn mã sử dụng để gọi phương thức của một webservie:
private void Button1_Click(object sender, System.EventArgs e) {
try {
TheWebReference.BookSearch bs = new
TheWebReference.BookSearch(); string filter = TextBox1.Text.Trim();
DataSet ds = bs.GetTitles(filter); DataGrid1.DataSource = ds; DataGrid1.DataBind();
Label3.Text = "Found " + ds.Tables["Table"].Rows.Count + "
items"; } catch(Exception ex) { Label3.Text = ex.Message; } }
Đoạn mã trên minh họa máy proxy. Việc gọi một phương thức web của một web service giống như gọi một phương thức thông thường. Khi chúng ta kiểm thử một dịch vụ web dùng máy proxy thì mã kiểm thử sẽ rất giống với mã của ứng dụng.
Khi một web service truy cập một cơ sở dữ liệu dùng store procedure (SP), thì các SP được coi là một phần của SUT. Những kỹ thuật kiểm thử dùng để kiểm thử các SP chúng ta chưa quan tâm ở đây. Các kỹ thuật của phần này chỉ demo cách thức để gọi và kiểm thử một phương thức web với một tình huống kiểm thử (test case).
3.2.2. Kiểm thử một phƣơng thức Web dùng Proxy
Để kiểm thử một phương thức trong web service bằng cách gọi các phương thức dùng máy proxy, chúng ta dùng Visual Studio.NET thêm một tham chiếu web vào kiểm thử tự động mà nó trỏ tới web service. Đây là phương pháp tạo ra một proxy cho web service mà web service xuất hiện trong lớp địa phương (local class). Sau đó khởi tạo một đối tượng biểu diễn web service và gọi phương thức thuộc service đó:
try {
string input = "the"; int expectedCount = 1;
TheWebReference.BookSearch bs = new
TheWebReference.BookSearch(); DataSet ds = new DataSet();
Console.WriteLine("Calling Web Method GetTitles() with 'the'"); ds = bs.GetTitles(input);
if (ds == null)
Console.WriteLine("Web Method GetTitles() returned null"); else
{
int actualCount = ds.Tables["Table"].Rows.Count;
Console.WriteLine("Web Method GetTitles() returned " +
actualCount + " rows"); if (actualCount == expectedCount) Console.WriteLine("Pass"); else Console.WriteLine("*FAIL*"); } Console.WriteLine("Done"); Console.ReadLine(); } catch(Exception ex) {
Console.WriteLine("Fatal error: " + ex.Message); Console.ReadLine();
}
Trong đoạn mã trên, giả sử rằng có một web service tên là BookSearch chứa một phương thức tên là GetTitles(). Phương thức này có một đối số đầu vào là chuỗi cần tìm và trả về một đối tượng DataSet chứa thông tin về các sách có tiêu đề chứa chuỗi cần tìm.
Khi thêm một tham chiếu Web vào harness code, thì tên của tham chiếu thay đổi từ mặc định là localhost sang TheWebReference. Tên này sau được dùng như là một bí danh namespace. Tên dịch vụ web là BookSearch, hoạt động như là một proxy và nó được khởi tạo như lớp địa phương vì vậy chúng ta có thể gọi GetTitle() giống như gọi các phương thức khởi tạo bình thường.
Đây là cách đơn giản nhất trong 4 cách kiểm thử web service, chúng ta gọi một phương thức web như là các ứng dụng thông thường. Tình huống này tương tự như là kiểm thử API. Dùng máy proxy là cơ bản nhất để gọi dịch vụ web và nó luôn là một phần quan trọng trong việc viêt một chương trình kiểm thử tự động.
Trong ví dụ này, quyết định giá trị đúng đắn trả về từ phương thức GetTitles() khó hơn là gọi phương thức có giá trị trả về là một kiểu vô hướng, bởi vì GetTitle() trả về một đối tượng DataSet. Ví dụ trường hợp giá trị trả về là kiểu Int chẳng hạn thì việc quyết định kết quả pass/fail hoàn toàn dễ dàng. Ví dụ như phương thức CountTitles() chẳng hạn:
Console.WriteLine("Testing CountTitles() via poxy mechanism");
TheWebReference.BookSearch bs = new TheWebReference.BookSearch(); string input = "testing";
int expected = 3;
int actual = bs.CountTitles(input); if (actual == expected)
Console.WriteLine("Pass"); else
Console.WriteLine("*FAIL*");
3.2.3. Kiểm thử một phƣơng thức Web dùng Sockets
Để kiểm thử một phương thức web trong một web service bằng cách gọi phương thức dùng sockets, chúng ta thực hiện các bước sau[15,20]:
Xây dựng một thông điệp SOAP gửi tới phương thức web.
Khởi tạo một đối tượng Socket và kết nối tới một máy chủ từ xa nơi chứa
web service.
Xây dựng một header chứa thông tin về HTTP.
Gửi Header bổ sung vào thông điệp SOAP dùng phương thức
Socket.Send().
Nhận SOAP phản hồi dùng phương thức Socket.Receive() trong một vòng lặp.
Phân tích SOAP phản hồi với một giá trị mong đợi.
Dưới đây là một ví dụ gửi một chuỗi “testing” tới phương thức web GetTitles() và kiểm tra phản hồi:
Console.WriteLine("Calling Web Method GetTitles() using sockets"); string input = "testing";
string soapMessage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"; soapMessage += "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchemainstance\""; soapMessage += " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""; soapMessage += "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"; soapMessage += "<soap:Body>";
soapMessage += "<GetTitles xmlns=\"http://tempuri.org/\">"; soapMessage += "<filter>" + input + "</filter>";
soapMessage += "</GetTitles>"; soapMessage += "</soap:Body>"; soapMessage += "</soap:Envelope>"; Console.WriteLine("SOAP message is: \n"); Console.WriteLine(soapMessage);
string host = "localhost";
string webService = "/TestAuto/Ch8/TheWebService/BookSearch.asmx"; string webMethod = "GetTitles";
IPHostEntry iphe = Dns.Resolve(host);
IPAddress[] addList = iphe.AddressList; // addList[0] == 127.0.0.1 EndPoint ep = new IPEndPoint(addList[0], 80); // ep = 127.0.0.1:80
Socket socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp); socket.Connect(ep); if (socket.Connected) Console.WriteLine("\nConnected to " + ep.ToString()); else
Console.WriteLine("\nError: socket not connected"); string header = "POST " + webService + " HTTP/1.1\r\n"; header += "Host: " + host + "\r\n";
header += "Content-Type: text/xml; charset=utf-8\r\n";
header += "Content-Length: " + soapMessage.Length.ToString() +
"\r\n"; header += "Connection: close\r\n";
header += "SOAPAction: \"http://tempuri.org/" + webMethod +
"\"\r\n\r\n"; Console.Write("Header is: \n" + header);
string sendAsString = header + soapMessage;
int numBytesSent = socket.Send(sendAsBytes, sendAsBytes.Length, SocketFlags.None);
Console.WriteLine("Sending = " + numBytesSent + " bytes\n"); byte[] receiveBufferAsBytes = new byte[512];
string receiveAsString = ""; string entireReceive = ""; int numBytesReceived = 0;
while ((numBytesReceived = socket.Receive(receiveBufferAsBytes, 512, SocketFlags.None)) > 0 ) { receiveAsString = Encoding.ASCII.GetString(receiveBufferAsBytes, 0, numBytesReceived); entireReceive += receiveAsString; }
Console.WriteLine("\nThe SOAP response is " + entireReceive); Console.WriteLine("\nDetermining pass/fail"); if ( entireReceive.IndexOf("002") >= 0 && entireReceive.IndexOf("004") >= 0 && entireReceive.IndexOf("005") >= 0 ) Console.WriteLine("\nPass"); else Console.WriteLine("\nFail");
Trong ví dụ trên, bước đầu tiên là xây dựng một thông điệp SOAP. Chúng ta phải xây dựng thông điệp SOAP trước khi xây dựng chuỗi HTTP header bởi vì chuỗi header yêu cầu độ dài của thông điệp SOAP. Việc xây dựng thông điệp SOAP trở lên dễ dàng hơn, lấy một mẫu thông điệp SOAP của Visual Studio.NET bằng cách nạp web service như là một project. Tiếp theo chúng ta chỉ cho Visual Studio.NET chạy dịch vụ bằng cách bấm F5. Vì một web service là một kiều thư viện và nó không có khả năng thi hành, dịch vụ không thể chạy nên Thay vì đó Visual Studio chạy một tiện ích ứng dụng và xuất ra mẫu thông điệp SOAP để gửi. Ví dụ, chỉ dẫn dịch vụ BookSearch để chạy và chọn phương thức GetTitles() đưa ra một trang web mà chứa mẫu thông tin:
POST /TestAuto/TheWebService/BookSearch.asmx HTTP/1.1 Host: localhost
Content-Type: text/xml; charset=utf-8 Content-Length: length
SOAPAction: "http://tempuri.org/GetTitles" <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema- instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <GetTitles xmlns="http://tempuri.org/"> <filter>string</filter> </GetTitles> </soap:Body> </soap:Envelope>
Chúng ta có thể xây dựng thông điệp bằng cách chèn vào một chuỗi ngắn như trong ví dụ của giải pháp trước, hoặc chúng ta có thể đơn giản gán toàn bộ thông điệp SOAP như một chuỗi dài (chú ý rằng SOAP là không có gì hơn một kiểu đặc biệt của XML. Trong XML chúng ta có thể dùng dấu nháy đơn hoặc dấu nháy kép). Cần phải lưu ý tới cú pháp khi xây dựng thông điệp SOAP bởi vì cú pháp ở đây rất chặt chẽ điều đó có nghĩa là nếu chúng ta chỉ đưa ra một ký tự sai thì hệ thống sẽ sinh lỗi[10].
Khi chúng ta gọi một phương thức của web service dùng socket chúng ta phải khởi tạo đối tượng socket và kết nối tới máy chủ từ xa nơi chứa web service. Lớp Socket nằm trong namespace System.Net.Sockets.
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Đối tượng Socket như là một máy trừu tượng nó gửi và nhận dữ liệu trên mạng. Đối số thứ nhất nói lên giá trị chỉ ra của IP là phiên bản 4. Một số giá trị khác là:
InterNetworkV6: Address for IP version 6.
NetBios: NetBios address.
Unix: Unix local to host address.
SNA: IBM SNA (Systems Network Architecture) address.
Đối số thứ 2 chỉ ra một trong 6 kiểu sockets mà chúng ta có thể tạo. Dùng SocketType.Stream nghĩa là soket là một TCP socket. Đối số thứ 3 biểu diễn giao thức mạng mà nó sẽ dùng để giao tiếp. Kiểu ProtocolType.Tcp là kiểu chung nhất nhưng kiểu ProtocolType.Udp cũng có sẵn để dùng. Khả năng linh hoạt lớn là có thể khởi tạo một đối tượng socket dễ dàng hơn dùng máy proxy. Ví dụ, dùng socket cho phép
chúng ta gọi một UDP web service. Sau khi chúng ta tạo một đối tượng Socket chúng ta kết nối tới máy chủ nơi chứa các web service.
Phương thức Socket.Connect() chấp nhận một đối tượng EndPoint - là một địa chỉ IP cộng với số cổng. Chúng ta cũng có thể chỉ ra địa chỉ IP trực tiếp giống như câu lệnh:
IPAddress ipa = IPAddress.Parse("127.0.0.1");
Chúng ta cũng có thể lấy địa chỉ IP bằng cách gọi phương thức Dns.Resolve(), nó trả về một danh sách địa chỉ IP ánh xạ. Sau khi có địa chỉ IP việc đơn giản là chúng ta tạo kết nối.
Bước thứ 3: khi gọi một phương thức web dùng socket thì phải xây dựng một header mà nó chứa các thông tin về HTTP. Chúng ta có thể nhận thông tin này từ Visual Studio để sinh ra mẫu. Bước thứ 4: khi goi phương thức web dùng socket để gửi một header và bổ sung thông điệp SOAP dùng phương thức Socket.Send(). Bước 5 khi gọi phương thức web dùng sockets để nhận SOAP phản hồi ta dùng phương thức Socket.Receive() ben trong vòng lặp:
byte[] receiveBufferAsBytes = new byte[512]; string receiveAsString = "";
string entireReceive = ""; int numBytesReceived = 0;
while ((numBytesReceived = socket.Receive(receiveBufferAsBytes, 512, SocketFlags.None)) > 0 ) { receiveAsString = Encoding.ASCII.GetString(receiveBufferAsBytes, 0, numBytesReceived); entireReceive += receiveAsString; }
Không có cách nào có thể đoán biết được dung lượng nhận về nên ta dùng buffer 512 để tách. Đến bước này chúng ta đã nhận được toàn bộ SOAP phản hồi vào một biến kiểu chuỗi. Nếu chúng ta chỉ gọi phương thức web sử dụng socket thì đến bước này coi như xong. Nhưng nếu chúng ta làm kiểm thử phương thức web thì chúng ta phải thực hiện bước tiếp theo là kiểm tra dữ liệu với giá trị trả về. Điều này là không hề dễ dàng. Trong giải pháp trước, chúng ta nhận được 3 chuỗi con, là các ID của 3 cuốn sách mà có tiêu đề là “Testing”:
if ( entireReceive.IndexOf("002") >= 0 && entireReceive.IndexOf("004") >= 0 &&
entireReceive.IndexOf("005") >= 0 ) Console.WriteLine("\nPass");
else
Console.WriteLine("\nFail");
Cách tiếp cận này không hoàn toàn đảm bảo rằng phản hồi SOAP là chính xác, bởi vì giá trị trả về khi gọi một phương thức web dùng sockets là một chuỗi SOAP, nó trả về một kiểu XML, một giá trị kiểm chứng đầy đủ sẽ phải là chuỗi SOAP/XML. Vì vậy việc so sánh 2 tài liệu hoặc 2 phân đoạn XML sẽ là cách tiếp cận đúng đắn và đầy đủ hơn.
3.2.4. Kiểm thử một phƣơng thức Web dùng HTTP