Rails 应用程序测试指南
在开发 Rails 应用程序时,测试是确保代码质量和功能正确性的重要环节。本文将详细介绍如何对 Rails 应用程序进行单元测试和功能测试。
单元测试
Rails 会自动生成一些测试文件,我们可以从这些文件开始进行单元测试。以
Article
模型的测试为例,打开
test/model/article_test.rb
文件:
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
这个测试文件虽然简单,但为我们提供了一个构建实际测试的模板。它包含以下几个关键元素:
-
测试类
:是
ActiveSupport::TestCase
的子类,
ActiveSupport::TestCase
是 Rails 对 Ruby 内置测试框架
Test::Unit
的增强版本。
-
测试方法
:使用
test
方法实现,第一个参数是测试的描述。
-
断言
:在测试用例中,使用断言来测试预期结果。
在
test/model
目录下,每个模型都有一个类似的测试文件。我们可以使用
rake test:models
命令来运行单元测试:
$ rake test:models
Run options: --seed 45237
# Running tests:
Finished tests in 0.020474s, 0.0000 tests/s, 0.0000 assertions/s.
0 tests, 0 assertions, 0 failures, 0 errors, 0 skips
由于生成的测试用例被注释掉了,所以目前没有实际的测试运行。如果测试通过,会显示一个
.
;如果测试用例出错,会显示
E
;如果断言返回
false
,会显示
F
;测试套件完成后,会打印一个总结信息。
测试 Article 模型
我们可以从基本的 CRUD 操作开始测试
Article
模型,具体包括:
- 创建新文章
- 查找文章
- 更新文章
- 删除文章
在开始测试之前,我们需要创建一些测试数据的固定装置(fixtures)。打开
test/fixtures/articles.yml
文件,将其内容替换为:
welcome_to_rails:
user: eugene
title: "Welcome to Rails"
body: "Rails is such a nice web framework written in Ruby"
published_at: <%= 3.days.ago %>
固定装置中的数据会在测试运行之前自动插入到测试数据库中。
添加创建测试
打开
test/models/article_test.rb
文件,删除
test "the truth"
方法,并替换为
test "should create article"
方法:
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
test "should create article" do
article = Article.new
article.user = users(:eugene)
article.title = "Test article"
article.body = "Test body"
assert article.save
end
end
这个测试用例模拟了从控制台创建新文章的过程,最后使用
assert article.save
断言文章是否成功保存。
断言测试
断言是对预期结果的声明。
Test::Unit
提供了一系列内置的断言,Rails 也添加了一些自定义的断言。以下是
Test::Unit
的标准断言列表:
-
assert(boolean, message=nil)
-
assert_block(message="assert_block failed.") do ... end
-
assert_equal(expected, actual, message=nil)
-
assert_in_delta(expected_float, actual_float, delta, message="")
-
assert_instance_of(klass, object, message="")
-
assert_kind_of(klass, object, message="")
-
assert_match(pattern, string, message="")
-
assert_nil(object, message="")
-
assert_no_match(regexp, string, message="")
-
assert_not_equal(expected, actual, message="")
-
assert_not_nil(object, message="")
-
assert_not_same(expected, actual, message="")
-
assert_nothing_raised(*args) do ... end
-
assert_nothing_thrown(message="") do ... end
-
assert_operator(object1, operator, object2, message="")
-
assert_raise(expected_exception_klass, message="") do ... end
-
assert_respond_to(object, method, message="")
-
assert_same(expected, actual, message="")
-
assert_send(send_array, message="")
-
assert_throws(expected_symbol, message="") do ... end
在
test "should create article"
测试用例中,使用的
assert article.save
是最基本的断言之一,它断言
article.save
的返回值为
true
。
运行测试:
$ rake test:models
Started
.
Finished in 0.168879 seconds.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
测试通过,说明文章创建功能正常。
添加查找测试
在
test/models/article_test.rb
文件中添加
test "should find article"
方法:
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
test "should create article" do
article = Article.new
article.user = users(:eugene)
article.title = "Test article"
article.body = "Test body"
assert article.save
end
test "should find article" do
article_id = articles(:welcome_to_rails).id
assert_nothing_raised { Article.find(article_id) }
end
end
这个测试用例从固定装置中获取文章的
id
,然后使用
Article.find
方法查找文章。使用
assert_nothing_raised
断言查找过程中不会抛出异常。
运行测试:
$ rake test:models
Run options: --seed 36921
# Running tests:
...
Finished tests in 0.068310s, 29.2781 tests/s, 14.6391 assertions/s.
2 tests, 1 assertions, 0 failures, 0 errors, 0 skips
测试通过,说明文章查找功能正常。
添加更新测试
在
test/models/article_test.rb
文件中添加
test "should update article"
方法:
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
test "should create article" do
article = Article.new
article.user = users(:eugene)
article.title = "Test article"
article.body = "Test body"
assert article.save
end
test "should find article" do
article_id = articles(:welcome_to_rails).id
assert_nothing_raised { Article.find(article_id) }
end
test "should update article" do
article = articles(:welcome_to_rails)
assert article.update_attributes(:title => 'New title')
end
end
这个测试用例从固定装置中获取文章,然后使用
update_attributes
方法更新文章的标题。使用
assert
断言更新操作成功。
运行测试:
$ rake test:models
Run options: --seed 2804
# Running tests:
...
Finished tests in 0.073883s, 40.6050 tests/s, 27.0700 assertions/s.
3 tests, 2 assertions, 0 failures, 0 errors, 0 skips
测试通过,说明文章更新功能正常。
添加删除测试
在
test/models/article_test.rb
文件中添加
test "should destroy article"
方法:
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
test "should create article" do
article = Article.new
article.user = users(:eugene)
article.title = "Test article"
article.body = "Test body"
assert article.save
end
test "should find article" do
article_id = articles(:welcome_to_rails).id
assert_nothing_raised { Article.find(article_id) }
end
test "should update article" do
article = articles(:welcome_to_rails)
assert article.update_attributes(:title => 'New title')
end
test "should destroy article" do
article = articles(:welcome_to_rails)
article.destroy
assert_raise(ActiveRecord::RecordNotFound) { Article.find(article.id) }
end
end
这个测试用例从固定装置中获取文章,然后使用
destroy
方法删除文章。使用
assert_raise
断言在尝试查找已删除的文章时会抛出
ActiveRecord::RecordNotFound
异常。
运行测试:
$ rake test:models
Run options: --seed 64949
# Running tests:
....
Finished tests in 0.078197s, 51.1526 tests/s, 38.3644 assertions/s.
4 tests, 3 assertions, 0 failures, 0 errors, 0 skips
测试通过,说明文章删除功能正常。
测试验证
Article
模型有一些验证规则,例如标题和正文必须存在。我们可以添加一个测试用例来验证这些规则:
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
test "should create article" do
article = Article.new
article.user = users(:eugene)
article.title = "Test article"
article.body = "Test body"
assert article.save
end
test "should find article" do
article_id = articles(:welcome_to_rails).id
assert_nothing_raised { Article.find(article_id) }
end
test "should update article" do
article = articles(:welcome_to_rails)
assert article.update_attributes(:title => 'New title')
end
test "should destroy article" do
article = articles(:welcome_to_rails)
article.destroy
assert_raise(ActiveRecord::RecordNotFound) { Article.find(article.id) }
end
test "should not create an article without title nor body" do
article = Article.new
assert !article.valid?
assert article.errors[:title].any?
assert article.errors[:body].any?
assert_equal ["can't be blank"], article.errors[:title]
assert_equal ["can't be blank"], article.errors[:body]
assert !article.save
end
end
这个测试用例创建一个没有标题和正文的
Article
对象,然后使用一系列断言来验证该对象无效,并且错误信息符合预期。
运行测试:
$ rake test:models
Run options: --seed 49599
# Running tests:
.....
Finished tests in 0.108590s, 46.0446 tests/s, 82.8802 assertions/s.
5 tests, 9 assertions, 0 failures, 0 errors, 0 skips
测试通过,说明验证规则正常工作。
当需求发生变化时,我们可能需要更新测试用例。建议先更新测试用例(这会使测试失败),然后更新代码(使测试通过),这就是测试驱动开发(TDD)。
功能测试
功能测试用于检查控制器的功能。与单元测试不同,功能测试在 Web 应用程序的上下文中进行,涉及 Web 请求和响应以及 URL。
测试 Articles 控制器
打开
test/functional/articles_controller_test.rb
文件:
require 'test_helper'
class ArticlesControllerTest < ActionController::TestCase
# ...
end
与单元测试一样,首先需要引入
test_helper
文件。
ArticlesControllerTest
是
ActionController::TestCase
的子类,它会自动创建
@controller
、
@request
和
@response
三个实例变量,用于模拟控制器环境。
在测试之前,我们需要创建一个测试辅助方法来模拟用户登录。打开
test/test_helper.rb
文件,添加
login_as
方法:
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'rails/test_help'
class ActiveSupport::TestCase
ActiveRecord::Migration.check_pending!
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
fixtures :all
# Add more helper methods to be used by all tests here...
def login_as(user)
@request.session[:user_id] = users(user).id
end
end
login_as
方法将用户的
id
设置到
@request.session
中,模拟用户登录状态。
测试索引动作
更新
test/controllers/articles_controller_test.rb
文件中的
setup
方法和
test "should get index"
方法:
require 'test_helper'
class ArticlesControllerTest < ActionController::TestCase
setup do
@article = articles(:welcome_to_rails)
end
test "should get index" do
get :index
assert_response :success
assert_template 'index'
assert_not_nil assigns(:articles)
end
test "should get new" do
get :new
assert_response :success
end
test "should create article" do
assert_difference('Article.count') do
post :create, :article => @article.attributes
end
assert_redirected_to article_path(assigns(:article))
end
test "should show article" do
get :show, :id => @article.to_param
assert_response :success
end
test "should get edit" do
get :edit, :id => @article.to_param
assert_response :success
end
test "should update article" do
put :update, :id => @article.to_param, :article => @article.attributes
assert_redirected_to article_path(assigns(:article))
end
test "should destroy article" do
assert_difference('Article.count', -1) do
delete :destroy, :id => @article.to_param
end
assert_redirected_to articles_path
end
end
setup
方法在每个测试用例之前执行,将
:welcome_to_rails
记录从固定装置中赋值给
@article
实例变量。
test "should get index"
方法使用
get :index
发送一个 GET 请求到
index
动作,然后使用以下断言来验证响应:
-
assert_response :success
:断言响应成功。
-
assert_template 'index'
:断言渲染的模板是
index
。
-
assert_not_nil assigns(:articles)
:断言
assigns(:articles)
不为
nil
。
assert_response
是 Rails 自定义的断言,用于验证响应的状态码。以下是
assert_response
可用的状态码快捷方式:
| 符号 | 含义 |
| ---- | ---- |
|
:success
| 状态码为 200 |
|
:redirect
| 状态码在 300 - 399 范围内 |
|
:missing
| 状态码为 404 |
|
:error
| 状态码在 500 - 599 范围内 |
通过以上单元测试和功能测试,我们可以确保
Article
模型和
Articles
控制器的功能正常工作。在开发过程中,不断编写和更新测试用例可以帮助我们及时发现和解决问题,提高代码的质量和可维护性。
Rails 应用程序测试指南
后续测试及注意事项
在完成了对
Article
模型和
Articles
控制器的基本测试后,我们还可以进一步拓展测试范围,以确保应用程序的稳定性和可靠性。
异常处理测试
除了对正常流程进行测试,我们还需要对异常情况进行测试。例如,在查找文章时,如果传入的
id
不存在,应该返回相应的错误信息。我们可以在
ArticleTest
类中添加如下测试方法:
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
# 之前的测试方法...
test "should raise error when finding non-existent article" do
non_existent_id = 9999
assert_raise(ActiveRecord::RecordNotFound) { Article.find(non_existent_id) }
end
end
运行测试:
$ rake test:models
Run options: --seed [随机种子]
# Running tests:
......
Finished tests in [时间]s, [测试速度] tests/s, [断言速度] assertions/s.
[测试数量] tests, [断言数量] assertions, 0 failures, 0 errors, 0 skips
这个测试用例使用
assert_raise
断言当查找一个不存在的文章
id
时,会抛出
ActiveRecord::RecordNotFound
异常。
边界条件测试
对于
Article
模型的验证规则,我们还可以进行边界条件测试。例如,标题和正文的长度限制。假设我们在模型中添加了长度限制:
class Article < ApplicationRecord
validates :title, presence: true, length: { minimum: 5, maximum: 100 }
validates :body, presence: true, length: { minimum: 10, maximum: 1000 }
end
我们可以添加如下测试用例:
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
# 之前的测试方法...
test "should not create article with short title" do
article = Article.new
article.user = users(:eugene)
article.title = "abc"
article.body = "This is a test body."
assert !article.valid?
assert article.errors[:title].any?
assert_equal ["is too short (minimum is 5 characters)"], article.errors[:title]
assert !article.save
end
test "should not create article with long title" do
article = Article.new
article.user = users(:eugene)
article.title = "a" * 101
article.body = "This is a test body."
assert !article.valid?
assert article.errors[:title].any?
assert_equal ["is too long (maximum is 100 characters)"], article.errors[:title]
assert !article.save
end
test "should not create article with short body" do
article = Article.new
article.user = users(:eugene)
article.title = "Test title"
article.body = "abc"
assert !article.valid?
assert article.errors[:body].any?
assert_equal ["is too short (minimum is 10 characters)"], article.errors[:body]
assert !article.save
end
test "should not create article with long body" do
article = Article.new
article.user = users(:eugene)
article.title = "Test title"
article.body = "a" * 1001
assert !article.valid?
assert article.errors[:body].any?
assert_equal ["is too long (maximum is 1000 characters)"], article.errors[:body]
assert !article.save
end
end
这些测试用例分别测试了标题和正文长度过短和过长的情况,确保验证规则在边界条件下也能正常工作。
测试流程总结
为了更清晰地展示测试的流程,我们可以使用 mermaid 流程图:
graph LR
A[开始] --> B[创建测试数据固定装置]
B --> C[编写单元测试用例]
C --> D[运行单元测试]
D --> E{单元测试是否通过}
E -- 是 --> F[编写功能测试用例]
E -- 否 --> C
F --> G[运行功能测试]
G --> H{功能测试是否通过}
H -- 是 --> I[完成测试]
H -- 否 --> F
持续集成与测试
在实际开发中,我们可以将测试集成到持续集成(CI)流程中。例如,使用 Travis CI 或 GitLab CI/CD 等工具,每次代码提交或合并时自动运行测试。以下是一个简单的
.travis.yml
配置示例:
language: ruby
rvm:
- 2.7.6
before_script:
- bundle install
- rake db:create
- rake db:migrate RAILS_ENV=test
script:
- rake test
这个配置文件指定了 Ruby 版本,在测试前安装依赖、创建数据库和迁移,然后运行测试。
总结
通过对
Article
模型和
Articles
控制器的单元测试和功能测试,我们可以全面地验证应用程序的功能。在测试过程中,我们需要考虑正常流程、异常情况和边界条件,确保应用程序的稳定性和可靠性。同时,将测试集成到持续集成流程中,可以及时发现代码变更带来的问题,提高开发效率和代码质量。
在未来的开发中,我们还可以进一步拓展测试范围,例如进行集成测试、性能测试和安全测试等,以确保应用程序在各种场景下都能正常工作。
超级会员免费看

被折叠的 条评论
为什么被折叠?



