Rails 使用 RSpec 寫測試:入門操作篇


RSpec完整基礎教學(含8部影片)

前一篇提到RSpec在Rails當中的安裝,現在要實際執行一次BDD開發,讓大家了解實際流程為何。開發環境與前一篇相同,因此便不再贅述。

開發規格

每一次的網站開發都需要規劃出規格表,不管是為客戶規劃網站、或是新產品開發,都需要有詳細的規格,整個團隊才能目標相同。以下是這篇文章會完成的一個網站。

  1. 使用者能夠在網站上產生文章,文章會包含標題、內容欄位
  2. 使用者能夠產生評論,藉以回應文章,評論包含內容欄位
  3. 每個文章都會有多個回應
  4. 文章底下可以看到該篇文章所有回應
  5. 文章的標題、內容不能為空白
  6. 回應的內容不可為空白
  7. 網站首頁是所有文章列表

好了,我們開出了一個簡單的網站規格,我們來把這個東西寫成RSpec的測試,再根據這些測試的內容來撰寫網站程式碼。

首先,進行以下操作,將rails的應用程式建立起來,以及安裝RSpec

  1. $ rails new rspec_test
  2. 修改Gemfile安裝RSpec
  3. $ rails generate rspec:install
  4. 修改.rspec檔案解除warning

RSpec測試撰寫

接下來進入正文,根據以上開出來的規格,我們需要建立post和comment的model,讓兩者有從屬關係,以及建立相對應的controller和view。

我們在spec目錄底下,建立一個 integration_spec.rb 檔案,將所有測試寫在裡面,因為稍後我們使用scaffold建立起來資料以後,會有非常多不必要的測試項目,那些部分我們就都跳過。以下是測試內容:

require 'rails_helper'

#這裡進行Post的model測試
RSpec.describe Post, :type => :model do
    #在每一個it的測試項目之前,都先建立一個post資料
    before(:each) do
        @post = Post.new
    end

    #測試post是否包含標題和內容
    it "should contain :title & :content" do
        @post.title
        @post.content
    end

    #測試post如果標題空白,就不能儲存
    it ":title should not be blank" do
        @post.title = nil
        @post.save.should == false
    end

    #測試post如果內容空白,就不能儲存
    it ":content should not be blank" do
        @post.content = nil
        @post.save.should == false
    end

    #測試post要和comment建立has_many的關係
    it "has many comments" do
        $post = Post.reflect_on_association(:comments)
        $post.macro.should == :has_many
    end
end

#這裡進行Comment的model測試
RSpec.describe Comment, :type => :model do
    before(:each) do
        @comment = Comment.new
    end

    #測試comment是否包含內容
    it "should contain :content" do
        @comment.content
    end

    #測試comment如果內容空白,就不能儲存
    it ":content should not be blank" do
        @comment.content = nil
        @comment.save.should == false
    end

    #測試comment要和post建立belongs_to的關係
    it "belongs to post" do
        $comment = Comment.reflect_on_association(:post)
        $comment.macro.should == :belongs_to
    end
end

由於目前我們什麼資料都還沒有,所以測試都不會通過。為了解決測試項目,我們現在就來實際操作,架構網站。

$ rails generate scaffold post title content
$ rails generate scaffold comment post_id:integer content

這兩個指令會產生許多資料,但我們實際會用到的其實沒那麼多。接下來我們直接來解決測試的問題。

post.rb

class Post < ActiveRecord::Base
  validates :title, :content, presence: true
  has_many :comments
end

comment.rb

class Comment < ActiveRecord::Base
  validates :content, presence: true
  belongs_to :post
end

以上兩個檔案的修改,就足以通過所有測試。這時候我們到Command Line執行  $rspec spec/integration_spec.rb 就會看到八項測試全部通過啦!

你可能會覺得,為什麼這麼簡單的東西,測試要寫得這麼複雜?因為目前我們的網站架構還非常簡單,測試內容相對看起來很多,但等到網站規模超大的時候,相對之下測試檔案的內容會是非常少的!

測試route和view:

不過在規格表上還有幾個東西需要解決的:

  1. 文章底下可以看到該篇文章所有回應
  2. 網站首頁是所有文章列表

如果要在文章底下看到所有回應,代表我們要在view裡面可以檢查有comment的出現,而首頁的設定也需要在route上進行撰寫。以下是我們繼續在integration_spec.rb檔案當中增加的項目:

#這裡進行Post的view測試
RSpec.describe "posts/show", :type => :view do
  #建立post和comment的資料,讓同一筆post裡頭含有多筆comment
  before(:each) do
    @post = assign(:post, Post.create(:title => "Title", :content => "Content"))
    @comment_1 = @post.comments.create(:content => "display_comment_1")
    @comment_2 = @post.comments.create(:content => "display_comment_2")
  end

  #測試view裡面,post的顯示頁面要同時顯示他所有的comment
  it "renders comments by post" do
    render
    rendered.should include("display_comment_1")
    rendered.should include("display_comment_2")
  end
end

#這裡進行route測試
RSpec.describe "Routing root", :type => :routing do
    #測試網站首頁是包含所有文章的post index頁面
    it "to posts index" do
        expect(:get => "/").to route_to("posts#index")
    end

為了通過以上測試,首先先找到config/routes.rb檔案,增加以下幾行:

resources :posts do
  resources :comments
end
  root to: "posts#index"

上面檔案第04行的部份,就等於把首頁設定到文章列表,已滿足RSpec的測試,所以最後我們就是要確保在post的show.html.erb當中可以看到所有的comments。我們在以下頁面增加幾行程式碼:

  def show
    @comments = @post.comments.all
  end

app/views/posts/show.html.erb

<% @post.comments.each do |c| %>
    <%= c.content %>
<% end %>

<%= link_to "New Comment", new_post_comment_path(@post) %>

這樣子在post的show.html.erb當中,就可以看到該篇post的所有comment,不過因為我們是使用scaffold的方法創造兩個model,所以在路徑上還有許多預設路徑需要修改,整個網站才能正常運作,以下是需要修改的地方:

  1. 修改第01行

app/views/comments/_form.html.erb

<%= form_for [@post, @comment] do |f| %>
  1. 修改第05行

app/views/posts/new.html.erb

<%= link_to 'Back', post_path(@post) %>
  1. 修改new的action,約莫第16行

posts_controller.rb

def new
  @post = Post.find(params[:post_id])
  @comment = @post.comments.build
end

修改完成以後,再執行一次測試,9個測試都順利通過!我們順利的用RSpec建立一個Rails網站囉!

整體網站示範內容請看Github。