二、迭代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
现在该商品现在有一个模型、一组视图、一个控制器和一组单元测试。
相关推荐
- **测试**:介绍如何为Ruby on Rails项目编写单元测试和集成测试。 - **AJAX**:学习如何在Ruby on Rails应用中集成异步JavaScript和XML技术。 - **部署、安全与性能**:讲解如何部署Ruby on Rails应用、实施安全...
### Ruby on Rails Guides v2 - Ruby on Rails 4.2.5 #### 一、重要概念及基础假设 - **重要概念**:本指南旨在帮助读者深入理解Ruby on Rails(以下简称Rails)4.2.5版本的核心功能与最佳实践。 - **基础假设**:...
Ruby on Rails,简称Rails,是基于Ruby编程语言的一个开源Web应用程序框架,它遵循MVC(模型-视图-控制器)架构模式,旨在提高开发效率和代码的可读性。Rails以其“约定优于配置”(Convention over Configuration)...
在这个全球互联的世界中,计算机编程和 Web 应用程序开发都在迅猛发展,我很期待能为中国的开发者提供 Ruby on Rails 培训。学习英语这门世界语言是很重要的,但先通过母语学习往往会更有效果。正因为这样,当看到 ...
Ruby on Rails,简称Rails,是一款基于Ruby语言的开源Web应用框架,它遵循MVC(Model-View-Controller)架构模式,旨在简化Web应用程序的开发。Rails由David Heinemeier Hansson于2004年创建,它提倡“约定优于配置...
Ruby on Rails,简称ROR或Rails,是一款基于Ruby语言的开源Web应用框架,它遵循Model-View-Controller(MVC)架构模式,旨在提高开发效率和代码可读性。本教程“Ruby on Rails 教程 - 201406”可能是针对2014年6月时...
接下来,书中会详细解释Rails的安装和配置过程,包括环境搭建、数据库配置以及Gemfile的使用,使读者能够快速创建并运行第一个Rails应用。同时,还会讲解Rails的核心组件,如路由、控制器、模型和视图,以及它们在...
- **第一个应用:** 创建完应用后,可以通过编写简单的代码来测试Rails的基本功能。例如,创建一个简单的“Hello, Rails!”页面,用来验证环境配置是否正确。 - **链接页面:** Rails提供了强大的路由机制,用于定义...
Ruby on Rails,简称Rails,是一种基于Ruby语言的开源Web应用框架,它遵循敏捷开发原则,致力于简化Web开发过程。Rails的核心理念是“Convention over Configuration”(约定优于配置),这意味着开发者可以减少大量...
1. **Gemfile**:列出项目依赖的Ruby gems(库),Rails和其他第三方库都通过Gemfile来管理。 2. **Gemfile.lock**:记录项目的精确gem版本,确保在不同环境中部署时保持一致性。 3. **config/**:配置文件夹,...
- **MVC架构**:文档中提到的MVC是Rails的核心架构模式,它将应用程序分为三个主要的组件:模型(Model)、视图(View)和控制器(Controller)。这种分离使得代码更加模块化,易于管理和扩展。 ### Rails应用的...
Ruby on Rails,简称Rails,是由David Heinemeier Hansson创建的一个开源Web应用程序框架,它基于Ruby编程语言。这个框架以其MVC(Model-View-Controller)架构、约定优于配置(Convention over Configuration)的...
- **Rails入门**:讲解如何安装配置Ruby on Rails环境,以及创建第一个Rails应用的过程。 - **数据库交互**:教授如何在Rails应用中使用ActiveRecord来操作数据库。 - **控制器与视图**:介绍Rails中的控制器和视图...
Ruby on Rails,简称Rails,是基于Ruby编程语言的一个开源Web应用程序框架,以其“约定优于配置”(Convention over Configuration)的设计哲学和“模型-视图-控制器”(MVC)架构模式,深受开发者喜爱。这套书全集...
### Ruby on Rails 指南 v5.0.1 中文版 #### Rails入门 - **前提条件**:为了能够顺利地开始Rails的学习之旅,读者需要具备一定的Ruby语言基础,并且对Web开发有一定的了解。 - **Rails是什么?**:Rails是一种...
4. 实践创建第一个Rails应用,了解路由、模型、控制器和视图。 5. 探索数据库操作和ActiveRecord,阅读相关章节。 6. 学习Rails的高级特性,如回调、观察者、缓存、异步处理等。 7. 熟悉测试,通过"Ruby On Rails[1]...
第一章“Ruby on Rails概述”,介绍了Ruby on Rails的基本概念和开发环境的搭建方法。通过这一章的学习,读者将对Rails框架有一个整体的认识,并了解如何搭建开发环境以及设计用户界面(UI)。 第二章“Rails中的...
- **定义**:Ruby on Rails(简称Rails)是一种用于Web应用程序开发的开源框架,基于Ruby编程语言。 - **特点**: - **约定优于配置**(Convention over Configuration, CoC):简化了开发过程中的配置步骤。 - **...