二、迭代B2:模型的单元测试
rails在每个项目创建时,就生成了一个基本的测试环境,在/test/unit下。
E:\works\ruby\depot\test\unit>dir 驱动器 E 中的卷是 work 卷的序列号是 F4C3-30B8 E:\works\ruby\depot\test\unit 的目录 2013-03-14 14:45 <DIR> . 2013-03-14 14:45 <DIR> .. 2013-03-14 14:33 0 .gitkeep 2013-03-14 14:45 <DIR> helpers 2013-03-14 14:45 121 product_test.rb 2 个文件 121 字节 3 个目录 21,651,755,008 可用字节
这里的product_test.rb文件就是用来保存我们生成的模型单元测试的。
require 'test_helper' class ProductTest < ActiveSupport::TestCase # test "the truth" do # assert true # end end
ProductTest是ActiveSupport::TestCase的子类,而ActiveSupport::TestCase又是Test::Unit::TestCase的子类,这里说明rails生成的测试是基于Test::Unit框架的,这个框架来自Ruby。
1.真正的单元测试
首先,如果创建一个没有属性集的商品我们期待它是无效的,而且会显示与每个字段关联的错误信息。那么可以使用该模型的errors和invalid?方法,另外可以使用错误清单的any?方法来查看是否有和特定的属性相关联的错误。
可以通过断言(accertions)来告诉测试框架该代码是否通过了测试。断言是一个简单的方法调用,它告诉框架什么才是所期望的真。最简单的断言方法是assert这个方法预期其参数为真。如果参数为真,那么就不会发生什么;如果参数为假,则断言失败,该框架将输出一条消息,并停止执行包含错误的测试方法。
我们预计一个空的Product模型无法通过验证,用assert product.invalid?。修改测试代码为/test/unit/product_test.rb(下载的源代码中此文件在test/unit/models/下):
require 'test_helper' class ProductTest < ActiveSupport::TestCase test "product attributes must not be empty" do product = Product.new assert product.invalid? assert product.errors[:title].any? assert product.errors[:description].any? assert product.errors[:price].any? assert product.errors[:image_url].any? end end
可以通过rake test:units来运行单元测试:
rake test:units
E:\works\ruby\depot>rake test:units 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/actionpack-3.2.1/lib/action_dispatch/middleware/session/abstract_store.rb:28:in `initialize'. Rack::File headers parameter replaces cache_control after Rack 1.5. Run options: # Running tests: . Finished tests in 0.390625s, 2.5600 tests/s, 12.8000 assertions/s. 1 tests, 5 assertions, 0 failures, 0 errors, 0 skips
再深入点来验证价格,在/test/unit/product_test.rb添加一个测试方法:
test "product price must be positive" do product = Product.new(title: "My Book Title", description: "yyy", image_url: "zzz.jpg") product.price = -1 assert product.invalid? assert_equal ["must be greater than or equal to 0.01"], product.errors[:price] product.price = 0 assert product.invalid? assert_equal ["must be greater than or equal to 0.01"], product.errors[:price] product.price = 1 assert product.valid? end
把这个测试方法的三种测试分为三个独立的方法,也是合理的。
rake test:units
E:\works\ruby\depot>rake test:units 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.343750s, 5.8182 tests/s, 29.0909 assertions/s. 2 tests, 10 assertions, 0 failures, 0 errors, 0 skips
再来测试验证图片url是否以.gif,.jpg,.png结尾,/test/unit/product_test.rb:
def new_product(image_url) Product.new(title: "My Book Title", description: "yyy", price: 1, image_url: image_url) end test "image url" do ok = %w{ fred.gif fred.jpg fred.png FRED.JPG FRED.Jpg http://a.b.c/x/y/z/fred.gif } bad = %w{ fred.doc fred.gif/more fred.gif.more } ok.each do |name| assert new_product(name).valid?, "#{name} shouldn't be invalid" end bad.each do |name| assert new_product(name).invalid?, "#{name} shouldn't be valid" end end
rake test:units
... Finished tests in 0.406250s, 7.3846 tests/s, 46.7692 assertions/s. 3 tests, 19 assertions, 0 failures, 0 errors, 0 skips
最后测试商品标题是否唯一。一种方法是,创建一个商品,然后保存它,然后再创建一个商品,使用前一个商品相同的标题,也保存到数据库中,这是可行的,但是Rails的fixtures更简单。
2.静态测试
静态测试(test fixture)只是为待测试的一个或多个模型而准备的初始内容的规范。如:如果要确保products表在每一个单元测试都从书籍的数据开始,可以在一次静态测试中指定下面内容,Rails会处理剩下的事情。
可以在/test/fixtures目录内的文件中详细说明静态测试数据。这些文件包含以逗号分隔值(CSV)或YAML格式的测试数据。在测试中将首选YAML格式。每个静态测试文件单个模型的测试数据。静态测试文件的名称是很重要的;文件的基本名称必须与数据库表的名称相匹配。因为需要给Product模型一些数据,而这些数据存在products表中,因为要把它添加到名为products.yml的文件中。
rails开始已经生成了这个静态测试文件,/test/fixtures/products.yml:
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html one: title: MyString description: MyText image_url: MyString price: 9.99 two: title: MyString description: MyText image_url: MyString price: 9.99
这个静态测试文件都包含了一个要插入数据库的条目,并且每行都给出了字段名。one/two的行对数据库是没有意义的,不会插入到数据库。
在每个条目中会看到一个名称/值组合的缩进列表。就像在config/database.yml文件中,每个数据行的开始必须使用空格,而不是Tab键,并且数据库中与同一记录相关的所有行都必须有相同的缩进。在修改程序时须格外小心,因为必须保证每个条目中的列名是正确的,与数据库中的列不匹配名称可能导致难以跟踪的异常。
添加更多有用数据来测试Product模型,/test/fixtures/products.yml添加:
ruby: title: Programming Ruby 1.9 description: Ruby is the fastest growing and most exciting dynamic language out there. If you need to get working programs delivered fast, you should add Ruby to your toolbox. price: 49.50 image_url: ruby.png #END:ruby
在运行单元测试时,rails已经把这些测试数据加载到products表了;我们也可以通过在文件/test/unit/product_test.rb中指定以下行来控制加载哪些静态测试:
fixtures :products
测试文件名决定要加载的表,用:products表示使用products.yml静态测试文件。
在ProductTest使用fixtures表示,在运行每个测试方法之前,products表将清空,然后用静态测试中定义的数据来填充表。
要注意,大部分rails生成的脚手架并不包含fixtures方法。因为测试之前默认加载所有静态测试。
products方法通过加载静态测试为生成的表创建索引。需要修改这个索引来匹配在静态测试中所给出的名称。
目前为止我们一直是在开发数据库中工作。但是现在我们要进行测试,rails需要使用一个测试数据库。如果看看/config/database.yml,会发现在实际上rails为3个独立的数据库创建了配置:
db/development.sqlite3是开发数据库。所有编程工作将在这里完成。
db/test.sqlite3是一个测试数据库
db/production.sqlite3是实际产品数据库。应用程序正式上线时就使用这个数据库。
在测试数据库中每个测试方法都有一张刚刚初始化的表加载了所提供的静态测试数据。这是由命令rake test自动完成的,但也可单独运行rake db:test:prepare来初始化数据库表。
3.使用静态测试数据
如何使用这些静态测试数据呢?一种办法是使用模型中的finder方法来读取数据。但是rails中对于每一个加载到测试中的静态测试,都定义有具有和静态测试市民名称的方法。可以使用此方法来访问已经预装了的、包含了静态测试数据的模型对象:简单地yaml静态测试文件中定义的行名,它会返回包含该行数据的模型对象。这里我们可以用products(:ruby)返回我们定义的这个Product模型,这里用来验证商品名称的唯一性/test/until/product_test.rb:
test "product is not valid without a unique title" do product = Product.new(title: products(:ruby).title, description: "yyy", price: 1, image_url: "fred.gif") assert !product.save assert_equal ["has already been taken"], product.errors[:title] end
这里用一数据库中已存在的标题来创建一个新的Product模型,并断言嘎这个模型会失败,并且输出和tile属性相关的错误信息。
rake test:units
.... Finished tests in 0.375000s, 10.6667 tests/s, 56.0000 assertions/s. 4 tests, 21 assertions, 0 failures, 0 errors, 0 skips
如果想避免在Active Record错误中使用硬编码的字符串,可以将返回的消息和其内置的错误信息表进行比较来解决这个问题,用i18n函数,/test/unit/product_test.rb中使用如下测试方法:
test "product is not valid without a unique title - i18n" do product = Product.new(title: products(:ruby).title, description: "yyy", price: 1, image_url: "fred.gif") assert product.invalid? assert_equal [I18n.translate('errors.messages.taken')], product.errors[:title] end
rake test:units
E:\works\ruby\depot>rake test:units 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/actionpack-3.2.1/lib/action_dispatch/middleware/session/abstract_store.rb:28:in `initialize'. Rack::File headers parameter replaces cache_control after Rack 1.5. Run options: # Running tests: ...F. Finished tests in 0.390625s, 12.8000 tests/s, 58.8800 assertions/s. 1) Failure: test_product_is_not_valid_without_a_unique_title_-_i18n(ProductTest) [E:/works/ruby/depot/test/unit/product_test.rb:69]: <["translation missing: en.errors.messages.taken"]> expected but was <["has already been taken"]>. 5 tests, 23 assertions, 1 failures, 0 errors, 0 skips
/test/unit/product_test.rb文件最终代码:
require 'test_helper' class ProductTest < ActiveSupport::TestCase fixtures :products test "product attributes must not be empty" do product = Product.new assert product.invalid? assert product.errors[:title].any? assert product.errors[:description].any? assert product.errors[:price].any? assert product.errors[:image_url].any? end test "product price must be positive" do product = Product.new(title: "My Book Title", description: "yyy", image_url: "zzz.jpg") product.price = -1 assert product.invalid? assert_equal ["must be greater than or equal to 0.01"], product.errors[:price] product.price = 0 assert product.invalid? assert_equal ["must be greater than or equal to 0.01"], product.errors[:price] product.price = 1 assert product.valid? end def new_product(image_url) Product.new(title: "My Book Title", description: "yyy", price: 1, image_url: image_url) end test "image url" do ok = %w{ fred.gif fred.jpg fred.png FRED.JPG FRED.Jpg http://a.b.c/x/y/z/fred.gif } bad = %w{ fred.doc fred.gif/more fred.gif.more } ok.each do |name| assert new_product(name).valid?, "#{name} shouldn't be invalid" end bad.each do |name| assert new_product(name).invalid?, "#{name} shouldn't be valid" end end test "product is not valid without a unique title" do product = Product.new(title: products(:ruby).title, description: "yyy", price: 1, image_url: "fred.gif") assert !product.save assert_equal ["has already been taken"], product.errors[:title] end test "product is not valid without a unique title - i18n" do product = Product.new(title: products(:ruby).title, description: "yyy", price: 1, image_url: "fred.gif") assert product.invalid? assert_equal [I18n.translate(['activerecord.errors.messages.taken'])], product.errors[:title] end end
现在该商品现在有一个模型、一组视图、一个控制器和一组单元测试。
相关推荐
在这个全球互联的世界中,计算机编程和 Web 应用程序开发都在迅猛发展,我很期待能为中国的开发者提供 Ruby on Rails 培训。学习英语这门世界语言是很重要的,但先通过母语学习往往会更有效果。正因为这样,当看到 ...
也曾使用 Ruby on Rails 开发过一个名为 Insoshi 的社交网络平台,这个平台曾经很流行,现在已经过气了。因为他对 Ruby 社区的贡献,于 2011 年被授予了 Ruby Hero 奖。他毕业于哈佛学院,并获得了加州理工学院的...
ruby on rails最新版 这是本人精心收集的重要软件
Ruby On Rails教程,web开发
开源框架Ruby on Rails Ruby on Rails, 也称RoR或简称Rails, 是一个使用Ruby语言写的开源网络应用框架,它是...Ruby语言开始迅速上升并且受欢迎是在2004年7月David Heinemeier Hansson发布了Ruby on Rails的第一版。
web开发敏捷之道的原书Agile Web Development With Ruby On Rails第一及第二版
三个资源在一起解压缩后(windows 平台下解压缩)即可得到《Ruby.on.Rails.Web开发之旅》一书。 内容简介 本书从Ruby和Rails的历史讲起,以在Rails框架上克隆故事共享网站digg.com为例,详细讲述了Ruby和Rails的...
三个资源在一起解压缩后(windows 平台下解压缩)即可得到《Ruby.on.Rails.Web开发之旅》一书。 内容简介 本书从Ruby和Rails的历史讲起,以在Rails框架上克隆故事共享网站digg.com为例,详细讲述了Ruby和Rails的...
三个资源在一起解压缩后(windows 平台下解压缩)即可得到《Ruby.on.Rails.Web开发之旅》一书。 内容简介 本书从Ruby和Rails的历史讲起,以在Rails框架上克隆故事共享网站digg.com为例,详细讲述了Ruby和Rails的...
Ruby on Rails教程第6版Gemfiles 这些是编写的(第6版)的最新Gemfile。 如果存在任何差异,那么Ruby on Rails教程的读者应该始终优先使用这些Gemfile,而不是书中列出的Gemfile。 的Gemfile应该始终与下面显示的G...
Agile Web Development With Ruby On Rails 的第一版的修补版本,有些内容第二版未提及,如fastCGI的部署等
我的第一个Ruby on Rails应用程序:Todo-Organizer Ruby版本:ruby 2.6.3p62 Rails版本:Rails 6.1.3 如何运行这个项目? 1.使用以下命令检查本地计算机上是否安装了rbenv,node,yarn,bundler,ruby,rails ...
这是我的第一个 Ruby on Rails 项目。 重点是学习 Rails 脚手架而不是布局样式。 我在这个项目中使用了 TDD(测试驱动开发)方法。 因此,在相应的规范目录和文件中对所有内容进行了测试。 用法 要查看此项目的运行...
《Ruby on Rails Web开发学习实录》 第(1/2)分卷 PDF电子书 高清扫描版
#Ruby on rails:第一个应用程序 这是计算课程中的专题课程的第一个 Ruby on Rails 应用程序。 2014/02。
- Ruby on Rails 应用程序谷歌地图 API #HTTParty 连接到 openweathermap.org 这是一个 ruby on Rails 应用程序,它使用谷歌地图 API 来确定百老汇大会地址。 我使用 HTTParty gem 连接到 openweathermap.org ...
rails-样板用于快速创建 Rails 应用程序的 Ruby on Rails (RoR) 样板。安装步骤第 1 步 - 安装 Core Essentials sudo apt-get -y updatesudo apt-get -y upgradesudo apt-get -y install build-essential git第 2 步...
TTT1 Ruby on rails 教程第一个应用程序。
本书算是最好的rails的与ruby的教程。。。全书共144M,此为part2,part1: http://download.csdn.net/detail/jiaoxiaogu/3835985