引言
测试驱动开发(TDD,Test Driven Development)的例子大多是基于非常简单的单元测试。如何在大规模的应用中使用TDD常常是一项挑战。在本教程中,我们将展示如何使用Selenium和Castle以测试先行原理来构建一个Web应用。前期准备
比方说,开发者需要应用“测试先行”的思想,为应用程序编写一种带有以下特征的方法:管理用户(添加新用户,删除,编辑用户资料,所有用户列表)
在该测试用例中,每个用户都必须拥有一个全名、一个用户名、一个密码和一个邮件地址等,所有的信息都是必须的。
基本步骤
典型的步骤如下:
- 编写测试
- 确保测试失败
- 编写代码使测试成功
- 重构
- 重复以上步骤
第一个测试
第一个要进行的测试是添加新用户的测试。测试驱动开发与其说是测试的技术不如说是设计的技术,因为当编写测试的时候,我们将规定代码或页面的工作方式,这个过程就是设计。
对于添加一个新用户而言,像下面这样简单的表单就够了:
对于功能测试,开发者需要打开添加页面(准备阶段),填写表单并保存(执行阶段)和确认用户是否被保存了(项目的确认阶段)。为了做到这一点,开发者需要更新页面,在左侧添加一个新的包含用户信息的列表,以便在点击“保存”按钮后可以验证用户存在与否。
使用Selenium
对于像这样的工作,开发者需要一个能够适合他们的执行这个行为的工具。Selenium在浏览器中可以方便地做到这一点,它也是一个很好的开源工具,可以根据你自己的需要进行修改。Selenium提供了基于Web的功能测试,而且只需要一个为开发人员运行这些行为的编译器,Selenium就能使这些测试的编写过程像HTML测试那么简单:
对于希望将自己的测试整合到一个持续集成的工具中的开发者而言,最大的喜讯莫过于,他们可以用自己喜欢的语言(如C#、JAVA、VB.NET、Ruby或者Python等)来编写测试,并利用Selenium的一个名为Selenium RC的扩展实现整合。
比如使用 Selenium RC时,该测试的.NET版如下:
第二步,保证初始的测试失败
在这一阶段开发者没有写任何代码,因此测试失败。 首先启动Selenium RC服务器(一个处理Selenium指令并将它们传送给浏览器的小型java服务器):
>java -jar selenium-server.jar
意料之中,运行的测试失败:
这是一个好现象,因为这意味着测试在应该失败时失败了。否则这次测试就没有起到任何作用,是毫无意义的。
第三步,编写代码
在TDD实施步骤的第三步中,开发者需要编写代码。这意味着当反向测试时,代码应该不会运行失败。下一步建立User控制器,然后建立视图并运行测试:
接着再建立一个空的add.vm,并且重新运行该测试:
Selenium.SeleniumException: ERROR: Element link=Add new user not found
at Selenium.HttpCommandProcessor.DoCommand(String command, String[] args)
at Selenium.DefaultSelenium.Click(String locator)
at MRProjectTest.Functionals.Selenium.ManageUsersTests.TestAddNewUser() in
ManageUsersTests.cs:line 34
因为错误报告说在网页上找不到内容,所以我们就把内容添加到add.vm中:
重新测试:
错误再次出现,因为将表单的内容提交到create.aspx之后,点击页面按钮的行为尚未实现。
然后添加如下代码以保存数据:
现在我们稍等片刻,因为无论list 行为还是数据库中都不存在User类。
在表示层下对其它层进行TDD
为了构造代码,开发者需要根据“测试先行”构造User类。 虽然在有的情况下这没有必要,因为ActiveRecord已经很好地被测试过了,并且它也通过了功能测试。 但仍需指明应该如何处理一些更为复杂的情况。
接下来的测试不是功能测试,而是集成测试,一个利用数据库的单元测试:
测试它会不会失败。其实它甚至没有进行编译,所以首先是构造一个没有任何方法的User类,强制编译其代码:
现在,运行测试:
Castle.ActiveRecord.Framework.ActiveRecordException: An ActiveRecord class (UserManagement.Model.User) was used but the framework seems not properly initialized. Did you forget about ActiveRecordStarter.Initialize() ?
at Castle.ActiveRecord.ActiveRecordBase.EnsureInitialized(Type type)
at Castle.ActiveRecord.ActiveRecordBase.Save(Object instance)
at Castle.ActiveRecord.ActiveRecordBase.Save()
at MRProjectTest.Database.UsersDataAccessTests.TestSaveNewUser()
in UserDataAccessTest.cs:line 23
错误表明User类没有在ActiveRecord中初始化,因此对测试调整如下:
为ActiveRecord与构造器加上适当的属性,并且重新运行测试。现在相应的数据库表还没有,但这可以在测试中添加如下代码迅速进行补救:
ActiveRecordStarter.CreateSchema();//创建数据库schema
运行完测试后,数据库表就生成了,但仍有一个问题:
System.NotImplementedException: todo
at UserManagement.Model.User.Find(Int64 id) in User.cs:line 72
at MRProjectTest.Database.UsersDataAccessTests.TestSaveNewUser() in
UserDataAccessTest.cs:line 41
完成对User类Find()方法的实现:
public static User Find(long id)
{
return (User) FindByPrimaryKey(typeof(User),id,false)
}
终于,一个数据库测试能够运行了!