Link Search Menu Expand Document

4. 用戶驗收測試

4-1 目標

單元測試是針對單一類別和方法進行測試,但是對一個系統來說,單一組件運作正常,不代表整個系統運作正常。就像這張 GIF 圖一樣,鎖是正常運作的,門也是正常運作的,但是組起來不OK啊。

image

剛才我們只測試了 Parking model,但是整個 Rails 還包括 Router、Controller 和 View。

這時候可以撰寫所謂的用戶驗收測試,在 Rspec 中叫做 Feature Spec。這會模擬用戶操作瀏覽器的行為,來對 Rails 進行整合性的測試。

在稍後的補充我們會提到也可以針對 Router、Controller 和 View 進行做單元測試,但是效益不大,因為用 Model Spec 和 Feature Spec 就可以大致涵蓋我們的測試需要了。

4-2 Capybara 安裝

Capybara 這個 gem 會用來搭配 Rspec 進行 Feature Spec 測試,請先安裝:

  group :development, :test do
    gem 'rspec-rails'
+   gem 'capybara'
    gem 'byebug', platform: :mri
  end

執行 bundle

編輯 spec/rails_helper.rb

   # (略)
   require 'spec_helper'
   require 'rspec/rails'

+  require 'capybara/rails'
+  require 'capybara/rspec'

4-3 測試「一般費率」繳費流程

執行 mkdir spec/features 建立驗收測試的目錄

新增 spec/features/guest_spec.rb

require 'rails_helper'

feature "parking", :type => :feature do

  scenario "guest parking" do
    # Step 1
    visit "/"            # 瀏覽首頁
    # save_and_open_page # 這會存下測試當時的 HTML 頁面

    expect(page).to have_content("一般費率") # 檢查 HTML 中要出現 "一般費率" 文字

    # Step 2
    click_button "開始計費" # 按這個按鈕
    # Step 3:
    click_button "結束計費" # 按這個按鈕
    # Step 4: 看到費用畫面
    expect(page).to have_content("¥2.00")  # 檢查 HTML 中要出現 ¥2.00 文字
  end

end


執行 rspec spec/features/guest_spec.rb 測試會通過。

你可以試試看稍微更動畫面的文字,就會發現測試失敗。例如拿掉開始計費按鈕之類的。

說明一下:

  1. feature 的作用等同於 describescenario 的作用等同於 it
  2. 這裡用了 have_content 來檢查指定文字有沒有出現在 HTML 裡面。Web 應用的測試,沒辦法去完整比對 HTML 字串,因為字串比對差一個空白就不一樣。如果說設計師稍微多加一個 <br> 就要改測試,這樣就太累了,測試會太敏感。所以我們只能檢查說 HTML 裡面有出現我們希望要有的關鍵字。
  3. 注意到我們不需要再重復測試金額對不對了,在前幾章單元測試中我們已經確定這部分的元件正常。Feature Spec 的重點在於檢查東西(Model + Controller + View)接起來有沒有正常運作。
  4. 被註解掉的 save_and_open_page 會存下測試當時的 HTML 頁面,除錯的時候可以使用。如果打開的話,跑測試會出現:

image

你會找到存下來的 HTML 檔名,執行 open tmp/capybara/capybara-xxxxxx.html 就可以直接用預設瀏覽器打開看看。

4-4 測試註冊流程

測試用戶註冊的流程:

require 'rails_helper'

feature "register and login", :type => :feature do

  scenario "register" do
    visit "/users/sign_up"  # 瀏覽註冊頁面

    expect(page).to have_content("Sign up")

    within("#new_user") do  # 填表單
      fill_in "Email", with: "foobar@example.com"
      fill_in "Password", with: "12345678"
      fill_in "Password confirmation", with: "12345678"
    end

    click_button "Sign up"

    # 檢查文字。這文字是 Devise 預設會放在 flash[:notice] 上的
    expect(page).to have_content("Welcome! You have signed up successfully!")

    # 檢查資料庫裡面最後一筆真的有剛剛填的資料
    user = User.last
    expect(user.email).to eq("foobar@example.com")
  end

end

這裡我們模擬用戶填寫表單送出的情況,用fill_in可以填入值。最後除了檢查畫面上有沒有出現想要的關鍵字,也可以檢查資料有沒有正確存進資料庫。

4-5 測試登入登出流程

測試用戶登入登出的流程:

  feature "register and login", :type => :feature do

+  scenario "login and logout" do
+    # 先建立一個測試用的用戶在資料庫
+    user = User.create!( :email => "foobar@example.com", :password => "12345678")
+
+    visit "/users/sign_in"
+
+    within("#new_user") do
+      fill_in "Email", with: "foobar@example.com"
+      fill_in "Password", with: "12345678"
+    end
+
+    click_button "Log in" # 點擊登入按鈕
+    expect(page).to have_content("Signed in successfully")
+
+    click_link "登出"  # 點擊主選單的登出超連結
+    expect(page).to have_content("Signed out successfully")
+  end

  end

4-6 測試「短期費率」流程

要操作「短期費率」必須登入。但是登入剛剛已經測試過了,不需要再用 capybara 再走一次。Devise 有提供測試用的 sign_in 方法,請修改 spec/rails_helper.rb

  RSpec.configure do |config|

+    config.include Devise::Test::ControllerHelpers, type: :controller
+    config.include Devise::Test::ControllerHelpers, type: :view
+    config.include Devise::Test::IntegrationHelpers, type: :feature

  # (略)

新增 spec/features/short_term_spec.rb

require 'rails_helper'

feature "parking", :type => :feature do

  scenario "short-term parking" do
    user = User.create!( :email => "foobar@example.com", :password => "12345678")
    sign_in(user) # 這樣就可以登入了

    visit "/"
    choose "短期費率"  # 選 radio button

    click_button "開始計費"

    click_button "結束計費"

    expect(page).to have_content("¥2.00")
  end

end

執行 rspec spec/features/short_term_spec.rb 測試通過。

這裡用了 choose 來對 Radio 按鈕做選擇,在 capybara 中還有提供其他方法針對不同表單元件做操作,例如:

  • check “核選方塊名稱”
  • uncheck “核選方塊名稱”
  • select “選項名稱”, :from => “下拉選單名稱”
  • attach_file 上傳檔案

詳細用法請參考 capybara 文件。

4-7 故意修改 Model API

做到這裡,假設我們想回來修改一下 Model,這個 calculate_amount 其實可以放在回呼(callback)裡面,這樣只要 save 存進資料庫時就會自動計算,不需要手動呼叫calculate_amount,也不需要檢查 amount 必填了。然後我們有點故意地順便改個方法名稱:

+  before_validation :setup_amount

-  def calculate_amount
+  def setup_amount

   # (略)
   def validate_end_at_with_amount
-    if ( end_at.present? && amount.blank? )
-      errors.add(:amount, "有結束時間就必須有金額")
-    end

修改 spec/models/parking_spec.rb 中把所有 calculate_amount 改成 save(有多處請都修改到):


-        @parking.calculate_amount

+        @parking.save

之前提過測試案例跟案例之間是互相獨立、不會互相影響的(這樣測試失敗時,才不用懷疑是不是被別的測試影響到)。每個測試案例,裡面需要的物件會重新建立。跑自動化測試用的資料庫跟開發用的不一樣(在config/database.yml裡面會設定test環境用的資料庫,並用 db/schema.rb 的定義建立測試用資料庫)。另外,如果你有 save 存進資料庫的話,Rails 也會在跑完測試案例後,自動復原砍掉該測試案例新增的資料。

刪除檢查 amount 必填的測試:

-     it "is invalid without amount" do
-       parking = Parking.new( :parking_type => "guest",
-                              :start_at => Time.now - 6.hours,
-                              :end_at => Time.now)
-       expect( parking ).to_not be_valid
-     end

然後再跑一次 rspec spec/models/parking_spec.rb 測試全部通過。

但是呢,這個 Parking model 元件是好的。但是其實跑驗收其實是爛的,因為我們改了 Parking model 的 API。

執行 rspec spec/features/guest_spec.rb

image

這告訴我們單元測試的局限性,驗收測試幫助我們檢查了整個系統整合起來是正常的。

修改 app/controllers/parkings_controller.rb,砍掉 @parking.calculate_amount

   def update
     @parking = Parking.find(params[:id])
     @parking.end_at = Time.now
-    @parking.calculate_amount

     @parking.save!

    redirect_to parking_path(@parking)
  end

再跑一次 rspec spec/features/guest_spec.rb 就通過了。

剛剛都是跑單個測試檔案,如果要跑全部測試,可以執行 rake spec

4-8 小結

剛剛都是跑單個測試檔案,要一次跑全部的測試的話,請執行 rake spec

我們會在 git push 前,盡量跑過一次全部的測試進行檢查。實際部署上 production 伺服器前,也一定會執行 rake spec 檢查所有測試都必須通過。一個專案如果有良好的測試涵蓋,那麽透過執行自動化測試可以大幅減少人工測試的時間,確保這次的修改一切功能正常,增加成功上線的把握。

至於驗收測試什麽時候寫? 要寫多少呢?

  • 相對於單元測試,驗收測試通常是在功能完成之後才撰寫,主要的目的其實是做「回歸測試(Regression Testing」,旨在檢驗軟體原有功能在修改後是否保持正常,每次部署新版本上線前,會跑全部的測試做回歸測試檢查,看看有沒有東西被弄壞了,是一種投資未來的測試。
  • 單元測試通常是跟該功能一起由開發者完成,驗收測試則會另外開任務,並可能由另一個開發者(或專門的測試工程師)來完成會更好。
  • 撰寫驗收測試會花額外的時間,而且比較脆弱。因為頁面流程一改、或是改個文案,測試就得跟著改。因此通常我們只會針對網站最常用的功能(Happy Path)來撰寫驗收測試,這樣投資報酬率最高。
  • 驗收測試很難完全取代人工驗證,因為有太多東西是很難驗證的,例如 CSS、畫面顏色、按鈕位置等等。如果要做到自動檢查畫面完全一模一樣,會耗費很大的維護成本。
  • 因此相對於新創公司還在時常變動功能的軟體,成熟期的軟體比較會投資在完整的驗收測試,特別是 B2B 領域,因為軟體出錯對客戶造成的損失是很大的。

Copyright © 2010-2022 Wen-Tien Chang All Rights Reserved.