刚刚以SCRUM的方式结束了一个的ASP.NET网站的测试的第一个Spring,因为团队从无到有实现自动化测试系统,有必要把这次的经验和教训总结一下,以便后续的Spring可以获取一些有意义的借鉴。
因为是一个技术博客,所以使用SCRUM管理这个测试项目的经验放在别的地方分享,这系列的文章分享一下使用VSTT和Selenium结合实现自动化测试系统的经验。
Selenium简介
Selenium主要是一个录制并回放的自动化测试用例编制工具,由一个录制工具Selenium IDE(一个Firefox插件,当然这个工具也可以回放啦),一个回放工具Selenium Remote Control在其他机器和其他操作系统上进行回放。Selenium的一个好处就是你可以使用它测试所有操作系统下的所有主流浏览器,至于Linux下面的konqueror和gnome下面自带的浏览器,没有试过Selenium是否支持,当然那个控制台界面下的浏览器就更没有试过啦。Selenium还有一个Selenium Grid,据说很强大,因为项目比较紧,就没有花时间去看它。
至于Selenium各个工具的用法,它的官网上有详细的文档,如果文档也没说清楚的话,那就直接读源代码吧。
Selenium和VSTT的整合
Selenium可以根据录制的步骤生成直接在NUnit中使用的C#代码,这些代码基本上都可以在VSTT中直接使用,就是一些属性需要更改。例如[TestFixture]改成[TestClass],[Test]改成[TestMethod]之类的,改好以后,启动Selenium-RC,就可以直接在VSTT里面当作普通的单元测试用例执行了。
Selenium代码优化
既然要做自动化测试,那么有一点是必须要时刻考虑的,就是在产品开发过程中,程序界面甚至是内部的类库接口也是时刻改变的。而Selenium只能记录当时录制测试用例的界面情况,因此需需要将它生成的代码分解一下,以面向对象的方式来重写。例如下面这段代码的目的是测试用户可以查看自己的博客:
[TestMethod]
public void TheTestTest()
{
selenium.Open("/");
selenium.Click("link=登录");
selenium.WaitForPageToLoad("30000");
selenium.Type("tbUserName", "donjuan");
selenium.Type("tbPassword", "");
selenium.Click("btnLogin");
selenium.WaitForPageToLoad("30000");
selenium.Click("link=donjuan");
selenium.WaitForPageToLoad("30000");
selenium.Click("link=博客");
selenium.WaitForPageToLoad("30000");
}
但是网页页面布局,或者Html控件的Id、文本等内容随时都会被程序员修改,修改的原因有多种,例如修复新的错误(Bug),或者仅仅就是代码重构。因此作为测试团队,不能总是认为网页的内容一成不变的。而象登录这种操作,大部分测试用例都会用到,所以最好只要为登录动作创建唯一的代码 。有多个方案:
1. 为登录创建一个独立的测试用例,本来登录这个功能就是要测试的嘛,在编辑自动化测试用例列表的时候,把登录用例放在最前面。
2. 为登录动作创建一个单独的函数,例如LogOn(),然后在其他测试用例当中(包括登录的测试用例)调用这个函数,另外,因为可能会需要用到不同的 用户,所以最好把用户名和密码等变量提取出来,变成LogOn(string username, string password)之类的函数。
两个方案,显然是第二个方案的弹性大,但是对于第一个方案,如果测试人员都是新手,且对代码不熟悉的话,建议可以考虑。
于是我们的代码就变成类似下面的代码:
using System;
//
// 这个异常是故意创建出来,用来封装所有在测试代码中发生的错误
//
public class CaseErrorException : Exception
{
public CaseErrorException(string message)
: base()
{
}
public CaseErrorException(Exception inner)
: this(null, inner)
{
}
public CaseErrorException(string message, Exception inner)
: base(message == null ? "测试代码错误,请修复测试代码,查看InnerException属性!" :
string.Format("测试代码错误,请修复测试代码,详细错误信息:{0};或者查看InnerException属性!", message),
inner)
{
}
}
public class UserOperationsHelper
{
public void LogOn(string username, string password)
{
// string.Empty留出来为测试目的服务
if (username == null)
throw new CaseErrorException(new ArgumentNullException("username"));
if (password == null)
throw new CaseErrorException(new ArgumentNullException("password"));
selenium.Open("/");
selenium.Click("link=登录");
selenium.WaitForPageToLoad(Consts.TimeToWaitForPageLoad);
selenium.Type("tbUserName", username);
selenium.Type("tbPassword", password);
selenium.Click("btnLogin");
selenium.WaitForPageToLoad(Consts.TimeToWaitForPageLoad);
}
}
public static class Consts
{
// 将等待的时间提取成一个公开的函数,因为在今后大规模的测试
// 过程中,很多自动化测试用例不简单地执行,会导致网站响应速度