鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起看了 Rails 的 i18n
怎麼做,今天來看官方推薦的 Rails 測試方法怎麼說,夠夠~
老實說我沒有用過 Rails 內建的測試,之前只有用過 Rspec
而已,先來看看預設的有什麼不一樣
Rails 內建的測試套件為 minitest
,預設被包在 ActiveSupport
裡,在建立專案時就一起安裝了
內建的測試會自動建立以下資料夾在 test/
底下,最主要分為
controllers
:測試 controller 層各 API 或 頁面models
:測試 model 層商業邏輯system
:開瀏覽器,以使用者的角度來做測試,預設使用 selenium + chrome
次要的測試還有
helpers
: 測試 controller 會用到的自訂 helpers 功能integration
: 跨 Controller 之間的測試mailers
: 測試 ActionMailer 的 mailer 運作channels
: 測試 ActionCable 的 websocket 運作輔助的測試
fixtures
: 事先定義好測試用的資料class ArticlesControllerTest < ActionDispatch::IntegrationTest
# called before every single test
setup do
@article = articles(:one)
end
# called after every single test
teardown do
# when controller is using cache it may be a good idea to reset it afterwards
Rails.cache.clear
end
test "should get index" do
get articles_url
assert_response :success
end
test "should create article" do
assert_difference("Article.count") do
post articles_url, params: { article: { body: "Rails is awesome!", title: "Hello Rails" } }
end
assert_redirected_to article_path(Article.last)
end
test "should show article" do
article = articles(:one)
get article_url(article)
assert_response :success
end
test "should destroy article" do
article = articles(:one)
assert_difference("Article.count", -1) do
delete article_url(article)
end
assert_redirected_to articles_path
end
test "should update article" do
article = articles(:one)
patch article_url(article), params: { article: { title: "updated" } }
assert_redirected_to article_path(article)
# Reload association to fetch updated data and assert that title is updated.
article.reload
assert_equal "updated", article.title
end
end
require "application_system_test_case"
class ArticlesTest < ApplicationSystemTestCase
test "viewing the index" do
visit articles_path
assert_selector "h1", text: "Articles"
end
test "should create Article" do
visit articles_path
click_on "New Article"
fill_in "Title", with: "Creating an Article"
fill_in "Body", with: "Created this article successfully!"
click_on "Create Article"
assert_text "Creating an Article"
end
end
預設在 test/application_system_test_case.rb
有定義 screen_size 為 1400 x 1400
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end
還可以自己新增一個 test/mobile_system_test_case.rb
定義出手機螢幕大小
require "test_helper"
class MobileSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :chrome, screen_size: [375, 667]
end
其他 system test 再去繼承 MobileSystemTestCase
就可以測試 RWD
require "mobile_system_test_case"
class PostsTest < MobileSystemTestCase
test "visiting the index" do
visit posts_url
assert_selector "h1", text: "Posts"
end
end
require "test_helper"
class BlogFlowTest < ActionDispatch::IntegrationTest
test "can see the welcome page" do
get "/"
assert_select "h1", "Welcome#index"
end
test "can create an article" do
get "/articles/new"
assert_response :success
post "/articles",
params: { article: { title: "can create", body: "article successfully." } }
assert_response :redirect
follow_redirect!
assert_response :success
assert_select "p", "Article was successfully created."
end
end
從上面的例子看起來,System Test
和 Integration Test
好像很相似,他們之間最大的差別是,System Test
是實際開瀏覽器起來執行的測試,而 Integration Test
沒有開瀏覽器
這個有開瀏覽器的差別,讓他們有了不同的使用情境,有開瀏覽器的 System Test
,就可以像上面提到的調整視窗大小,來測試 RWD 元件是不是正常運作,還有,因為有瀏覽器就可以測試 Javascript 是否正常運作,例如 bootstrap 的 navbar 元件,在小視窗時會需要 jquery 來展開 navbar,這就是 System Test
才能做到的測試
而 Integration Test
雖然不是透過開啟瀏覽器來做測試,但還是能跟靜態的 HTML 元件互動測試,因為 Rails 是 Server Side Render 麻,呼叫完 Controller Action 之後就會得到 HTML 了,瀏覽器並非必要的
Integration Test
主要是用來測試,重要的 Controller Actions 之間的運作邏輯是不是正常、API 之間的互動是否正常,像是能不能依照我們需要的欄位填入,來建立一篇部落格文章
Integration Test
能做到的,System Test
也能做到,所以如果你都要全部都是透過 System Test
來測也是可以,不過 System Test
因為要開瀏覽器,整個測試下來就會比較慢,所以就看個人囉
另外在 test
資料夾內,創一個 test_helper.rb
檔案,然後讓 ActionDispatch::IntegrationTest
去 include 我們擴充的 module
# test/test_helper.rb
module SignInHelper
def sign_in_as(user)
post sign_in_url(email: user.email, password: user.password)
end
end
class ActionDispatch::IntegrationTest
include SignInHelper
end
在需要使用 sign_in_as
的地方,去 require 我們剛建立的 test_helper
,就可以直接使用
require "test_helper"
class ProfileControllerTest < ActionDispatch::IntegrationTest
test "should show profile" do
# helper is now reusable from any controller test case
sign_in_as users(:david)
get profile_url
assert_response :success
end
end
如果 test_helper 很多的話,也可以再把結構拉出來,test/test_helpers/xxx_helper.rb
,然後直接在 test/test_helper.rb
去 eager requiring
# test/test_helper.rb
Dir[Rails.root.join("test", "test_helpers", "**", "*.rb")].each { |file| require file }
Minitest 內建的 assertions
assert
/ assert_not
assert_equal
/ assert_not_equal
assert_same
/ assert_not_same
assert_nil
/ assert_not_nil
assert_empty
/ assert_not_empty
assert_match
/ assert_not_match
assert_includes
/ assert_not_includes
assert_in_delta
/ assert_not_in_delta
assert_in_epsilon
/ assert_not_in_epsilon
assert_throws
assert_raises
assert_instance_of
/ assert_not_instance_of
assert_kind_of
/ assert_not_kind_of
assert_respond_to
/ assert_not_respond_to
assert_operator
/ assert_not_operator
assert_predicate
/ assert_not_predicate
Rails 擴充的 assertions
assert_difference
/ assert_not_difference
assert_changes
/ assert_not_changes
assert_nothing_raised
assert_recognizes
assert_generates
assert_response
assert_redirected_to
Views
assert_selector
assert_text
assert_select_email
assert_select_encoded
css_select
assert_dom_equal
ActionMailer
assert_emails
assert_enqueued_emails
ActionJob
assert_enqueued_with
ActionCable
assert_reject_connection
assert_has_stream
assert_has_stream_for
assert_broadcast_on
Fixture 的 yaml 檔支援 ERB
<% 1000.times do |n| %>
user_<%= n %>:
username: <%= "user#{n}" %>
email: <%= "user#{n}@example.com" %>
<% end %>
可自定義測試的 Worker 數量,加快測試速度
PARALLEL_WORKERS**=**15 bin/rails test
在測試中,可以透過 response
或 @response
得到 response 的內容,適合用在 debugging
@request
, @controller
, flash
, cookies
, session
, ,Minitest
require "test_helper"
class ArticleTest < ActiveSupport::TestCase
setup do
@article = articles(:one)
end
test 'title cant be blank' do
@article.title = nil
assert @article.valid?
assert_equal ["can't be blank"], @article.errors[:title]
end
test "body cant be blank" do
@article.body = nil
assert @article.valid?
assert_equal ["can't be blank"], @article.errors[:body]
end
end
Rspec
require 'rails_helper'
RSpec.describe Article, type: :model do
context 'cant be blank' do
article = Article.new
article.valid?
it 'must have a title' do
expect(article.errors.messages[:title]).to include("can't be blank")
end
it 'must have a body' do
expect(article.errors.messages[:body]).to include("can't be blank")
end
end
end
最後我從網路上文章找到範例,並改一下,讓他們變得有點像,然後來比較看看兩者的差異,Minitest 其實就是一般的 Ruby code,只是有包語法糖,讓 def test_title_cant_be_blank
可以寫成 test 'title cant be blank' do
而已
而 Rspec 比起 Minitest 來說,可以寫出更有結構的測試,可以指定不同的 context、每個 context 也可以有各個 unit test
我還真的是第一次用 Minitest
,覺得挺新奇的,不過從各個來源看來,Ruby 或 Rails 的開發者應該都比較偏好 Rspec
,我也不清楚為何 Rails 不預設用 Rspec
作為測試的框架就好,似乎有一段糾葛?
Minitest
確實用起來就是跟寫 Ruby 一樣,差不多就是只要會寫 Ruby、就會寫 Minitest
,而 Rspec
就是好像在寫新的語言的感覺,學習成本比較高一點
總之今天就是這樣拉,我們明天見~