Hình 14 : Kiểm thử tự động tích hợp cả 2 webservices
3.1. Một số vấn đề về kiểm thử các trang web
3.1.8. Xử lý đầu vào có chứa các ký tự đặc biệt
Khi webClient gửi các yêu cầu có các ký tự đặc biệt như k tự “&” thì cần phải xử lý các ký tự đặc biệt này. Sử dụng phương thức HttpUtility.UrlEncode() để chuyển đổi các ký tự đặc biệt sang các kiểu ký tự khác tương đương:
string badValueForTextBox1 = "this&that"; string goodValueForTextBox1 =
HttpUtility.UrlEncode(badValueForTextBox1);
string data = "TextBox1=" + goodValueForTextBox1;
Nếu có ký tự đặc biệt trong thông tin Http-request thì khi nhận ASP.NET Web server có thể sẽ không hiểu. Việc mã hoá URL bằng cách chuyển đổi các ký tự không cho phép trong URL sang các ký tự phụ hợp tương đương, ví dụ như: cần chuyển chuỗi yêu cầu đến Web server mà trong URL có các ký tự “<” và “>” thì có thể chuyển đổi thành %3c và %cd tương ứng.
Giả sử có một ứng dụng Web ASP.NET có chưa đoạn mã sau:
if (TextBox1.Text == "this&that") TextBox2.Text = "Oh really"; Else
TextBox2.Text = "unknown input";
Để kiểm tra cần phải gửi chuỗi “this&that” tới ứng dụng Web. Nếu gửi trực tiếp chuỗi:
string data = "TextBox1=this&that";
thì sẽ nhận được được Http-response với “unknown input” trong giá trị của Textbox2 thay vì “Oh realy”, vì webServer sẽ từ chối ký tự “&”. Để khắc phục vấn đề này sử dụng phương thức HttpUtility.UrlEncode() chuyển đổi ký tự “&” thành “%26”, khi đó webServer nhận các Http-request và sẽ giải mã “%26” thành ký tự “&”.
Khi kiểm thử, downside là một thủ thuật vì đôi khi chúng ta cần phải gửi thực sự các ký tự đặc biệt đó. Chúng ta tiếp cận bằng cách tạo ra 2 công việc đồng thời :
Một là luôn luôn thực hiện phương thức UrlEncode() trên mỗi phần của cặp dữ liệu Post “tên – giá trị”.
Một là không bao giờ thực hiện phương thức UrlEndcode().
Hoặc cách khác là tham số hoá Harness để truyền vào dữ liệu post và lấy giá trị yêu cầu từ kho dữ liệu chuẩn bên ngoài, gồm cả giá trị biểu thị dữ liệu vào có được mã hóa URL hay không.
001!TextBox1!red!noencode!Roses are red 002!TextBox1!this&that!encode!Oh really
003!TextBox1!this&that!noencode!unknown input
Chúng ta có thể quyết định có hay không có URL-Endcode
while ((line = sr.ReadLine()) != null) // Kiểm tra lặp { tokens = line.Split('!'); if (tokens[3] == "encode") input = HttpUtility.UrlEncode(tokens[2]); else input = tokens[2];
// vân vân.... }
3.1.9. Lập chƣơng trình lấy các giá trị VS và giá trị EV
Trong ứng dụng Web ASP.NET 2.0. để lấy các giá trị ViewState và giá trị EventValidation, có thể dùng đối tượng WebClient gửi một Http-request với mục đích thăm dò ứng dụng. Sau đó nhận về các Http-response chúng ta sử dụng phương thức String.IndexOf() và String.SubString() để lấy các giá trị ViewState và giá trị EventValidation. Mã lệnh thực thi như sau:
Nếu thực thi trên môi trường ASP.NET 2.0:
string uri = "http://server/path/WebForm.aspx"; WebClient wc = new WebClient();
Stream st = wc.OpenRead(uri);
StreamReader sr = new StreamReader(st); string res = sr.ReadToEnd();
sr.Close(); st.Close();
int startVS = res.IndexOf("__VIEWSTATE", 0) + 37; int endVS = res.IndexOf("\"", startVS);
string vs = res.Substring(startVS, (endVS-startVS)); Console.WriteLine("ViewState = " + vs);
int startEV = res.IndexOf("__EVENTVALIDATION", 0) + 49; int endEV = res.IndexOf("\"", startEV);
string ev = res.Substring(startEV, (endEV-startEV)); Console.WriteLine("EventValidation = " + ev);
Nếu thực thi trên môi trường ASP.NET 1.1:
string uri = "http://server/path/WebForm.aspx"; WebClient wc = new WebClient();
Stream st = wc.OpenRead(uri);
StreamReader sr = new StreamReader(st); string res = sr.ReadToEnd();
sr.Close(); st.Close();
int start = res.IndexOf("__VIEWSTATE", 0) + 20; int end = res.IndexOf("\"", start);
Console.WriteLine("ViewState = " + vs);
Trước khi xây dựng một chương trình tự động thực hiện gửi một Http-request tới một ứng dụng web, thì phải quyết định giá trị ViewState và giá trị EventValidation của ứng dụng (với ASP.NET 2.0). Sau mỗi vòng yêu cầu – phản hồi, giá trị trạng thái của ứng dụng được mã hoá bởi base64. Phương thức này tương tự như việc phát triển ứng dụng web phải duy trì các trạng thái trong trang ASP bằng cách dùng các thẻ HTML ẩn các giá trị đầu vào.
Chú ý: phải loại bỏ các ký tự trích dẫn. Sau đó sẽ xác định được chỉ mục của 2 ký tự trích dẫn làm phân cách cho giá trị ViewState, lấy giá trị ViewState bằng cách sử dụng phương thức SubString():
string vs = res.Substring(start, (end-start));
Chúng ta cũng có thể tạo đoạn mã lệnh thực hiện chức năng kiểm thử tự động bằng cách tạo ra một phương thức thực thi có nhận dữ liệu vào là chuỗi URL và trả về chuỗi ViewState:
private static string ViewState(string uri) {
try {
WebClient wc = new WebClient(); Stream st = wc.OpenRead(uri);
StreamReader sr = new StreamReader(st); string res = sr.ReadToEnd(); sr.Close();
st.Close();
int start = res.IndexOf("__VIEWSTATE", 0) + 20; int end = res.IndexOf("\"", start);
string vs = res.Substring(start, (end-start)); return vs;
} catch {
throw new Exception("Fatal error finding ViewState"); }
}
string uri = "http://server/path/WebForm.aspx"; string postData = "TextBox1=red&";
...
string vs = ViewState(uri); vs = HttpUtility.UrlEncode(vs); ....
postData += "__VIEWSTATE=" + vs;
Tương tự như vậy chúng ta có thể lấy giá trị EventValidation. Giá trị ViewState và EventValidation được nối vào chuối dữ liệu post, nhưng cũng có thể thực hiện đơn giản hơn bằng cách tạo phương thức bao gồm cả hai phương thức này, mã lệnh của phương thức như sau:
private static string ViewStateAndEventValidation(string uri) {
try {
WebClient wc = new WebClient(); Stream st = wc.OpenRead(uri); StreamReader sr = new StreamReader(st); string res = sr.ReadToEnd(); sr.Close();
st.Close();
int startVS = res.IndexOf("__VIEWSTATE", 0) + 37; int endVS = res.IndexOf("\"", startVS);
string vs = res.Substring(startVS, (endVS-startVS)); vs = HttpUtility.UrlEncode(vs);
int startEV = res.IndexOf("__EVENTVALIDATION", 0) + 49; int endEV = res.IndexOf("\"", startEV);
string ev = res.Substring(startEV, (endEV-startEV)); ev = HttpUtility.UrlEncode(ev);
return "&__VIEWSTATE=" + vs + "&__EVENTVALIDATION=" + ev; }
catch {
throw new Exception("Fatal error finding ViewState or EventValidation");
} }
Thực hiện theo phương thức này giá trị thực sự ViewState và EventValidation phụ thuộc vào Web AUT
3.1.10. Xử lý với các điểu khiển Checkbox và RadioButtonList
Khi muốn gửi một Http-request, trong đó chỉ ra một điều khiển CheckBox hoặc điều khiển RadioButton có giá trị được check, chỉ cần sửa nội dung chuỗi dữ liệu POST với các cặp giá trị “tên-giá trị” thành ID của điều khiển và giá trị mới của điều khiển đó:
string url = "http://server/path/WebForm.aspx";
string data = "CheckBox1=checked&RadioButtonList1=Alpha";
string viewstate = HttpUtility.UrlEncode("dDwtMTQ2MzgwNTQ2MD=="); data += "&__VIEWSTATE=" + viewstate;
CheckBox và RadioButtonList là hai điều khiển thường dùng trong các ứng dụng Web ASP.NET. Giả sử có ứng dụng sau:
<html> <head>
<script language="c#" runat="server">
void Button1_Click(object sender, System.EventArgs e) {
if (CheckBox1.Checked == true)
TextBox1.Text = "CheckBox is checked";
else
TextBox1.Text = "CheckBox NOT checked"; if (RadioButtonList1.Items[0].Selected == true) TextBox2.Text = "Alpha selected";
else if RadioButtonList1.Items[1].Selected == true) TextBox2.Text = "Beta selected";
} </script> </head> <body>
<h3>CheckBox and RadioButtonList</h3>
<form id="Form1" method="post" runat="server"> <p>Check or not:
<asp:CheckBox id="CheckBox1" runat="server" /> <p>Select one:
<asp:ListItem Value="Alpha">Alpha</asp:ListItem> <asp:ListItem Value="Beta">Beta</asp:ListItem> </asp:RadioButtonList>
<p>
<asp:Button id="Button1" text="Send"
onclick="Button1_Click" runat="server" /> <p>My obervations:
<p><asp:TextBox id="TextBox1" runat="server" /></p> <p><asp:TextBox id="TextBox2" runat="server" /></p> </form>
</body> </html>
Kiểm tra ứng dụng web này để xác định rõ khi nào điều khiển CheckBox1 được tích và khi nào thì điều khiển RadioButtonList lựa chọn giá trị nào và xuất ra ra thông báo trong điều khiển TextBox. Để ứng dụng gửi Http-request có giá trị tương đương với giá trị của CheckBox1 và RadioButtonList, thiết lập chuối dữ liệu post như sau:
string data = "CheckBox1=checked&RadioButtonList1=Alpha"; data += "&TextBox1=empty&TextBox2=empty&Button1=clicked";
Nếu thực thi dữ liệu này trên ứng dụng web, kết quả Http-response như sau:
<p>My obervations:
<p><input name="TextBox1" type="text" value="CheckBox is checked" id="TextBox1" /></p>
<p><input name="TextBox2" type="text" value="Alpha selected" id="TextBox2"/></p>
Để biểu thị đối tượng CheckBox không được tích(check), gửi Http-request với cặp “tên - giá trị” không có thành phần giá trị “CheckBox1=”, tương tự như vậy nếu RadioButtonList1 không có giá trị được chọn, chuỗi gửi là: “RadioButtonList1=”
3.2. Kiểm thử web services
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()