II 任务B:验证和单元测试
一、迭代B1:验证
到迭代A1止,new product时,如果输入无效价格或者没有输入商品描述,都会把商品添加到数据库中。所以现在需要添加验证。
验证代码应该放到哪里呢?模型层!无论是从数据库中读出,还是写到数据库中,都会先通过模型层。在模型层验证,不用管数据直接来自表单,还是经过程序处理的。
现在模型类的源代码(/app/models/product.rb):
class Product < ActiveRecord::Base
end
注:在mac下:ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin11.4.0],rails3.2.13 环境。还会生成这样一行:
attr_accessible :description, :image_url, :price, :title
先验证非空的文本字段:
validates :title, :description, :image_url, :presence => true
这里,validates是标准的rails验证器。它会根据一个或者多个条件来验证一个或多个模型字段。
:presence=>true是要验证每个字段都存在,并且内容不为空。也可以写成presence: true,现在这两种写法是一样的意思。按个人习惯保持统一就可以。
现在来看一下效果。点new product进入http://localhost:3000/products/new。不输入任何内容,直接点Create Product按钮,会看到一片红色高亮显示,表示修改生效了。用一行代码就搞定这些了,另外还会注意到,不需要重启程序来测试修改,只要重新加载网页就行。
再来验证一下价格为有效正数:
validates :price, :numericality => {:greater_than_or_equal_to => 0.01}
:numericality验证是否是一个有效的数字;另外还给:greater_than_or_equal_to选项(忒TM长了吧- -)传递一个值0.01(用0.01,不用0;原因是可能输入0.001,而我们价格在数据库中只允许到小数点后两位,结果保存到数据库的数据会是0而不是0.001)。
现在又要确保每个商品有唯一的标题。来添加一个唯一性验证:
validates :title, :uniqueness => true
还要验证图片是否有效:
validates :image_url, :format => {
:with=> %r{\.(gif|jpg|png)$}i,
:message => 'must be a URL for GIF, JPG, or PNG image.'
}
用format选项来实现这个功能。format可以判断字段是否和正则表达式相匹配,这里只检查.gif,.jpg,.png。事实上,我们可能要改变表单,让用户浏览一个可用图片,不过这种情况还是要验证。
现在添加的验证有以下几项内容:
·字段的标题、描述以及图片的URl不是空的
·价格是一个有效的数字,且不少于0.01
·标题在所有的端口中是唯一的。
·图片的URL看起来要是有效的
现在模型的代码如下:
class Product < ActiveRecord::Base
validates :title, :description, :image_url, :presence => true
validates :price, :numericality => {:greater_than_or_equal_to => 0.01}
validates :title, :uniqueness => true
validates :image_url, :format => {
:with => %r{\.(gif|jpg|png)$}i,
:message => 'must be a URL for GIF, JPG, or PNG image.'
}
end
继续之前再测试一下:
rake test
oh~有两个地方报错了,我们发现一个是在test_should_create_product中,另一个在test_should_update_product中。这就是我们添加的验证导致的,也正是验证的目的,现在测试的创建和更新商品失败了。
E:\works\ruby\depot>rake test SECURITY WARNING: No secret option provided to Rack::Session::Cookie. This poses a security threat. It is strongly recommended that you provide a secret to prevent exploits that may be possible from crafted cookies. This will not be supported in future versions of Rack, and future versions will even invalidate your existing user cookies. Called from: D:/dev/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/gems/ac tionpack-3.2.1/lib/action_dispatch/middleware/session/abstract_store.rb:28:in `i nitialize'. Rack::File headers parameter replaces cache_control after Rack 1.5. Run options: # Running tests: Finished tests in 0.000000s, NaN tests/s, NaN assertions/s. 0 tests, 0 assertions, 0 failures, 0 errors, 0 skips SECURITY WARNING: No secret option provided to Rack::Session::Cookie. This poses a security threat. It is strongly recommended that you provide a secret to prevent exploits that may be possible from crafted cookies. This will not be supported in future versions of Rack, and future versions will even invalidate your existing user cookies. Called from: D:/dev/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/gems/ac tionpack-3.2.1/lib/action_dispatch/middleware/session/abstract_store.rb:28:in `i nitialize'. Rack::File headers parameter replaces cache_control after Rack 1.5. Run options: # Running tests: F.....F Finished tests in 0.796875s, 8.7843 tests/s, 11.2941 assertions/s. 1) Failure: test_should_create_product(ProductsControllerTest) [E:/works/ruby/depot/test/fun ctional/products_controller_test.rb:20]: "Product.count" didn't change by 1. <3> expected but was <2>. 2) Failure: test_should_update_product(ProductsControllerTest) [E:/works/ruby/depot/test/fun ctional/products_controller_test.rb:39]: Expected response to be a <:redirect>, but was <200> 7 tests, 9 assertions, 2 failures, 0 errors, 0 skips
解决办法是在/test/functional/products_controller_test.rb中给出有效的测试数据(下的源代码中这个文件在test/controllers/目录下),修改后的代码:
require 'test_helper'
class ProductsControllerTest < ActionController::TestCase
setup do
@product = products(:one)
@update = {
title: 'Lorem Ipsum',
description: 'Wibbles are fun!',
image_url: 'lorem.jpg',
price: 19.95
}
end
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:products)
end
test "should get new" do
get :new
assert_response :success
end
test "should create product" do
assert_difference('Product.count') do
post :create, product: @update
end
assert_redirected_to product_path(assigns(:product))
end
test "should show product" do
get :show, id: @product
assert_response :success
end
test "should get edit" do
get :edit, id: @product
assert_response :success
end
test "should update product" do
put :update, id: @product, product: @update
assert_redirected_to product_path(assigns(:product))
end
test "should destroy product" do
assert_difference('Product.count', -1) do
delete :destroy, id: @product
end
assert_redirected_to products_path
end
end
修改后,运行测试,正常。说明我们的修改没有破坏什么。但是我们要确保的是不仅现在能工作,而且进一步修改后也能正常。以后我们需要功能测试,现在我们做下单元测试。