论坛首页 编程语言技术论坛

如何为Rails的views写测试。

浏览 4764 次
该帖已经被评为良好帖
作者 正文
   发表时间:2009-10-01   最后修改:2010-09-06
原文链接:http://weblog.jamisbuck.org/2007/1/29/testing-your-views

简单的说,就是别用断言来测试你的页面结构。
也就是说,别这么做:

#输入框必须在一个table的单元格里
assert_select "table td input[type=text]"
#person.name必须在h2标签里
assert_select "h2", person.name


为什么呢?因为你会希望页面(的结构)非常的容易改变(be very fluid)。你会希望改动页面的成本很低以便于你可以毫不犹豫地投入到页面的重新布局,从而使页面变得更干净。如果你在测试代码里使用的是明确的标签名,那你的页面结构将会变得十分僵硬。因为你的测试代码暗示着使用table以外的标签来排版你的表单是错误的。想用h1或者div来包装person.name吗?你不敢,这样会使测试失败。

一个更好的方式是:思考一下你真正想要测试的是什么。

首先,别测试静态的内容,比如table的结构、表单域(form fields)的顺序。相反的,要测试的是页面上动态的部分,特别是受条件支配渲染的部分(especially those parts that are subject to conditional rendering. )。

第二,测试语义,而不是测试语法(test semantically, not syntactically. )。也就是说,别基于标签的类型写测试,而是基于你要展现的内容。用CSS的class属性或者DOM的id来代替明确的标签名。

这里有个实例,假设有个这样的页面:
<% if @user.administrator? %>
  Hi <%= @user.name %>! You appear to be an administrator.
  <%= link_to "Click here", admin_url, :id => "admin_link" %>
  to see the admin stuff!
<% end %>

这里唯一真正有必要测试的是:admin的链接只能让管理员(administrators)看见。也许你会想测试链接是否指向你期望的地方,但那是次要的。

def test_admin_sees_link
  # 为管理员set up一个session,然后:
  get "index"
  assert_select "#admin_link"
end

def test_non_admin_does_not_see_link
  # 先为非管理员用户set up一个session,然后:
  get "index"
  assert_select "#admin_link", false
end


像这样安排你的测试会让测试代码更少的干扰到你为页面的美化而进行的调整。而且这会增强你对测试的信心,同时你也会很乐意去调整界面。

相关链接:Spec your views
   发表时间:2009-10-06  
我个人以为写这种views的测试实在是得不偿失.
做的功夫多,但是用处不大.一次都没写过.
0 请登录后投票
   发表时间:2009-10-06  
页面上的测试,就只能考人工测试了,机器没法取代的
0 请登录后投票
   发表时间:2009-10-08  
呵呵,好文啊
最近也在学习写测试代码呢。

ps:yuan同学,你很勤奋嘛,假期里还学习,发文章
0 请登录后投票
   发表时间:2009-10-10  
圆圆英文不错啊!
0 请登录后投票
   发表时间:2009-10-10  
我喜欢TA的头像
0 请登录后投票
   发表时间:2009-10-11   最后修改:2009-10-11
测试view里元素是否存在,在Cucumber时代很简单很常见的场景,一句:
I should see "Click here"

很自然很语义

甚至可以顺便测试连接可访问性(如权限限制)

When I follow "Click here"
Then I should be on the admin page

这几句都是Cucumber默认生成的steps
3 请登录后投票
   发表时间:2010-03-13   最后修改:2010-09-25
我觉得给页面写测试是必要的,而且是一开始要做的第一件事(也可能是从route测试开始)。
需求细化之后,可能最终会变成这样:

引用
访问 GET /admin/articles/new链接
我希望能返回一个页面,这个页面上要有一个表单,里面有一个textfield和一个textarea,根据Rails的约定,这个textfield的name必须是article[title],textarea的name必须是article[body]。表单的action值必须是'/admin/articles',method值必须是post


再进一步(写伪代码吧):

example1:
向/admin/articles/new发送GET请求
我希望rails会去调用Admin::ArticlesController#new方法


然后开始写路由,我觉得没有必要像TDD那本书上那样傻傻的写一个直接让测试通过的傻瓜实现。没有必要先写一个map.connect,回头再写测试,说POST请求不允许过,然后再给map.connect写个condition,等测试通过后,最后再回来重构,写成map.resources。这样真的太麻烦了,我记得那书上曾经说过,这么小步的走只是告诉咱们,当有难以找到根源的bug发生时,可以通过这种方式来慢慢排除bug。我可以直接写上map.resources,反正测试==需求文档,所以测试能通过==产品符合需求。

然后如果我们不希望post请求这个url能够通过,就把这个需求也写上:
example2:
向/admin/articles/new发送POST请求
我希望rails会抛出一个routing error


OK,路由的测试足够了,其它的情况我觉得可以等bug来驱动你写测试。(至少目前我不觉得这样的路由规则会有bug。另外我认为bug==需求,bug是在它发生之前没有被考虑到的需求。)

写好路由并通过测试同时代码看起来很干净之后,接着
example3:
渲染模板/admin/articles/new
我希望渲染的结果中包括一个表单 do |表单|
  表单的action值是'/admin/articles'
  表单的method值是post
  表单.包含一个名为article[title]的textfield
  表单.包含一个名为article[body]的textarea
end


ok,现在这个页面的测试写好了。当然,还有细节上的东西要处理,比如该mock的mock,该stub的stub,让我们把注意力只集中在views上。等view写好了,测试通过了,可以开始接下来的controller测试。驱动咱们给controller写测试的原因应该是集成测试工具Cucumber告诉咱们:Red alert,任务还没有完成。(我觉得mock1234同学说“凡是考虑测试驱动必从突出集成测试这个概念入手”挺有道理的。)



我认为给页面写测试的一个原则就是:至少保证页面可以正常工作(这个也是给页面写测试的理由),不用多考虑页面的外观、结构。

最后我觉得这样自顶向下的过程是完全从需求出发,很自然。我不是很明白为什么有人说TDD/BDD必须是自底向上的(不是在本帖中说的)。如果真的是我理解的不对,我希望坚持必须自底向上的同学可以说明一下自底向上的理由。

呃,写些自己的想法,不知道看起来乱不乱。
=====================2010年9月25==================
回头再看主帖跟这个回复,似乎挺矛盾的。
0 请登录后投票
   发表时间:2010-04-07  
yuan同学对测试非常有研究啊。。。

0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics