如果你正在HTTP上使用安全套接字层(SSL)来加密用户数据,并且想通过编程来测试你的Web应用,你会发现此技术并非广为人知。在本月的栏目中,我将示范如何建立一个 SSL 测试服务器,然后编写测试自动化代码,并通过一个简单而又具有代表性的 Web 应用来验证。
Microsoft .NET环境提供了强有力的测试工具来测试采用SSL安全机制的ASP.NET Web应用程序。为了说明它们的使用方法,我将建立一个SSL服务器,并通过一个短小精悍的程序来示范如何自动测试基于HTTPS的Web例子应用程序。虽然 在这方面已有个别技术非常经典并且有很好的文章介绍,但在与我的许多同事交流中,我发现对测试 SSL Web 应用的整体过程的把握相对来说并不是很到位。用两幅屏幕截图最能说明问题。Figure 1展示了一个简单但是很有代表性的ASP.NET的Web应用。
Figure 1 一个 ASP.NET Web 应用
注意我使用的是 SSL 连接,因为我要在 Internet 上传送敏感的信用卡信息(注意是"https://"协议,并且在状态栏有一个小锁 图标)。
现在,让我们想象一下用手工方式是如何测试这个应用程序的。我们必须在Web页上输入成百甚至上千的用户名,数量以及信用卡号码,检查每一个确认码,针对预期的结果检查每行代码以确定结果是否正确,然后将这些结果记录在一些表格中,比如 Excel 电子表格或者文本文件中。整个过程耗时、低效、繁琐并且容易出错。
一个更好的方法是利用.NET框架的强大能力编写自动化测试例程,在程序中用 SSL 发送测试数据,然后针对预期的确认码来检查响应流,Figure 2 是一个控制台应用程序,它演示了上述的思路。
正像你看到的,自动化测试案例的基本做法与 Figure 1 中所示的手动测试是一样的。用户名称是"Smith",物品数量是"3",信用卡号是"1234 5678 9012", 通过基于 SSL 的 HTTP 加密后被提交到Web应用,测试程序获取 HTTP 响应流,并搜索响应流中的“C3-57-ED-DA-8B”,这时,在该响应流中找到期望的确认码,所以测试 自动化程序记录下“PASS”结果。在本栏目后面的三个章节中,我将讲解产生如 Figure 2 所示输出的测试程序。演示如何建立一个接受 SSL 请求的测试服务器,并讨论如何扩展本文呈现的技术来满足你自己的需要。
在我讲解如何编写测试自动化程序之前,让我们首先快速回顾一下本文的例子Web应用程序。正如你在 Figure 1 看到的一样,有三个 TextBox 控件,我使用 Visual Studio .NET 缺省的ID:TextBox1、TextBox2、TextBox3 来命名它们。它们分别对应着用户名称、物品数量以及信用卡帐号。 Label5 控件 用于显示应用程序信息。当我编写测试自动化程序时,我必须要知道这些信息,此外还需要知道订单确认码的产生方式,以便我能确定我的测试案例期望的结果。以下是用于测试此Web应用程序的核心代码:
if (TextBox3.Text.Length == 0)
Label5.Text = "Please enter credit card number";
else
{
byte[] input = Encoding.Unicode.GetBytes(TextBox3.Text);
byte[] hashed;
using(MD5 m = new MD5CryptoServiceProvider())
{
hashed = m.ComputeHash(input);
}
Label5.Text = "Thank you. Your confirmation code is " +
BitConverter.ToString(hashed).Substring(0,14);
}
为了模拟确认码的生成,我只利用了用户输入的信用卡号,用它产生一个MD5散列,然后截取散列值最左边的14个字符。在实际的生产系统中,你可能会用更为复杂的方式来产生确认码。在这种情况下确定预期的结果可能会更具技巧性。不过有一点要特别注意,你不能通过调用被测试的程序来确定预期结果,因为这将破坏测试的有效性,因为你本来就是要检查 测试自动化程序返回的结果和被测程序返回的结果是否一致。
测试自动化程序
这个测试自动化程序出奇的短小。其全部代码如 Figure 3 所示。尽管通过编程将数据提交给某个ASP.NET Web 应用程序的技术在 MSDN 库中已有文档描述,但是其中有几个技巧需要特别关注 。
我决定编写一个C#控制台程序作为我的测试程序。使用和被测程序一样的语言开发测试自动化程序通常是个好主意。不管怎么说,规划良好的设计并与.NET环境集成意味着你能安全地使用Visual Basic .NET或其它任何与.NET兼容的语言。一般来说,控制台程序类型最适合作为 测试自动化程序。虽然测试程序具备漂亮的用户界面能给用户留下深刻印象,但自动化测试程序是一个工具,而不是个人秀,此外,控制台程序也比GUI程序更容易集成到构建系统中。
测试自动化程序的整个结构相当简单。我将测试案例的数据保存在一个简单的文本文件中,每一行数据表示单个测试案例,以下是该测试案例文件的内容:
001:Smith:3:1234 5678 9012:C3-57-ED-DA-8B
002:Baker:2:1111 2222 3333:CE-81-8C-2F-94
003:Gates:9:9999 9999 9999:95-D6-05-31-8A
信息之间使用冒号(:)进行分隔。我也可以使用任何字符作为分隔符,但在实际的测试案例中避免出现含义模糊的字符很重要。第一个字段是测试案例编号,第二个字 是用户名称,第三个字段是数量,第四个字段是信用卡号码,第五个字段是预期的确认码。如果你不想使用文本文件,那么XML文件或 SQL 表 都是很好的可选方案。
我的测试自动化程序的基本结构与我的测试案例数据文件是相关在一起的。使用伪代码表示如下:
loop
read a test case line
parse out test case data
build up data to post to application
convert post data to a byte array
post the data
retrieve the response stream
if response stream contains expected confirmation code
log "pass" result
else
log "fail" result
end loop
我首先声明要用到的命名空间,这样可以避免用到每个.NET类和对象时都得写全称限定名。同时测试自动化程序将要涉及哪些类库功能也一目了然。 using System;
using System.Web;
using System.Text;
using System.Net;
using System.IO;
System.Web 命名空间包含了 HttpUtility 类,这个类可以将一些特殊字符转换为转义字符序列,因为缺省的控制台程序并不引用它的所在程序集,即 System.Web.dll,我们必须手动地添加对它的引用。System.Text 命名空间包含了一个Encoding 类,我要用它来处理字节数组 (Byte Array)。System.Net 命名空间包含了 HttpWebRequest类, 它是将数据提交到 ASP.NET Web 应用 的基础类。使用 System.IO 命名空间 是因为我要用数据流处理基于 SSL 的 HTTP 的响应,此外我还需要用它从文本文件中读取测试案例数据。注意:using 指令字 允许你在使用某个命名空间中的类型时,不必用长长的限定名。
接下来,在命令外壳中显示一段简单的启动信息后,声明测试自动化 程序要用到的一些关键变量: string url = "https://localhost/LitwareOrder/Order.aspx";
string viewstate = HttpUtility.UrlEncode(
"dDw0MDIxOTUwNDQ7Oz6E/7ailqx8X9zCUfpbWTPybfS4MA==");
string line;
string[] tokens;
StringBuilder data = new StringBuidler();
byte[] buffer;
string proxy = null;
上面大多数变量的目的从其命名一目了然,只有 viewstate 是个新变量,所以我会对之作简要解释。现在我打开测试案例文件,并且一行一行地读取:
using(FileStream fs = new FileStream(args[0], FileMode.Open))
{
StreamReader tc = new StreamReader(fs);
while ((line = tc.ReadLine()) != null)
{
// parse line, post data, get response
// determine pass or fail, log result
}
}
虽然有很多可选方法来设计此自动化过程,但是 上述这个简单的结构已经在几个大型项目中被证明是健壮的。下一步是解析测试案例中数据的每个字段,并且构建一个包含“名称-值”对 的字符串。
tokens = line.Split('':'');
data.Length = 0;
data.Append("TextBox1=" + tokens[1]);
// Last name
data.Append("&TextBox2=" + tokens[2]); // Quantity
data.Append("&TextBox3=" + tokens[3]); // Credit card number
data.Append("&Button1=clicked");
data.Append("&__VIEWSTATE=" + viewstate);
我使用String.Split方法将测试 案例数据行分开,并且将每个字段保存到tokens数组中, 测试案例的ID保存到tokens[0]中,用户名称保存到tokens[1]中,物品数量保存到tokens[2]中,信用卡号保存到tokens[3]中。为了清晰起见,也可以将这些数值复制到额外的 具有描述性的字符串变量中,如:"caseID","lastName"等,如下所示:
caseID = tokens[0];
lastName = tokens[1];
// etc.
但是我想让所使用的变量数为最少,传统的Web服务器 一般都用“名称-值”对来 提交(POST)数据,多个数据之间用“&”符号分开,如下:
lastName=Smith&quantity=3&creditCardNo=123456789012
但是,ASP.NET扩展了这种做法,在这个例子中 ,有五个"名称-值"对,第一对,你可能希望是:TextBox1=tokens[1],它将当前测试 案例的用户名称(保存在tokens[