`

rails测试中遇到的一些问题

阅读更多
1,页面的测试。
假设某view中有一个表单,表单里有3个字段,《The Rspec Book》中的做法是为这3个字段各写一个example:
it 'renders a form to create product'

it 'renders a text field for product name'

it 'renders a text field for product price'

it 'renders a text field for product sku'

而我认为这样写就足够了:
it 'renders a form to create product' do
  render
  response.should have_selector('form', :action => 'xxaction', :method => 'post') do |form|
    form.should have_selector('input[type=text]', :name => 'product[name]')
    form.should have_selector('input[type=text]', :name => 'product[price]')
    form.should have_selector('input[type=text]', :name => 'product[sku]')
  end
end

我不明白前一种写法的好处。书上为什么要这么写?

2,多层链式调用的setup问题:
虽然我极力避免2层及2层以上的链式调用,但有时候似乎不得不用,例如:
current_store.products.build params[:product] #store has many products

这种情况下controller测试进行之前的setup很麻烦:

describe Mystore::ProductsController, 'POST create' do
  before do
    @product = mock_model(Product).as_null_object
    @products = [stub!(:products)]

    @current_store = stub!('current_store')
    @current_store.stub!(:products => @products)
    controller.stub!(:current_store => @current_store)
    @products.stub!('build' => @product)
  end

  it 'creates product with params' do
    @products.should_receive(:build).with('name' => 'the rspec book')
    post 'create', :product => {:name => 'the rspec book'}
  end

#....

对应要测的controller:
  def create
    @product = current_store.products.build params[:product]
#...

这种问题有什么办法解决?

======================================================================

关于问题2我认真的想了一会,其实rails提供的那种方法本身就破坏了封装,直接把products这么一个集合暴露出来,我们关心的只是往store里添加一个product,并不关心store内部是个什么结构。

我觉得可以给store加上build_product和create_product方法,虽然这两个方法是在has_one和belongs_to的关系中自动生成的,但这里store和product是has_many关系,并没有这两个方法。一开始我想给它们起名叫做new_product和add_product,但是这样似乎理解起来更费劲,不如就按照rails的规则叫build_product和create_product得了。

修改后的代码如下:

describe Mystore::ProductsController, 'POST create' do
  before do
    @current_store = mock_model(Store).as_null_object
    controller.stub!(:current_store => @current_store)

    @product = mock_model(Product).as_null_object
    @current_store.stub!(:build_product => @product)
  end

  it 'creates product with params' do
    @current_store.should_receive(:build_product).with('name' => 'the rspec book')
    post 'create', :product => {:name => 'the rspec book'}
  end
#...


  def create
    @product = current_store.build_product params[:product]
#...


这里@current_store的setup过程又可以提出来,放在一个叫login_as_seller的macros里面,以后只要在需要的地方调用一下login_as_seller即可:
class Store;end
class User;end
module ControllerMacros
  def login_as_seller
    @current_user = mock_model(User).as_null_object
    controller.stub!(:current_user => @current_user)

    @current_store = mock_model(Store).as_null_object
    controller.stub!(:current_store => @current_store)
  end
end

describe 'as a seller' do
  before do
    login_as_seller
  end
  describe Mystore::ProductsController, 'POST create' do
    before do
      @product = mock_model(Product).as_null_object
      @current_store.stub!(:build_product => @product)
    end

    it 'creates product with params' do
      @current_store.should_receive(:build_product).with('name' => 'the rspec book')
      post 'create', :product => {:name => 'the rspec book'}
    end
  #...


当然,为了以上代码能够正常工作,我们需要为Store模型添加build_product的测试,并实现之。

=====================================================================

2010-9-3 19:47:56 我: hi

2010-9-3 19:49:37 Kadvin hi

2010-9-3 19:49:53 我: 呵呵,有空不?想请教点问题

2010-9-3 19:50:01 Kadvin 有

2010-9-3 19:51:03 我: controller中出现这样的代码好还是不
好?current_store.products.build params[:product]

2010-9-3 19:51:41 Kadvin 可以接受

2010-9-3 19:51:48 我: 也就是说有点问题?

2010-9-3 19:52:10 Kadvin 把逻辑往模型里面放,也不要走得太过,什么都往模型里面放,也会导致模型臃肿。
2010-9-3 19:53:03 Kadvin 是有一点点的问题,就是一般写得好的编码,调用层次不应该超过3层 A类里面 b.c.d.e 这样调用,就基本上说明对b的封装不足。

2010-9-3 19:53:12 我: 嗯,可是这里出现了2层的链式调用,暴露了products,写测试的时候setup好像有些麻烦。
2010-9-3 19:53:16 我: 是呀,我也是这么觉得。

2010-9-3 19:53:51 Kadvin 但你完全追求两层调用,这会导致你系统过度设计。
2010-9-3 19:54:18 Kadvin 过度设计(Over Design),这是很多初搞软件的人犯的错误。
2010-9-3 19:54:32 Kadvin 所以,你这里这个代码,是可以接受的。
2010-9-3 19:55:00 Kadvin 因为Store -has_many-> products,你是通过ActiveRecord的DSL建立的一个很简便的关系。

2010-9-3 19:54:38 我: 可是测试变得难写了咋办?

2010-9-3 19:55:34 Kadvin 你测试用例的Fixture里面,应该做好准备啊。
2010-9-3 19:55:40 Kadvin 我没觉得数据困难了多少

2010-9-3 19:56:39 我: 咦,你写controller测试需要用到fixture吗?
2010-9-3 19:57:27 我: 我用的是stub和mock
2010-9-3 19:58:00 我: fixture我们现在用factory_girl来替代,也只是在测model的时候用到。

2010-9-3 19:58:38 Kadvin 嗯,一般是Stub就够了
2010-9-3 19:58:51 Kadvin 我用DataSet
2010-9-3 19:58:55 Kadvin 做模型的Fixture

2010-9-3 20:02:27 我: 哦。这是我今天记的一点东西,其中第2个问题就是现在咱们说的这个问题:http://yuan.iteye.com/blog/754367 在后面我自己给自己回复,试着给出一个解决办法,用这个办法看起来before那块代码简单得多了,但是model里要多一个方法。那如果给每个 has_many关系添加这么一个方法,好像就会有许多重复代码。

2010-9-3 20:00:40 Kadvin 你这个地方
stub(:current_store) do
  Store.first 
end
给一个实际的store对象
2010-9-3 20:00:54 Kadvin 那build自然就没问题了嘛~

2010-9-3 20:03:07 我: 这样啊,我一直以为controller的测试代码里出现真实的model不好。

2010-9-3 20:04:46 Kadvin 如果你的模型是经过测试的,那没啥不好的。尤其是,如果你用的模型的方法是Rails自身的(说明经过测试)
2010-9-3 20:04:49 Kadvin 不能太僵化

2010-9-3 20:05:39 我: 我想想

2010-9-3 20:06:17 Kadvin 我看了下你的文章,觉得你完全弃用AR做控制器测试的思路太呆板了。
2010-9-3 20:07:02 Kadvin 一般而言,控制器的测试是基于模型的测试,适当引入一些预制的模型,会大大降低控制器的setup工作。

2010-9-3 20:13:34 我: 那问题1呢?

2010-9-3 20:14:19 Kadvin 你的观点正确,我也是这么搞的,一下子验证多个。

2010-9-3 20:14:26 我:

2010-9-3 20:16:00 Kadvin 关键是,找你信得过的东西来做stub

2010-9-3 20:16:27 我: 信得过的是指经过测试的或者Rails本身提供的api是吧?

2010-9-3 20:16:33 Kadvin yes

=====================================================================

RSpec官方文档 写道
We strongly recommend that you use RSpec’s mocking/stubbing frameworkto intercept class level calls like :find, :create andeven :new to introduce mock instances instead of real active_record instances.

This allows you to focus your specs on the things that the controller does and notworry about complex validations and relationships that should be described indetail in the Model Examples

http://rspec.info/rails/writing/controllers.html

=====================================================================

Stub Chain
Let’s say we’re building an educational website with Ruby on Rails, and we need a database query that finds all of the published articles written in the last week by a particular author. Using a custom DSL built on ActiveRecord named scopes, we can express that query like so:
 Article.recent.published.authored_by(params[:author_id])

Now let’s say that we want to stub the return value of authored_by( ) for an example. Using standard stubbing, we might come up with something like this:
recent= double()
published= double()
authored_by = double()
article= double()
Article.stub(:recent).and_return(recent)
recent.stub(:published).and_return(published)
published.stub(:authored_by).and_return(article)

That’s a lot of stubs! Instead of revealing intent it does a great job of hiding it. It’s complex, confusing, and if we should ever decide to change any part of the chain we’re in for some pain changing this. For these reasons, many people simply avoid writing stubs when they’d otherwise want to. Those people don’t know about RSpec’s stub_chain( ) method, which allows us to write this:
article = double()
Article.stub_chain(:recent, :published, :authored_by).and_return(article)

Much nicer! Now this is still quite coupled to the implementation, but it’s also quite a bit easier to see what’s going on and map this to any changes we might make in the implementation.

最后的结果是:我承认在current_store.products.build这个地方追求1层的方法调用有点过了。但我仍然坚持在controller里使用mock/stub,不碰fixture/factory等model相关的东西。stub_chain完美解决以上问题。并且,对属性的链式访问,我仍然会继续追求1层的方法调用——如果你采用自顶向下的开发方式,会发现这是自然而然的事情。至于rails的current_store.products.build/create这些方法,这属于rails实现的问题,其实rails也可以实现成current_store.build_product,不是吗?
分享到:
评论
3 楼 yuan 2011-05-10  
关于第1个问题,我猜测作者是为了遵循one assertion per test。
但这里有个问题是,以下代码也可以通过作者所写的测试:

<form action='xxaction' method='post'></form>
<form action='yyaction' method='get'>
<input type='text' name='product[name]'/>
<input type='text' name='product[price]'/>
<input type='text' name='product[sku]'/>
</form>

但显然以上代码是不符合需求的。如果为每个字段写一个example,并在每个example中指定action、method,重复代码就太多了。所以,在这个地方,目前我仍然坚持用后面那种写法。

关于one assertion per test: http://www.artima.com/weblogs/viewpost.jsp?thread=35578
2 楼 夜鸣猪 2010-09-25  
这个好

值得研究
1 楼 yuan 2010-09-19  
这可能是rspec-rails的一只bug:
controller如下:
  def index
#.....
      render 'index', :layout => !request.xhr?
#......
  end

相应的测试代码如下:
controller.should_receive(:render).with 'index', :layout => false


无论如何测试都通不过:
Mock "render_proxy" expected :render with ("index", {:layout=>false}) once, but received it 0 times

但这样的代码却没问题:
  def index
#.....
      render :partial => 'categories', :layout => !request.xhr?
#......
  end

controller.should_receive(:render).with(:partial => 'categories', :layout => false)


或者这样也没问题:
  def index
#.....
      render 'index'
#......
  end

controller.should_receive(:render).with('index')

难道rspec-rails只认第一个参数?

相关推荐

    Rails的中文乱码问题

    标题中的“Rails的中文乱码问题”涉及到的是在使用Ruby on Rails框架开发Web应用时,遇到的中文字符编码不正确的问题。Rails是一个基于Ruby语言的开源Web开发框架,它遵循MVC(Model-View-Controller)架构模式。在...

    Ruby-APITaster一种快速而简单的方法来可视化测试你的Rails应用API

    在集成过程中,你可能会遇到的问题包括版本兼容性、权限设置、路由冲突等,解决这些问题通常需要查阅官方文档或社区支持。同时,记得定期更新API Taster,以获取最新的功能和安全补丁。 总之,Ruby-APITaster是...

    Rails recipes

    在Rails Recipes中,作者们不仅关注技术层面的问题,还考虑到了程序员在实际开发过程中可能遇到的理论和思想上的困惑。他们试图通过书中的内容,让读者在解决问题的同时,能够获得更深层次的理解,这样的学习方式...

    Ruby on Rails入门经典代码

    - Stack Overflow:遇到问题时,可以在这里寻求帮助。 - RailsGuides:官方文档,详尽介绍Rails的各个方面。 通过学习和实践压缩包中的"Ruby on Rails入门经典代码",新手不仅可以了解Rails的基本概念,还能掌握...

    rails cookbook

    《Rails Cookbook》是一本专注于Ruby on Rails框架的实用指南,旨在帮助开发者解决在开发过程中遇到的具体问题和挑战。Rails作为一款强大的Web应用框架,以其MVC(模型-视图-控制器)架构和“约定优于配置”的设计...

    Ruby on Rails实例开发

    它可能涵盖了技术选择的原因、遇到的问题及解决方案、性能优化、用户体验设计等方面,是整个开发工作的总结和理论支持。 总的来说,Ruby on Rails实例开发涉及到一系列技术,包括Ruby编程、Rails框架的使用、SQLite...

    rails 项目起步示例

    在购物系统(shopping_system)这个项目中,你可能会遇到以下概念和技术: - **ActiveRecord**:Rails内置的ORM(对象关系映射)工具,负责与数据库的交互。 - **Routes**:Rails的路由系统将URL映射到控制器的...

    Ruby on Rails入门例子

    在"Ruby on Rails入门例子"中,我们可能会遇到以下关键概念: - **路由(Routes)**:Rails的路由系统将URL映射到特定的控制器动作,定义了应用的导航结构。在`config/routes.rb`文件中配置路由规则。 - **生成器...

    配置rails环境

    4. **解决Gem依赖问题**:在上述描述中遇到的问题是缺少`sqlite3`库,这是Rails与SQLite3数据库交互所必需的。在Windows环境下,安装SQLite3的Ruby绑定时可能会遇到编译错误。解决方法是下载预先编译好的版本,或者...

    rails 的安装

    - 如果在安装过程中遇到权限问题,尝试使用 `sudo` 命令或者改变Gem的安装位置。 - 如果有Gem冲突,可能需要更新或降级特定的Gem版本。 - 确保你的系统满足Rails和Ruby的最低要求,以及任何特定Gem的系统依赖。 ...

    Rails上的API:使用Rails构建REST APIAPIs on Rails: Building REST APIs with Rails

    3. **社区活跃**:Rails拥有庞大的开发者社区,提供了丰富的插件和教程资源,遇到问题时可以迅速获得帮助。 4. **安全性**:Rails内置了一系列安全措施,比如防止SQL注入、XSS攻击等,有助于保护应用免受常见威胁。 ...

    windows下配置netbeans开发rails

    在Windows环境下,使用NetBeans进行Rails...在实际开发中,你可能还会遇到如权限问题、编码问题、兼容性问题等,都需要逐一解决。不断学习和实践,掌握Rails和NetBeans的使用技巧,将有助于提高开发效率和代码质量。

    重构你的Rails程式码

    2. **升级行为的变化:** 升级过程中可能会遇到一些新特性或变更,这些变化可能需要通过重构代码来适应新的行为。例如,某些旧的功能可能已经不再适用,或者有更好的方式实现。 3. **利用新功能实现旧功能:** 升级...

    Rails.Recipes.Rails.3rd和源码

    这本书通过一系列的“配方”(recipes),为开发者提供了在实际开发中可能会遇到的问题及其解决方案,旨在帮助开发者提高工作效率,解决Rails应用中的各种挑战。 在Rails 3版中,框架进行了大量的更新和改进,包括...

    Ruby On Rails开发实例-源代码

    实践是掌握任何技术的关键,因此,建议你在阅读文档后,动手运行这个实例,跟随代码一步步操作,遇到问题时查阅官方文档或社区资源,这样能更好地理解Ruby on Rails的精髓。祝你学习愉快,早日成为Rails开发高手!

    NetBeans Ruby and Rails IDE with JRuby 2009

    - **解决Gems仓库权限问题**:有时可能会遇到Gems安装失败的问题,这通常与权限设置有关。 - **从命令行使用Gem工具**:除了在IDE内操作外,还可以直接通过命令行来管理Gems。 - **安装具有本地扩展的Gems**:某些...

    JRuby和Rails-让Ruby语言融入于Java项目.rar

    6. **案例研究**:提供实际的项目案例,展示如何在Java企业级环境中成功实施JRuby和Rails,解决可能遇到的问题和挑战。 7. **社区和工具**:列出相关的开源库、插件和工具,以及JRuby和Rails社区资源,帮助开发者...

    rails的经典学习项目

    而"cookbook.rar"可能是一个Rails的实践手册,里面包含了各种常见问题的解决方案和最佳实践,帮助开发者解决在实际开发中遇到的问题。 在学习Rails的过程中,开发者会接触到许多关键概念和技术,如ActiveRecord...

    Crafting Rails 4 Applications

    此外,书中尚未完成布局工作,因此可能会遇到糟糕的页面断行、过长的代码行等问题。 - **反馈机制**:作者鼓励读者通过指定链接提供反馈,以便在后续版本中改进内容。这种互动方式有助于提升书籍质量,并确保最终...

    Rails Recipes

    《Rails Recipes》是一本专注于Ruby on Rails开发实践的书籍,旨在提供一系列针对不同问题和场景的解决方案,犹如烹饪中的各种食谱。Rails是基于Ruby语言的流行Web应用框架,它以其DRY(Don't Repeat Yourself)原则...

Global site tag (gtag.js) - Google Analytics